D3D12动态常量缓冲区创建、绑定及运行时CPU到GPU数据拷贝方法
D3D12 运行时常量缓冲区实现方案
前置说明
你当前定义的常量缓冲区结构体如下:
struct ObjectConstants { DirectX::XMFLOAT4X4 World{ D3DUtil::Identity4x4() }; DirectX::XMFLOAT3 Color{ 0.f, 0.f, 0.f }; };
D3D12 要求常量缓冲区的单块数据大小必须向上对齐到256字节的整数倍,上述结构体总大小为76字节,实际分配时需要对齐到256字节。
1. 创建常量缓冲区资源
常量缓冲区需要频繁从CPU写入数据,因此选择D3D12_HEAP_TYPE_UPLOAD类型的堆,并且可以持久映射内存避免每次更新都执行Map/Unmap操作。
示例代码:
// 对齐工具函数 inline UINT AlignUp(UINT size, UINT alignment) { return (size + alignment - 1) & ~(alignment - 1); } // 计算对齐后的常量缓冲区大小 UINT constantBufferSize = AlignUp(sizeof(ObjectConstants), D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT); ComPtr<ID3D12Resource> constantBuffer; void* mappedConstantBufferPtr = nullptr; // 创建上传堆常量缓冲区 CD3DX12_HEAP_PROPERTIES uploadHeapProps(D3D12_HEAP_TYPE_UPLOAD); CD3DX12_RESOURCE_DESC constantBufferDesc = CD3DX12_RESOURCE_DESC::Buffer(constantBufferSize); device->CreateCommittedResource( &uploadHeapProps, D3D12_HEAP_FLAG_NONE, &constantBufferDesc, D3D12_RESOURCE_STATE_GENERIC_READ, // 上传堆默认状态就是通用读,无需手动转换状态 nullptr, IID_PPV_ARGS(&constantBuffer) ); // 持久映射内存,后续直接写入即可,资源销毁前再执行Unmap constantBuffer->Map(0, nullptr, &mappedConstantBufferPtr);
2. 运行时绑定到渲染管线
结合着色器反射拿到的常量缓冲区绑定槽位,有两种常用绑定方式:
方式1:直接作为根参数绑定(适合少量常量缓冲区)
如果根签名中对应槽位的参数类型是D3D12_ROOT_PARAMETER_TYPE_CBV,直接在命令列表中设置根常量缓冲区视图即可:
// 此处的cbvRootParameterIndex为着色器反射获取到的常量缓冲区绑定槽位,示例值为0 UINT cbvRootParameterIndex = 0; commandList->SetGraphicsRootConstantBufferView( cbvRootParameterIndex, constantBuffer->GetGPUVirtualAddress() );
方式2:通过描述符表绑定(适合大量资源场景)
如果用描述符表统一管理资源,需要先创建CBV描述符存入CBV_SRV_UAV堆,再绑定描述符堆和根描述符表:
// 创建CBV描述符 D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc{}; cbvDesc.BufferLocation = constantBuffer->GetGPUVirtualAddress(); cbvDesc.SizeInBytes = constantBufferSize; device->CreateConstantBufferView(&cbvDesc, cbvSrvUavHeap->GetCPUDescriptorHandleForHeapStart()); // 绑定描述符堆 ID3D12DescriptorHeap* heaps[] = { cbvSrvUavHeap.Get() }; commandList->SetDescriptorHeaps(_countof(heaps), heaps); // 设置根描述符表 commandList->SetGraphicsRootDescriptorTable( rootParameterIndex, cbvSrvUavHeap->GetGPUDescriptorHandleForHeapStart() );
3. CPU到GPU数据更新
因为已经完成了内存持久映射,直接拷贝数据到映射地址即可:
ObjectConstants cpuConstants; // 赋值CPU端的常量数据 cpuConstants.World = ...; cpuConstants.Color = ...; // 拷贝到GPU上传堆内存 memcpy(mappedConstantBufferPtr, &cpuConstants, sizeof(ObjectConstants));
注意事项
- 如果你的渲染逻辑采用多帧在飞(Frame Flight)的架构,需要为每帧创建独立的常量缓冲区,或者在同一个上传堆中按帧偏移分配空间,避免CPU覆盖GPU正在读取的显存数据,导致渲染异常。
- 常量缓冲区的大小必须严格对齐256字节,否则会触发D3D12调试层报错,渲染结果异常。
- 上传堆的映射内存不需要频繁Map/Unmap,在资源销毁前执行一次Unmap即可。
动态场景适配(适配你的着色器工具场景)
因为你是通过节点动态生成纹理,常量变量不固定,可以用着色器反射拿到每个变量的字节偏移,不需要写死ObjectConstants结构体,直接按偏移往映射的内存中写入对应类型的数据即可,灵活度更高。
内容的提问来源于stack exchange,提问作者mfloriani
相关产品推荐
相关产品推荐

