五、D3D12学习笔记——常量缓冲

龙书最简单的shader代码定义了一个数据:

cbuffer cbPerObject : register(b0)
{
    float4x4 gWorld; //用于shader使用的计算资源
};

cbuffer:常量缓冲区

cbPerObject:对象名

register:寄存器

(b0):b类寄存器上的0#槽。

我们关心两个问题:1.常量缓冲器怎么回事?2.寄存器的槽怎么设置?

一、常量缓冲区

既然是缓冲区Buffer,那也是着色器的一种资源(ID3D12Resource),是一种把数据从CPU运输到GPU的方式。

对于模型顶点这类数据,他可以认为在模型空间中是稳定的,不变的,在流水线上只用对其进行坐标变换就可以得到不同的观察效果,所以直接放入默认堆,后边CPU不再更新这部分数据,也不再让GPU更改他。但是有一部分数据,比如我们改了摄像机的视角,那总要告诉渲染流水线对我们的模型顶点数据进行变换,所以常量缓冲区就诞生了——他是一个每一帧都要更新的缓冲区。为了实现每一帧都要更新——使用上传堆

注意,常量缓冲区中的数据必须是256B的整数倍(256B),是硬件存储最小分配空间。我们可以自己设置,也可以程序帮我们实现。

 return (byteSize + 255) & ~255;//结果就是256B整数倍。

1.整体流程

先是进行数据的准备:CPU端定义待传递数据的基本结构:

struct ObjectConstants
{
    DirectX::XMFLOAT4X4 World = MathHelper::Identity4x4();
};

建立上传堆并存储数据

因为数据要实现每帧更新,就要用到上传堆,在传输顶点数据的时候我们看到过,现在再次贴出来:

ThrowIfFailed(device->CreateCommittedResource(
            &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
            D3D12_HEAP_FLAG_NONE,
            &CD3DX12_RESOURCE_DESC::Buffer(mElementByteSize*elementCount),//空间大小
            D3D12_RESOURCE_STATE_GENERIC_READ,//资源读写权限
            nullptr,//资源的清除格式
            IID_PPV_ARGS(&mUploadBuffer)));

这个函数创建了一个上传堆,产生了存储数据的一组内存,但是还没有数据。为了得到数据,常见的操作就是Map:

//资源的索引,资源的范围(nullptr表示全部范围),资源
ThrowIfFailed(mUploadBuffer->Map(0, nullptr, reinterpret_cast<void**>(&mMappedData)));

将mMappedData里边的数据映射到mUploadBuffer,那么mMappedData的数据如果更改了,在mUploadBuffer读到的数据就会更改。

再看怎么更改mMappedData的数据:

memcpy(&mMappedData[elementIndex*mElementByteSize], &data, sizeof(T));

内存拷贝的方式放入mMappedData,使用的时候对应了我们定义的常量数据结构ObjectConstants。

最后:

//资源的索引,资源的范围(nullptr表示全部范围)
mUploadBuffer->Unmap(0, nullptr);

龙书的源码将这个部分封装成了一个模板类,在构造和析构里边分别进行Map和Unmap,并提供一个CopyData进行memcpy。

我们现在创建了常量数据ObjectConstants的上传堆,并且把数据通过映射反应到了上传堆上提供给GPU来读,但是很遗憾,GPU还不知道怎么读,因为我们似乎还一点没说常量缓冲的事情,现在仅仅是数据准备好了而已。

2.常量缓冲区

一般叫做CBV。

1.创建描述符堆:结构体+创建

D3D12_DESCRIPTOR_HEAP_DESC cbvHeapDesc;
    cbvHeapDesc.NumDescriptors = numDescriptors;//,描述符CBV的个数
    cbvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
    cbvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
    cbvHeapDesc.NodeMask = 0;
    ThrowIfFailed(md3dDevice->CreateDescriptorHeap(&cbvHeapDesc,//结构体
        IID_PPV_ARGS(&mCbvHeap)));//堆的指针

首先是Type定义这是一个CBV或者其他,然后是指定他在Shader中可见,因为我们是要把数据放入流水线。

有了存储描述符的堆,现在就要创建描述符CBV:

//获得描符堆在CPU上的地址,并且根据我们常量存储的相对顺序进行偏移到准确位置
auto handle = CD3DX12_CPU_DESCRIPTOR_HANDLE(mCbvHeap>GetCPUDescriptorHandleForHeapStart());
handle.Offset(heapIndex, mCbvSrvUavDescriptorSize);//最开始初始化计算到的CBV占据空间的大小

//获得常量数据objectCB上传堆的实际存储地址

cbAddress = objectCB->GetGPUVirtualAddress();

// Offset to the ith object constant buffer in the buffer.
cbAddress += i*objCBByteSize;

