docs(api): deepen D3D12 sampler and screenshot docs

This commit is contained in:
2026-03-28 00:18:24 +08:00
parent 3d2fd5c8ee
commit f55c6d8ffb
14 changed files with 448 additions and 237 deletions

View File

@@ -1,31 +1,36 @@
# D3D12Sampler::Bind
公开方法,详见头文件声明。
```cpp
void Bind(unsigned int unit) override;
```
该方法声明于 `XCEngine/RHI/D3D12/D3D12Sampler.h`,当前页面用于固定 `D3D12Sampler` 类目录下的方法级 canonical 路径。
## 作用
**参数:**
- `unit` - 参数语义详见头文件声明。
保留跨后端统一接口中的“绑定采样器”入口。
**返回:** `void` - 无返回值。
## 当前实现行为
**示例:**
- 函数体为空
- 不读取 `unit`
- 不修改 descriptor heap
- 不修改命令列表、根签名或任何 D3D12 GPU 状态
```cpp
#include <XCEngine/RHI/D3D12/D3D12Sampler.h>
## 为什么是空实现
void Example() {
XCEngine::RHI::D3D12Sampler object;
// 根据上下文补齐参数后调用 D3D12Sampler::Bind(...)。
(void)object;
}
```
D3D12 不是按“把 sampler 绑到某个纹理单元”来工作的。真正的绑定路径通常是:
- 把 sampler descriptor 写入 shader-visible descriptor heap
- 再由 descriptor table 和 root signature 在命令列表上完成绑定
因此在当前后端设计里,采样器绑定的核心入口不是这里,而是 descriptor set 更新与命令列表绑定流程。
## 使用建议
- 不要指望调用 `Bind(unit)` 会对 D3D12 渲染结果产生影响
- 如果你在迁移旧式图形 API 思维,应把关注点转到 descriptor heap 和 descriptor set
## 相关文档
- [返回类总览](D3D12Sampler.md)
- [返回模块目录](../D3D12.md)
- [Unbind](Unbind.md)
- [D3D12DescriptorSet](../D3D12DescriptorSet/D3D12DescriptorSet.md)
- [D3D12Sampler](D3D12Sampler.md)

View File

@@ -1,28 +1,27 @@
# D3D12Sampler::D3D12Sampler()
构造对象。
# D3D12Sampler::D3D12Sampler
```cpp
D3D12Sampler();
```
该方法声明于 `XCEngine/RHI/D3D12/D3D12Sampler.h`,当前页面用于固定 `D3D12Sampler` 类目录下的方法级 canonical 路径。
## 作用
**参数:**
构造一个空的 D3D12 采样器包装对象
**返回:** `void` - 无返回值。
## 当前实现行为
**示例:**
- 使用 `memset` 把内部 `m_desc` 清零
- `m_id` 保持类内默认值 `0`
- 不创建任何原生 D3D12 对象
- 不关联任何 `ID3D12Device`
```cpp
#include <XCEngine/RHI/D3D12/D3D12Sampler.h>
## 使用说明
void Example() {
XCEngine::RHI::D3D12Sampler object;
}
```
在引擎常规路径里,这个构造函数通常由 [D3D12Device](../D3D12Device/D3D12Device.md) 的 `CreateSampler()` 间接调用,随后再进入 [Initialize](Initialize.md)。
如果你手动构造对象,需要明确一点:构造完成后它还只是一个空描述容器,不能代表已经写入 descriptor heap 的 sampler。
## 相关文档
- [返回类总览](D3D12Sampler.md)
- [返回模块目录](../D3D12.md)
- [Initialize](Initialize.md)
- [D3D12Sampler](D3D12Sampler.md)

View File

