7.5 KiB
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 有明确的后端适配边界
代价是:
- 使用显式、样板多
- 生命周期管理要求更严格
- 当前还会看到一部分后端痕迹和未完全收敛的接口
当前最真实的使用路径
按源码和测试总结,当前推荐按下面顺序理解:
- 用 RHIFactory 选择后端并创建 RHIDevice。
- 调用
device->Initialize()初始化设备。 - 读取
GetCapabilities()/GetDeviceInfo()决定可用路径。 - 通过
device创建 RHICommandQueue。 - 通过
device创建 RHICommandList。 - 通过
device创建 buffer、texture、resource view、render pass、framebuffer、descriptor pool / set、pipeline state 等对象。 - 在 command list 上
Reset()后录制命令,再Close()。 - 通过 queue 提交命令并用 fence 同步。
- 对所有对象执行
Shutdown(),最后delete。
一个最小化示意流程
下面这段更接近“当前抽象层的使用骨架”,不是完整 renderer:
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<uint32_t>(CommandQueueType::Direct);
RHICommandQueue* queue = device->CreateCommandQueue(queueDesc);
CommandListDesc cmdDesc = {};
cmdDesc.commandListType = static_cast<uint32_t>(CommandQueueType::Direct);
RHICommandList* cmd = device->CreateCommandList(cmdDesc);
BufferDesc vbDesc = {};
vbDesc.size = 1024;
vbDesc.stride = sizeof(float) * 8;
vbDesc.bufferType = static_cast<uint32_t>(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. 所有权是裸指针
当前工厂和设备的大部分创建接口都返回裸指针。测试中普遍采用:
- 创建
- 使用
Shutdown()delete
不要把这层接口自动代入 shared_ptr / unique_ptr 语义。
2. 很多描述符字段不是强类型枚举字段
例如 BufferDesc::bufferType、TextureDesc::format、CommandQueueDesc::queueType 都是整数类型。正确写法通常需要:
desc.queueType = static_cast<uint32_t>(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 已经完全采用单一抽象范式
当前它混合了多种录制路径和状态绑定思路,阅读具体页面时要按“实现事实”理解。