//填写描述符CBV结构体,包含在堆中的存储位置,以及需要的存储空间

D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc;
cbvDesc.BufferLocation = cbAddress;//CBV存储了上传堆存数据的位置
cbvDesc.SizeInBytes = objCBByteSize;

//将CBV存入描述符堆            

md3dDevice->CreateConstantBufferView(&cbvDesc, handle);

以上,CBV也进入他的描述符堆了。

3.数据流转渠道

我们现在做了三件事:

1.准备好了数据,并建立了上传堆对数据的映射;

2.建立了CBV并存进了他的堆;

3.在CBV的结构体描述中存储了上传堆的数据地址。

CPU负责将数据Copy到指定的dataMap,然后映射到GPU(资源的创建和更新)

CBV堆上的CBV记录了GPU资源的地址,以便对这部分资源进行解释;

为了让GPU看懂CBV这份说明书,答案就是根签名。

二、根签名

这是一个三级结构:

根签名——>根参数——>根描述符表/根常量/根描述符

1.创建跟签名

实际设置数据的定义是在最底层(第三层)来做的。

将它看作一个数组来理解,在根描述符表中:

CD3DX12_DESCRIPTOR_RANGE cbvTable0;

cbvTable0.Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 1, 0);//解释他是CBV的,描述符的数量,寄存器槽。

第一个参数指定了类型CBV的(b),第三个参数指定了这是0号,两者组合出了(b0

这段代码首先定义一个无类型的根描述符表,然后使用Init函数进行初始化。

2.根参数

第二级根参数:

CD3DX12_ROOT_PARAMETER slotRootParameter[1];

slotRootParameter[0].InitAsDescriptorTable(1, &cbvTable0);//描述符表个数,描述符表也是定义+赋值

3.根签名

第三级根签名:

//根参数数组的size,根参数,0,nullptr,指定根签名允许输入装配,输入布局等的权限
CD3DX12_ROOT_SIGNATURE_DESC rootSigDesc(1, slotRootParameter, 0, nullptr, D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);

以上只是填写了根签名的结构体,还没有创建,创建过程如下:

首先使用序列化,这是DX12要求的,然后调用创建根签名CreateRootSignature:

ComPtr<ID3DBlob> serializedRootSig = nullptr;
    ComPtr<ID3DBlob> errorBlob = nullptr;
    HRESULT hr = D3D12SerializeRootSignature(&rootSigDesc, D3D_ROOT_SIGNATURE_VERSION_1,
        serializedRootSig.GetAddressOf(), errorBlob.GetAddressOf());

ThrowIfFailed(md3dDevice->CreateRootSignature(
        0,
        serializedRootSig->GetBufferPointer(),//序列化的地址
        serializedRootSig->GetBufferSize(),//序列化的空间大小
        IID_PPV_ARGS(mRootSignature.GetAddressOf())));

三个级别都弄好了,描述符表用来定义根参数,根参数用来定义根签名,这相当于这个三级目录我们搭建好了,剩下的工作就是如何使用。

2.使用跟签名

1.激活当前使用的根签名(放入命令列表的,也就意味着放入流水线)

表示让哪个根签名处于活跃状态,因为后续马上会使用

mCommandList->SetGraphicsRootSignature(mRootSignature.Get());

2.激活CBV堆

CBV具有数据地址,激活CBV堆意味着激活了一些数据

(内部蕴含的逻辑:传到堆的CPU地址的数据会内部自动处理到GPU地址,否则此处拿GPU数据为null)

ID3D12DescriptorHeap* descriptorHeaps[] = { mCbvHeap.Get() };
mCommandList->SetDescriptorHeaps(_countof(descriptorHeaps), descriptorHeaps);

3.对两者进行关联

将激活的数据(在GPU)与激活的根签名记录的槽对齐

//先得到CBV在GPU里边的地址

auto cbvHandle = CD3DX12_GPU_DESCRIPTOR_HANDLE(mCbvHeap->GetGPUDescriptorHandleForHeapStart());
cbvHandle.Offset(cbvIndex, mCbvSrvUavDescriptorSize);//偏移到准确地址

//第一个参数根参数数据索引(寄存器槽和类型),第二个参数CBV地址=>数据对齐        

cmdList->SetGraphicsRootDescriptorTable(0, cbvHandle);

1.在CPU端定义/更新数据,CopyData到上传堆;上传堆作为CBV的结构体数据进入CBV,

2.利用根签名定义数据类型和取数据的槽(b0);

3.同时激活数据和根签名,将数据对齐到槽,定义了数据流通的渠道;

4.shader解析的是时候,自动把关联的数据填充到标记有register(b0)的结构中赋值,进行使用。

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值