@@ -6,33 +6,84 @@
**头文件**: `XCEngine/RHI/D3D12/D3D12Sampler.h`
**描述**: 定义 `XCEngine/RHI/D3D12` 子目录中的 `D3D12Sampler` public API
**描述**: D3D12 后端的采样器包装,但当前实现本质上只是一个 `D3D12_SAMPLER_DESC` 缓存对象,而不是独立的 GPU sampler 资源句柄
## 概
## 概
`D3D12Sampler.h` `XCEngine/RHI/D3D12` 子目录 下的 public header当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明
`D3D12Sampler` 在当前引擎里承担的是“描述数据载体”角色
## 声明概览
它做的事情很少,但很关键:
| 声明 | 类型 | 说明 |
|------|------|------|
| `D3D12Sampler` | `class` | 继承自 `RHISampler` 的公开声明。 |
- 保存一份已经转换成 D3D12 原生格式的 `D3D12_SAMPLER_DESC`
- 作为跨后端接口 [RHISampler](../../RHISampler/RHISampler.md) 的 D3D12 实现
- 让 [D3D12DescriptorSet](../D3D12DescriptorSet/D3D12DescriptorSet.md) 在真正写入 descriptor heap 时,可以从对象里取到 sampler 描述
## 公共方法
它当前不做这些事情:
| 方法 | 描述 |
|------|------|
| [D3D12Sampler()](Constructor.md) | 构造对象。 |
| [~D3D12Sampler()](Destructor.md) | 销毁对象并释放相关资源。 |
| [Initialize](Initialize.md) | 初始化内部状态。 |
| [Shutdown](Shutdown.md) | 关闭并清理内部状态。 |
| [GetDesc](GetDesc.md) | 获取相关状态或对象。 |
| [GetNativeHandle](GetNativeHandle.md) | 获取相关状态或对象。 |
| [GetID](GetID.md) | 获取相关状态或对象。 |
| [Bind](Bind.md) | 公开方法,详见头文件声明。 |
| [Unbind](Unbind.md) | 公开方法,详见头文件声明。 |
- 不创建独立的 D3D12 sampler 对象
- 不持有 descriptor heap 槽位
- 不执行类似 OpenGL `Bind(unit)` 的状态绑定
## 设计定位
D3D12 的 sampler 语义和 OpenGL 很不一样。OpenGL 常把 sampler 看成一个可绑定对象,而 D3D12 更接近“把采样状态写成 descriptor再由 descriptor table 参与绑定”。
这也是当前实现只保存 `D3D12_SAMPLER_DESC` 的原因:
- [D3D12Device](../D3D12Device/D3D12Device.md) 负责把引擎通用 `SamplerDesc` 翻译成 D3D12 描述
- [D3D12DescriptorSet](../D3D12DescriptorSet/D3D12DescriptorSet.md) 在 `UpdateSampler()` 中调用 `ID3D12Device::CreateSampler(...)`,把描述真正写进目标 heap
这种设计的好处是实现非常直接,能很好贴合 D3D12 的 descriptor 模型。代价是文档必须讲清楚:`D3D12Sampler` 不是资源拥有者,它只是后续写 descriptor 所需的数据源。
## 生命周期
- 构造后把 `m_desc` 清零,`m_id` 保持默认值 `0`
- [Initialize](Initialize.md) 只复制一份 `D3D12_SAMPLER_DESC`
- 真正的 GPU 可见 sampler descriptor 通常在 `D3D12DescriptorSet::UpdateSampler()` 期间才写入 heap
- [Shutdown](Shutdown.md) 只把内部描述清零,不回收任何 heap 槽位
- 析构时自动调用 [Shutdown](Shutdown.md)
## 当前实现的真实行为
- `Initialize()` 完全忽略传入的 `ID3D12Device*`
- `Initialize()` 不做合法性校验,也不调用 `ID3D12Device::CreateSampler(...)`
- [GetNativeHandle](GetNativeHandle.md) 返回的是 `&m_desc`,不是原生 D3D12 句柄
- [GetID](GetID.md) 返回 `m_id`,但当前实现从未给它赋过新值,因此始终是 `0`
- [Bind](Bind.md) 和 [Unbind](Unbind.md) 是空实现
- [Shutdown](Shutdown.md) 会清零 `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 调用方没有实际效果
## 关键方法
- [Initialize](Initialize.md)
- [GetDesc](GetDesc.md)
- [GetNativeHandle](GetNativeHandle.md)
- [Bind](Bind.md)
- [Unbind](Unbind.md)
- [Shutdown](Shutdown.md)
## 相关文档
- [当前目录](../D3D12.md) - 返回 `D3D12` 平行目录
- [API 总索引](../../../../main.md) - 返回顶层索引
- [D3D12](../D3D12.md)
- [D3D12Device](../D3D12Device/D3D12Device.md)
- [D3D12DescriptorSet](../D3D12DescriptorSet/D3D12DescriptorSet.md)
- [RHISampler](../../RHISampler/RHISampler.md)

View File

@@ -1,29 +1,25 @@
# D3D12Sampler::~D3D12Sampler()
销毁对象并释放相关资源。
# D3D12Sampler::~D3D12Sampler
```cpp
~D3D12Sampler() override;
```
该方法声明于 `XCEngine/RHI/D3D12/D3D12Sampler.h`,当前页面用于固定 `D3D12Sampler` 类目录下的方法级 canonical 路径。
## 作用
**参数:**
销毁 `D3D12Sampler` 对象
**返回:** `void` - 无返回值。
## 当前实现行为
**示例:**
- 析构函数内部直接调用 [Shutdown](Shutdown.md)
- 因为当前类不拥有原生 D3D12 sampler 句柄,所以析构阶段没有 COM 资源释放逻辑
```cpp
#include <XCEngine/RHI/D3D12/D3D12Sampler.h>
## 设计说明
void Example() {
XCEngine::RHI::D3D12Sampler object;
// 对象离开作用域时会自动触发析构
}
```
这里的析构语义比很多 GPU 资源类都轻。`D3D12Sampler` 当前只是描述缓存对象,因此销毁它主要是为了把内部状态复位,而不是回收 GPU 资源。
已经写入 descriptor heap 的 sampler descriptor 也不会因为这个对象析构而自动失效D3D12 在 `CreateSampler(...)` 时已经把描述拷贝到了目标 heap
## 相关文档
- [返回类总览](D3D12Sampler.md)
- [返回模块目录](../D3D12.md)
- [Shutdown](Shutdown.md)
- [D3D12Sampler](D3D12Sampler.md)

View File

@@ -1,30 +1,31 @@
# D3D12Sampler::GetDesc
获取相关状态或对象。
```cpp
const D3D12_SAMPLER_DESC& GetDesc() const;
```
该方法声明于 `XCEngine/RHI/D3D12/D3D12Sampler.h`,当前页面用于固定 `D3D12Sampler` 类目录下的方法级 canonical 路径。
## 作用
**参数:**
返回当前对象内部缓存的 `D3D12_SAMPLER_DESC`
**返回:** `const D3D12_SAMPLER_DESC&` - 返回值语义详见头文件声明。
## 当前实现行为
**示例:**
- 直接返回 `m_desc` 的常量引用
- 不复制数据
- 不做初始化状态检查
```cpp
#include <XCEngine/RHI/D3D12/D3D12Sampler.h>
## 使用场景
void Example() {
XCEngine::RHI::D3D12Sampler object;
// 根据上下文补齐参数后调用 D3D12Sampler::GetDesc(...)。
(void)object;
}
```
这个接口的主要消费方是 D3D12 绑定路径,例如 descriptor set 在写 sampler descriptor 时,会把这里返回的描述传给 `ID3D12Device::CreateSampler(...)`
## 注意事项
- 返回的是对象内部引用,不是独立副本
- 在 [Shutdown](Shutdown.md) 之后,这份描述会被清零
- 在对象析构之后,这个引用立即失效
## 相关文档
- [返回类总览](D3D12Sampler.md)
- [返回模块目录](../D3D12.md)
- [Initialize](Initialize.md)
- [GetNativeHandle](GetNativeHandle.md)
- [D3D12DescriptorSet](../D3D12DescriptorSet/D3D12DescriptorSet.md)

View File

@@ -1,30 +1,31 @@
# D3D12Sampler::GetID
获取相关状态或对象。
```cpp
unsigned int GetID() override;
```
该方法声明于 `XCEngine/RHI/D3D12/D3D12Sampler.h`,当前页面用于固定 `D3D12Sampler` 类目录下的方法级 canonical 路径。
## 作用
**参数:**
返回采样器对象的 ID
**返回:** `unsigned int` - 返回值语义详见头文件声明。
## 当前实现行为
**示例:**
- 直接返回成员 `m_id`
- 当前实现中 `m_id` 只有类内默认初始化值 `0`
- 源码里没有为它分配唯一 ID 的逻辑
```cpp
#include <XCEngine/RHI/D3D12/D3D12Sampler.h>
## 结果解读
void Example() {
XCEngine::RHI::D3D12Sampler object;
// 根据上下文补齐参数后调用 D3D12Sampler::GetID(...)
(void)object;
}
```
在当前 D3D12 后端里,这个返回值更像接口兼容字段,而不是可靠的对象标识。
这也解释了为什么通用测试通常只验证“能返回一个无符号整数”,而不是依赖它的唯一性或资源身份语义
## 使用建议
- 不要把这个值用于资源表、缓存键或调试追踪
- 如果你需要区分 sampler应该在更高层维护自己的标识或直接比较描述内容
## 相关文档
- [返回类总览](D3D12Sampler.md)
- [返回模块目录](../D3D12.md)
- [D3D12Sampler](D3D12Sampler.md)
- [GetNativeHandle](GetNativeHandle.md)

View File

@@ -1,30 +1,34 @@
# D3D12Sampler::GetNativeHandle
获取相关状态或对象。
```cpp
void* GetNativeHandle() override;
```
该方法声明于 `XCEngine/RHI/D3D12/D3D12Sampler.h`,当前页面用于固定 `D3D12Sampler` 类目录下的方法级 canonical 路径。
## 作用
**参数:**
返回当前对象的“原生句柄”表示
**返回:** `void*` - 返回值语义详见头文件声明。
## 当前实现行为
**示例:**
- 实际返回值是 `&m_desc`
- 返回的是 `D3D12_SAMPLER_DESC*`,再以 `void*` 形式暴露
- 不是 `ID3D12DescriptorHeap*`
- 也不是某个独立的 D3D12 sampler 资源句柄
```cpp
#include <XCEngine/RHI/D3D12/D3D12Sampler.h>
## 设计说明
void Example() {
XCEngine::RHI::D3D12Sampler object;
// 根据上下文补齐参数后调用 D3D12Sampler::GetNativeHandle(...)
(void)object;
}
```
`D3D12Sampler` 来说“原生句柄”这个接口带有明显的跨后端兼容色彩。OpenGL、Vulkan、D3D12 对 sampler 的底层表示不同,所以统一接口里只能允许各后端给出最贴近自己模型的原生暴露方式。
当前 D3D12 版本选择暴露“描述指针”,因为这正是它的真实核心数据
## 使用建议
- 如果你需要的是 sampler 描述本身,优先使用 [GetDesc](GetDesc.md)
- 如果你从 `void*` 还原类型,应把它解释成 `D3D12_SAMPLER_DESC*`
- 不要把它当成可直接提交给 GPU 的对象句柄
## 相关文档
- [返回类总览](D3D12Sampler.md)
- [返回模块目录](../D3D12.md)
- [GetDesc](GetDesc.md)
- [Initialize](Initialize.md)
- [D3D12Sampler](D3D12Sampler.md)

View File

@@ -1,32 +1,44 @@
# D3D12Sampler::Initialize
初始化内部状态。
```cpp
bool Initialize(ID3D12Device* device, const D3D12_SAMPLER_DESC& desc);
```
该方法声明于 `XCEngine/RHI/D3D12/D3D12Sampler.h`,当前页面用于固定 `D3D12Sampler` 类目录下的方法级 canonical 路径。
## 作用
**参数:**
- `device` - 参数语义详见头文件声明。
- `desc` - 参数语义详见头文件声明。
用一份原生 `D3D12_SAMPLER_DESC` 初始化 `D3D12Sampler` 对象。
**返回:** `bool` - 返回值语义详见头文件声明。
## 当前实现行为
**示例:**
- 直接执行 `m_desc = desc`
- 完全忽略 `device` 参数
- 不调用 `ID3D12Device::CreateSampler(...)`
- 不做字段合法性校验
- 始终返回 `true`
```cpp
#include <XCEngine/RHI/D3D12/D3D12Sampler.h>
## 这意味着什么
void Example() {
XCEngine::RHI::D3D12Sampler object;
// 根据上下文补齐参数后调用 D3D12Sampler::Initialize(...)。
(void)object;
}
```
调用 [Initialize](Initialize.md) 并不代表 GPU 侧已经存在 sampler descriptor。它只代表当前对象保存了一份后续可供写入 heap 的描述。
在当前后端里,真正把采样器写入 descriptor heap 的地方是:
- [D3D12DescriptorSet](../D3D12DescriptorSet/D3D12DescriptorSet.md) 的 `UpdateSampler()`
那段代码会取出 [GetDesc](GetDesc.md) 返回的描述,再调用 `ID3D12Device::CreateSampler(...)` 写到目标 CPU descriptor handle。
## 使用建议
- 常规调用方应优先通过 [D3D12Device](../D3D12Device/D3D12Device.md) 的 `CreateSampler()` 创建,而不是自己手填 `D3D12_SAMPLER_DESC`
- 如果你直接调用这个重载,需要自己保证 `desc` 的过滤、寻址、LOD 和比较函数都已经符合目标渲染路径要求
- 不要把这个函数的成功返回值误读为“GPU sampler 已创建成功”
## 当前限制
- 即使传入 `nullptr``device`,当前实现也会返回 `true`
- 错误的描述直到真正 `CreateSampler(...)` 写入 heap 时才可能暴露出来
## 相关文档
- [返回类总览](D3D12Sampler.md)
- [返回模块目录](../D3D12.md)
- [GetDesc](GetDesc.md)
- [D3D12Device](../D3D12Device/D3D12Device.md)
- [D3D12DescriptorSet](../D3D12DescriptorSet/D3D12DescriptorSet.md)

View File

@@ -1,30 +1,35 @@
# D3D12Sampler::Shutdown
关闭并清理内部状态。
```cpp
void Shutdown() override;
```
该方法声明于 `XCEngine/RHI/D3D12/D3D12Sampler.h`,当前页面用于固定 `D3D12Sampler` 类目录下的方法级 canonical 路径。
## 作用
**参数:**
清理 `D3D12Sampler` 的内部状态
**返回:** `void` - 无返回值。
## 当前实现行为
**示例:**
- 使用 `memset``m_desc` 清零
- 不释放任何 COM 资源,因为当前类没有持有这类资源
- 不重置 `m_id`
```cpp
#include <XCEngine/RHI/D3D12/D3D12Sampler.h>
## 设计说明
void Example() {
XCEngine::RHI::D3D12Sampler object;
// 根据上下文补齐参数后调用 D3D12Sampler::Shutdown(...)。
(void)object;
}
```
这个接口更像“把描述缓存复位”,而不是传统意义上的 GPU 资源销毁。
需要特别注意:
- 如果某份 sampler 描述已经通过 `ID3D12Device::CreateSampler(...)` 写进 descriptor heap那么这些 heap 中的 descriptor 不会因为这里的 `Shutdown()` 被回收或撤销
- 只有后续再次使用这个对象写 descriptor 时,清零后的 `m_desc` 才会影响结果
## 使用建议
- 对象不再使用时可以调用它来复位状态
- 如果你已经把它交给 descriptor set 更新过仍然应该把“descriptor heap 的生命周期”和“这个包装对象的生命周期”分开理解
## 相关文档
- [返回类总览](D3D12Sampler.md)
- [返回模块目录](../D3D12.md)
- [Initialize](Initialize.md)
- [GetDesc](GetDesc.md)
- [Destructor](Destructor.md)

View File

@@ -1,31 +1,36 @@
# D3D12Sampler::Unbind
公开方法,详见头文件声明。
```cpp
void Unbind(unsigned int unit) override;
```
该方法声明于 `XCEngine/RHI/D3D12/D3D12Sampler.h`,当前页面用于固定 `D3D12Sampler` 类目录下的方法级 canonical 路径。
## 作用
**参数:**
- `unit` - 参数语义详见头文件声明。
保留跨后端统一接口中的“解绑采样器”入口。
**返回:** `void` - 无返回值。
## 当前实现行为
**示例:**
- 函数体为空
- 不读取 `unit`
- 不撤销任何 descriptor heap 中已有的 sampler descriptor
- 不修改 GPU 状态
```cpp
#include <XCEngine/RHI/D3D12/D3D12Sampler.h>
## 设计说明
void Example() {
XCEngine::RHI::D3D12Sampler object;
// 根据上下文补齐参数后调用 D3D12Sampler::Unbind(...)。
(void)object;
}
```
在 D3D12 模型里,所谓“解绑”通常不会以单独的 sampler 对象操作出现。更常见的做法是:
- 改写 descriptor table
- 切换 descriptor heap
- 切换 pipeline layout / root signature
因此这里没有和 OpenGL `glBindSampler(unit, 0)` 对应的真实后端动作。
## 使用建议
- 不要把它当成资源生命周期控制接口
- 如果想改变采样器行为,应更新 descriptor set 或切换绑定表内容
## 相关文档
- [返回类总览](D3D12Sampler.md)
- [返回模块目录](../D3D12.md)
- [Bind](Bind.md)
- [D3D12DescriptorSet](../D3D12DescriptorSet/D3D12DescriptorSet.md)

View File

@@ -1,65 +1,127 @@
# D3D12Screenshot::Capture
公开方法,详见头文件声明。
```cpp
bool Capture(RHIDevice* device, RHISwapChain* swapChain, const char* filename) override;
static bool Capture(ID3D12Device* device,
ID3D12CommandQueue* commandQueue,
ID3D12Resource* renderTarget,
const char* filename,
uint32_t width,
uint32_t height);
static bool Capture(D3D12Device& device,
D3D12CommandQueue& commandQueue,
D3D12Texture& texture,
const char* filename);
```
该方法在 `XCEngine/RHI/D3D12/D3D12Screenshot.h` 中提供了 3 个重载,当前页面统一汇总这些公开声明。
## 作用
## 重载 1: 声明
从 D3D12 渲染目标抓取一帧图像,并把结果保存为 PPM 文件。
这三个公开重载只是适配不同调用层级,最终都会进入同一套 copy-to-readback 再写盘的实现。
## 核心流程
底层实现 `CopyToReadbackAndSave(...)` 的关键步骤如下:
- 校验原生 `device``commandQueue``renderTarget`
- 查询资源描述和 copy footprint
- 创建一次性 command allocator、command list、readback buffer
- 插入 `RENDER_TARGET -> COPY_SOURCE -> RENDER_TARGET` 状态切换
- 执行 `CopyTextureRegion(...)`
- 提交队列并通过 fence 阻塞等待
- `Map()` 读回数据,按 `P6` PPM 写出 RGB 像素
## 重载 1: RHI 入口
```cpp
bool Capture(RHIDevice* device, RHISwapChain* swapChain, const char* filename) override;
```
**参数:**
- `device` - 参数语义详见头文件声明。
- `swapChain` - 参数语义详见头文件声明。
- `filename` - 参数语义详见头文件声明。
### 当前实现行为
**返回:** `bool` - 返回值语义详见头文件声明。
- 直接把 `device` 转成 `D3D12Device*`
- 直接把 `swapChain` 转成 `D3D12SwapChain*`
- 通过 `swapChain->GetCurrentBackBuffer()` 取得当前 back buffer
- 通过 `d3d12SwapChain->GetNativeCommandQueue()` 取得原生命令队列
- 再转发到内部 copy 逻辑
## 重载 2: 声明
### 注意事项
- 没有运行时类型检查,所以它只能在 D3D12 后端上下文中正确工作
- 也没有对 `device` / `swapChain` 做空指针保护
- 如果 [D3D12SwapChain](../D3D12SwapChain/D3D12SwapChain.md) 当前没有缓存原生命令队列,底层会在空队列检查时报错并返回 `false`
## 重载 2: 原生 D3D12 入口
```cpp
static bool Capture(ID3D12Device* device, ID3D12CommandQueue* commandQueue, ID3D12Resource* renderTarget, const char* filename, uint32_t width, uint32_t height);
static bool Capture(ID3D12Device* device,
ID3D12CommandQueue* commandQueue,
ID3D12Resource* renderTarget,
const char* filename,
uint32_t width,
uint32_t height);
```
**参数:**
- `device` - 参数语义详见头文件声明。
- `commandQueue` - 参数语义详见头文件声明。
- `renderTarget` - 参数语义详见头文件声明。
- `filename` - 参数语义详见头文件声明。
- `width` - 参数语义详见头文件声明。
- `height` - 参数语义详见头文件声明。
### 当前实现行为
**返回:** `bool` - 返回值语义详见头文件声明。
- 这是最直接的后端入口
- 只做一层转发,真正逻辑全部在 `CopyToReadbackAndSave(...)`
- 会记录 `device``commandQueue``renderTarget` 为空的错误日志
## 重载 3: 声明
### 关键前提
- `renderTarget` 当前状态被假定为 `D3D12_RESOURCE_STATE_RENDER_TARGET`
- 目标资源需要能被 `CopyTextureRegion(...)` 读取
- 当前实现默认按“每像素 4 字节,取前 3 字节写 RGB”处理输出
### 宽高参数说明
这里的 `width` / `height` 不是通过 `renderTarget->GetDesc()` 自动推导出来的输出尺寸,而是调用者显式传入的写盘尺寸。
当前实现里:
- copy 区域使用的是资源描述中的 `Width` / `Height`
- 文件写出循环使用的是函数参数里的 `width` / `height`
因此如果两者不一致,输出内容可能越界、截断或产生错误图像。常规做法是传入与资源真实尺寸一致的值。
## 重载 3: D3D12 包装对象入口
```cpp
static bool Capture(D3D12Device& device, D3D12CommandQueue& commandQueue, D3D12Texture& texture, const char* filename);
static bool Capture(D3D12Device& device,
D3D12CommandQueue& commandQueue,
D3D12Texture& texture,
const char* filename);
```
**参数:**
- `device` - 参数语义详见头文件声明。
- `commandQueue` - 参数语义详见头文件声明。
- `texture` - 参数语义详见头文件声明。
- `filename` - 参数语义详见头文件声明。
### 当前实现行为
**返回:** `bool` - 返回值语义详见头文件声明。
-`D3D12Device``GetDevice()`
-`D3D12CommandQueue``GetCommandQueue()`
-`D3D12Texture``GetResource()``GetWidth()``GetHeight()`
- 再调用原生 D3D12 重载
**示例:**
### 适用场景
```cpp
#include <XCEngine/RHI/D3D12/D3D12Screenshot.h>
这通常是测试和集成样例里最方便的入口,因为调用方已经处在 D3D12 封装层,不需要再自己拆出所有原生句柄。
void Example() {
XCEngine::RHI::D3D12Screenshot object;
// 根据上下文补齐参数后调用 D3D12Screenshot::Capture(...)。
(void)object;
}
```
## 输出格式
- 文件格式固定为 `P6` PPM
- 只写 RGB不写 alpha
- 不做压缩
- 不做色彩空间转换或 HDR 编码
## 实践建议
- 优先用于测试截图、调试抓帧和最小验证程序
- 如果未来要支持商业级截图能力,通常应把这条同步路径升级成“异步 readback + 后台编码”体系
- 如果 render target 不是标准 4 字节颜色格式,应先评估当前实现是否适配,而不是直接假设结果正确
## 相关文档
- [返回类总览](D3D12Screenshot.md)
- [返回模块目录](../D3D12.md)
- [D3D12Screenshot](D3D12Screenshot.md)
- [Shutdown](Shutdown.md)
- [D3D12SwapChain](../D3D12SwapChain/D3D12SwapChain.md)
- [D3D12Texture](../D3D12Texture/D3D12Texture.md)

View File

@@ -6,26 +6,89 @@
**头文件**: `XCEngine/RHI/D3D12/D3D12Screenshot.h`
**描述**: 定义 `XCEngine/RHI/D3D12` 子目录中的 `D3D12Screenshot` public API
**描述**: D3D12 后端的截图工具类,负责把渲染目标复制到 readback buffer再以 PPM 格式写入磁盘
## 概
## 概
`D3D12Screenshot.h` `XCEngine/RHI/D3D12` 子目录 下的 public header当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明
`D3D12Screenshot`一个非常“实用主义”的后端工具
## 声明概览
它的目标不是构建完整的截图子系统,而是用最少依赖完成下面这条链路:
| 声明 | 类型 | 说明 |
|------|------|------|
| `D3D12Screenshot` | `class` | 继承自 `RHIScreenshot` 的公开声明。 |
- 从 swap chain back buffer 或指定 `ID3D12Resource`
- 提交一次 copy 到 readback buffer
- 等待 GPU 完成
- 把像素数据写成最简单的 `P6` PPM 文件
## 公共方法
这类实现特别适合:
| 方法 | 描述 |
|------|------|
| [Capture](Capture.md) | 公开方法,详见头文件声明。 |
| [Shutdown](Shutdown.md) | 关闭并清理内部状态。 |
- RHI 集成测试
- 自动化渲染结果抓图
- 最小示例程序或调试工具
## 设计定位
当前实现明显偏“测试/验证工具”而非“产品级截图系统”。
它的特点是:
- 每次截图都临时创建命令分配器、命令列表、读回缓冲和 fence
- 直接在调用线程阻塞等待 GPU 完成
- 直接用 `fopen` / `fprintf` / `fwrite` 输出未压缩 PPM
这条路径简单、可控、依赖少,很适合引擎底层验证阶段。代价是性能和通用性都比较一般。
## 截图流程
所有公开重载最终都会汇合到内部的 `CopyToReadbackAndSave(...)` 逻辑,流程大致如下:
1. 检查 `device``commandQueue``renderTarget` 是否为空
2. 读取 `renderTarget->GetDesc()`,并通过 `GetCopyableFootprints(...)` 计算读回布局
3. 创建一次性 command allocator、readback buffer 和 graphics command list
4. 把资源从 `D3D12_RESOURCE_STATE_RENDER_TARGET` 转到 `D3D12_RESOURCE_STATE_COPY_SOURCE`
5.`CopyTextureRegion(...)` 把内容拷到 readback buffer
6. 再把资源状态切回 `D3D12_RESOURCE_STATE_RENDER_TARGET`
7. 提交命令并用 fence + event 同步等待完成
8. `Map()` 读回缓冲,按 `P6` PPM 格式逐像素写文件,只输出 RGB不输出 alpha
## 当前实现的真实行为
- RHI 接口重载内部直接 `static_cast``D3D12Device*``D3D12SwapChain*`
- 代码假定源资源当前处于 `D3D12_RESOURCE_STATE_RENDER_TARGET`
- 代码没有做格式转换或通道重排,写文件时直接按每像素 4 字节读取前 3 个字节
- `width` / `height` 参数用于文件写出循环,而 copy 区域尺寸来自 `renderTarget->GetDesc()`
- 每次调用都会阻塞 CPU直到 GPU copy 完成
- [Shutdown](Shutdown.md) 是空实现,因为类本身不保存持久状态
## 为什么这样设计
商业级引擎里,真正的截图系统常常会包含异步读回、图像编码、多格式支持、后台写盘队列,甚至和工具链打通。但在 RHI 基建阶段,更重要的是先有一条可靠、容易定位问题的最短路径。
`D3D12Screenshot` 采用的正是这种思路:
- 不引入第三方图像编码依赖
- 不引入额外线程或任务系统
- 保持截图结果和当前后端行为一一对应,方便调试
这和很多成熟引擎在底层验证阶段的做法是一致的:先要可验证,再谈高性能和高可扩展。
## 当前限制
- 只适合颜色 render target不处理深度或多子资源选择
- 没有 MSAA resolve 路径
- 没有格式转换,最可靠的目标仍然是 `R8G8B8A8` / `B8G8R8A8` 这一类 4 字节像素格式
- 不支持异步截图或资源复用
- `Capture(RHIDevice*, RHISwapChain*, ...)` 没有做运行时类型检查
- 如果 swap chain 没有可用的原生命令队列,截图会失败
## 关键方法
- [Capture](Capture.md)
- [Shutdown](Shutdown.md)
## 相关文档
- [当前目录](../D3D12.md) - 返回 `D3D12` 平行目录
- [API 总索引](../../../../main.md) - 返回顶层索引
- [D3D12](../D3D12.md)
- [D3D12SwapChain](../D3D12SwapChain/D3D12SwapChain.md)
- [D3D12Texture](../D3D12Texture/D3D12Texture.md)
- [D3D12CommandQueue](../D3D12CommandQueue/D3D12CommandQueue.md)
- [RHIScreenshot](../../RHIScreenshot/RHIScreenshot.md)

View File

@@ -1,30 +1,37 @@
# D3D12Screenshot::Shutdown
关闭并清理内部状态。
```cpp
void Shutdown() override;
void Shutdown() override {}
```
该方法声明于 `XCEngine/RHI/D3D12/D3D12Screenshot.h`,当前页面用于固定 `D3D12Screenshot` 类目录下的方法级 canonical 路径。
## 作用
**参数:**
满足 [RHIScreenshot](../../RHIScreenshot/RHIScreenshot.md) 接口要求的清理入口
**返回:** `void` - 无返回值。
## 当前实现行为
**示例:**
- 这是一个空实现
- 不释放任何资源
- 不取消任何进行中的截图操作
```cpp
#include <XCEngine/RHI/D3D12/D3D12Screenshot.h>
## 为什么是空实现
void Example() {
XCEngine::RHI::D3D12Screenshot object;
// 根据上下文补齐参数后调用 D3D12Screenshot::Shutdown(...)。
(void)object;
}
```
`D3D12Screenshot` 当前本身没有成员资源。每一次 [Capture](Capture.md) 调用都会在函数内部临时创建并释放:
- command allocator
- command list
- readback buffer
- fence
- event
因此对象级别没有需要持久清理的状态。
## 使用说明
- 你可以调用它来保持接口使用上的一致性
- 但不要期待它承担“回收上一帧截图任务”之类的职责
## 相关文档
- [返回类总览](D3D12Screenshot.md)
- [返回模块目录](../D3D12.md)
- [Capture](Capture.md)
- [D3D12Screenshot](D3D12Screenshot.md)

View File

@@ -1,6 +1,6 @@
# API 文档重构状态
**生成时间**: `2026-03-28 00:08:45`
**生成时间**: `2026-03-28 00:17:57`
**来源**: `docs/api/_tools/audit_api_docs.py`