You need to enable JavaScript to run this app. D3D12动态常量缓冲区创建、绑定及运行时CPU到GPU数据拷贝方法
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.09 21:42:02