# Devices, Queues, Command Lists, And Resource Creation ## 这篇指南解决什么问题 如果只看 `RHI` 目录下的类型名,很容易知道“有设备、有队列、有命令列表”,但不容易知道: - 当前真实初始化顺序是什么 - 哪些对象负责创建,哪些对象负责提交 - 生命周期应该怎么收尾 - 这层抽象和 Unity、Unreal、原生图形 API 分别是什么关系 这篇指南专门把这些问题讲清楚。 ## 先建立正确心智模型 `XCEngine::RHI` 不是最终给游戏逻辑直接使用的渲染 API,它是更靠近后端实现的一层基础渲染抽象。 更准确地说: - 它比 Unity 常见 gameplay 层接触到的渲染接口更底层。 - 它比原生 D3D12 / OpenGL / Vulkan API 更统一。 - 它更像商业引擎内部的 render backend layer。 如果拿成熟引擎做类比: - 和 Unity 的关系:更接近 SRP / 图形后端内部会接触的那一层,而不是 MonoBehaviour 侧的普通渲染入口。 - 和 Unreal 的关系:更接近低层 RHI 的思路。 这么设计的好处是: - 后端 bring-up 更直接 - 图形测试可以绕开更高层 renderer,直接验证抽象层 - 上层 renderer 有明确的后端适配边界 代价是: - 使用显式、样板多 - 生命周期管理要求更严格 - 当前还会看到一部分后端痕迹和未完全收敛的接口 ## 当前最真实的使用路径 按源码和测试总结,当前推荐按下面顺序理解: 1. 用 [RHIFactory](../../XCEngine/RHI/RHIFactory/RHIFactory.md) 选择后端并创建 [RHIDevice](../../XCEngine/RHI/RHIDevice/RHIDevice.md)。 2. 调用 `device->Initialize()` 初始化设备。 3. 读取 `GetCapabilities()` / `GetDeviceInfo()` 决定可用路径。 4. 通过 `device` 创建 [RHICommandQueue](../../XCEngine/RHI/RHICommandQueue/RHICommandQueue.md)。 5. 通过 `device` 创建 [RHICommandList](../../XCEngine/RHI/RHICommandList/RHICommandList.md)。 6. 通过 `device` 创建 buffer、texture、resource view、render pass、framebuffer、descriptor pool / set、pipeline state 等对象。 7. 在 command list 上 `Reset()` 后录制命令,再 `Close()`。 8. 通过 queue 提交命令并用 fence 同步。 9. 对所有对象执行 `Shutdown()`,最后 `delete`。 ## 一个最小化示意流程 下面这段更接近“当前抽象层的使用骨架”,不是完整 renderer: ```cpp using namespace XCEngine::RHI; RHIDevice* device = RHIFactory::CreateRHIDevice(RHIType::D3D12); if (device == nullptr) { return; } RHIDeviceDesc deviceDesc = {}; deviceDesc.enableDebugLayer = true; if (!device->Initialize(deviceDesc)) { delete device; return; } CommandQueueDesc queueDesc = {}; queueDesc.queueType = static_cast(CommandQueueType::Direct); RHICommandQueue* queue = device->CreateCommandQueue(queueDesc); CommandListDesc cmdDesc = {}; cmdDesc.commandListType = static_cast(CommandQueueType::Direct); RHICommandList* cmd = device->CreateCommandList(cmdDesc); BufferDesc vbDesc = {}; vbDesc.size = 1024; vbDesc.stride = sizeof(float) * 8; vbDesc.bufferType = static_cast(BufferType::Vertex); RHIBuffer* vertexBuffer = device->CreateBuffer(vbDesc); cmd->Reset(); cmd->SetPrimitiveTopology(PrimitiveTopology::TriangleList); cmd->Close(); vertexBuffer->Shutdown(); delete vertexBuffer; cmd->Shutdown(); delete cmd; queue->Shutdown(); delete queue; device->Shutdown(); delete device; ``` 这里最值得注意的是最后那段清理代码。当前 `RHI` 层不是自动 RAII 托管风格,而是显式关闭、显式释放。 ## 为什么 `RHIDevice` 会这么“大” 初看 `RHIDevice` 很容易觉得它过于庞大,因为它几乎包揽了所有对象创建: - 资源 - 队列和命令列表 - shader / pipeline - descriptor pool / set - render pass / framebuffer - resource view 但这其实很符合低层图形后端的发展路径。因为这些对象本来就都依赖同一套 native device / context / backend state,把创建权集中到设备上有几个直接好处: - 上层只依赖一个统一入口 - 设备能力与对象创建天然放在一起 - 后端实现更容易把 native handle、allocator、capabilities 串起来 以后如果引擎演进到更成熟阶段,可以再考虑拆分 allocator、descriptor manager、pipeline cache 等子系统;但在当前阶段,这种集中式设备接口是务实的。 ## `RHICommandQueue` 和 `RHICommandList` 应该怎么分工理解 ### `RHICommandList` 负责“录制要做什么”。 它当前可以记录: - barrier - render pass - resource binding - draw / draw indexed - dispatch - clear - copy ### `RHICommandQueue` 负责“把录制好的东西交给 GPU 执行,并管理同步”。 它当前还暴露了: - fence signal / wait - idle 等待 - frame index - timestamp frequency 这和显式图形 API 的常规心智模型一致。 ## 需要提前知道的几个实现事实 ### 1. 所有权是裸指针 当前工厂和设备的大部分创建接口都返回裸指针。测试中普遍采用: 1. 创建 2. 使用 3. `Shutdown()` 4. `delete` 不要把这层接口自动代入 `shared_ptr` / `unique_ptr` 语义。 ### 2. 很多描述符字段不是强类型枚举字段 例如 `BufferDesc::bufferType`、`TextureDesc::format`、`CommandQueueDesc::queueType` 都是整数类型。正确写法通常需要: ```cpp desc.queueType = static_cast(CommandQueueType::Direct); ``` ### 3. `RHIFactory` 的后端选择受编译开关影响 当前真实情况是: - `D3D12` 总能创建 - `OpenGL` 受 `XCENGINE_SUPPORT_OPENGL` 控制 - `Vulkan` 受 `XCENGINE_SUPPORT_VULKAN` 控制 - `Metal` 当前返回 `nullptr` ### 4. 抽象层并没有完全抹平后端差异 例如: - `RHITypes.h` 里仍能看到 `RootSignatureDesc`、`DescriptorHeapDesc` 这类 D3D12 命名 - `RHICommandList` 同时保留 render pass 路径和 `SetRenderTargets()` 路径 - `RHICommandQueue::ExecuteCommandLists()` 仍是 `void**` 正确的工程心态不是要求它现在就绝对纯净,而是接受它正处在“可用、可扩展、逐步收敛”的阶段。 ## 和 Unity 风格设计该怎么对应 如果你更熟悉 Unity,需要避免一个常见误区:不要把这层直接理解成“Unity 的 public rendering API”。 更准确的对应关系是: - Unity gameplay / Component 层 对应 XCEngine 更高层的 `Rendering`、组件系统和未来的 renderer 封装 - Unity 底层图形设备 / SRP 内部命令缓冲 才更接近当前 `RHI` 这种设计的工程收益是: - renderer 能做更细粒度控制 - 后端替换成本更低 - 图形测试不需要依赖完整场景系统 ## 常见误用 ### 忘记 `Shutdown()` 当前很多对象不能只靠析构做资源回收,至少从测试约定看不应该这么用。 ### 把所有 `RHIType` 都当成可用后端 `Metal` 目前不是可创建后端,`OpenGL` / `Vulkan` 也不是所有构建都可用。 ### 把 `RHITypes` 当成纯中立 ABI 它是 C++ 内部描述符集合,不是稳定二进制协议。 ### 以为 `RHICommandList` 已经完全采用单一抽象范式 当前它混合了多种录制路径和状态绑定思路,阅读具体页面时要按“实现事实”理解。 ## 从这里继续读什么 - [RHI](../../XCEngine/RHI/RHI.md) - [RHIFactory](../../XCEngine/RHI/RHIFactory/RHIFactory.md) - [RHIDevice](../../XCEngine/RHI/RHIDevice/RHIDevice.md) - [RHICommandQueue](../../XCEngine/RHI/RHICommandQueue/RHICommandQueue.md) - [RHICommandList](../../XCEngine/RHI/RHICommandList/RHICommandList.md) - [RHITypes](../../XCEngine/RHI/RHITypes/RHITypes.md) - [RHIEnums](../../XCEngine/RHI/RHIEnums/RHIEnums.md)