Files
XCEngine/docs/api/XCEngine/RHI/D3D12/D3D12Sampler/D3D12Sampler.md

3.8 KiB
Raw Blame History

D3D12Sampler

命名空间: XCEngine::RHI

类型: class

头文件: XCEngine/RHI/D3D12/D3D12Sampler.h

描述: D3D12 后端的采样器包装,但当前实现本质上只是一个 D3D12_SAMPLER_DESC 缓存对象,而不是独立的 GPU sampler 资源句柄。

概览

D3D12Sampler 在当前引擎里承担的是“描述数据载体”角色。

它做的事情很少,但很关键:

  • 保存一份已经转换成 D3D12 原生格式的 D3D12_SAMPLER_DESC
  • 作为跨后端接口 RHISampler 的 D3D12 实现
  • D3D12DescriptorSet 在真正写入 descriptor heap 时,可以从对象里取到 sampler 描述

它当前不做这些事情:

  • 不创建独立的 D3D12 sampler 对象
  • 不持有 descriptor heap 槽位
  • 不执行类似 OpenGL Bind(unit) 的状态绑定

设计定位

D3D12 的 sampler 语义和 OpenGL 很不一样。OpenGL 常把 sampler 看成一个可绑定对象,而 D3D12 更接近“把采样状态写成 descriptor再由 descriptor table 参与绑定”。

这也是当前实现只保存 D3D12_SAMPLER_DESC 的原因:

  • D3D12Device 负责把引擎通用 SamplerDesc 翻译成 D3D12 描述
  • D3D12DescriptorSetUpdateSampler() 中调用 ID3D12Device::CreateSampler(...),把描述真正写进目标 heap

这种设计的好处是实现非常直接,能很好贴合 D3D12 的 descriptor 模型。代价是文档必须讲清楚:D3D12Sampler 不是资源拥有者,它只是后续写 descriptor 所需的数据源。

生命周期

  • 构造后把 m_desc 清零,m_id 保持默认值 0
  • Initialize 只复制一份 D3D12_SAMPLER_DESC
  • 真正的 GPU 可见 sampler descriptor 通常在 D3D12DescriptorSet::UpdateSampler() 期间才写入 heap
  • Shutdown 只把内部描述清零,不回收任何 heap 槽位
  • 析构时自动调用 Shutdown

当前实现的真实行为

  • Initialize() 完全忽略传入的 ID3D12Device*
  • Initialize() 不做合法性校验,也不调用 ID3D12Device::CreateSampler(...)
  • GetNativeHandle 返回的是 &m_desc,不是原生 D3D12 句柄
  • GetID 返回 m_id,但当前实现从未给它赋过新值,因此始终是 0
  • BindUnbind 是空实现
  • Shutdown 会清零 m_desc,但不会重置 m_id

为什么这样设计

从商业级引擎实践看D3D12 后端通常会把 sampler 设计成“不可变描述 + descriptor heap 写入”两段式流程,而不是像旧式 API 那样在对象上直接做 bind。

这种分层有几个明显好处:

  • 采样状态可以和纹理资源解耦,便于 descriptor set 统一管理
  • 后端不需要维护额外的 sampler 对象生命周期
  • 测试和资源绑定代码都可以围绕 descriptor heap 展开,而不是围绕状态机展开

当前 XCEngine 的实现选择了这条路线中的最轻量版本:先保证绑定链路可用,再逐步补缓存、复用和校验能力。

当前限制

  • 不是 GPU 资源拥有者,只是描述缓存
  • 不做参数校验,错误的 D3D12_SAMPLER_DESC 也会被接受
  • 没有 sampler 去重、缓存或共享机制
  • 没有真正可用于调试的唯一 ID
  • Bind / Unbind 对 D3D12 调用方没有实际效果

关键方法

相关文档