d3d12龙书阅读----绘制几何体(下)
本节在上一节的基础上,对整个绘制过程进行优化,将绘制单个几何体的内容拓展到了多个几何体,同时对根签名进行了进一步地探索。
帧资源
在之前绘制每帧的结尾,我们都要使用flushingcommandqueue方法,要一直等待gpu执行完所有命令,才会继续绘制下一帧,此时cpu处于空闲时间,同时,在绘制每一帧的初始阶段,gpu要等待cpu提交命令,此时gpu处于空闲时间
解决上述问题的一种方法是:
构建以cpu每帧都要更新的资源为数组元素的环形数组,这些资源被称为帧资源,一般循环数组由3个帧资源元素构成
当gpu在处理上一帧的命令时,cpu可以为下一帧更新资源,并构建并提交相应的命令列表,如果环形数组有三个元素,则令cpu比gpu提前处理两帧,这样可以确保gpu持续工作
帧资源定义:
针对每个物体/几何体的常量缓冲区定义
目前存储的是每个物体的世界矩阵 即 模型矩阵 将物体从局部坐标系转换到世界坐标系 代表着物体的位置
struct ObjectConstants { DirectX::XMFLOAT4X4 World = MathHelper::Identity4x4(); }; 针对每次渲染过程(rendering pass)所要用到的数据 比如 观察矩阵 投影矩阵 时间 等等 struct PassConstants { DirectX::XMFLOAT4X4 View = MathHelper::Identity4x4(); DirectX::XMFLOAT4X4 InvView = MathHelper::Identity4x4(); DirectX::XMFLOAT4X4 Proj = MathHelper::Identity4x4(); DirectX::XMFLOAT4X4 InvProj = MathHelper::Identity4x4(); DirectX::XMFLOAT4X4 ViewProj = MathHelper::Identity4x4(); DirectX::XMFLOAT4X4 InvViewProj = MathHelper::Identity4x4(); DirectX::XMFLOAT3 EyePosW = { 0.0f, 0.0f, 0.0f }; float cbPerObjectPad1 = 0.0f; DirectX::XMFLOAT2 RenderTargetSize = { 0.0f, 0.0f }; DirectX::XMFLOAT2 InvRenderTargetSize = { 0.0f, 0.0f }; float NearZ = 0.0f; float FarZ = 0.0f; float TotalTime = 0.0f; float DeltaTime = 0.0f; }; 顶点定义 struct Vertex { DirectX::XMFLOAT3 Pos; DirectX::XMFLOAT4 Color; }; 存储cpu为一帧构建命令列表所需资源 struct FrameResource { public: FrameResource(ID3D12Device* device, UINT passCount, UINT objectCount); FrameResource(const FrameResource& rhs) = delete; FrameResource& operator=(const FrameResource& rhs) = delete; ~FrameResource(); 每一帧都要有自己的命令分配器 因为当上一帧的gpu还在处理命令时 我们不能重置命令分配器 Microsoft::WRL::ComPtr<ID3D12CommandAllocator> CmdListAlloc; 同理 每个帧资源也要有自己的常量缓冲区 std::unique_ptr<UploadBuffer<PassConstants>> PassCB = nullptr; std::unique_ptr<UploadBuffer<ObjectConstants>> ObjectCB = nullptr; 围栏点可以帮助检测 gpu是否仍然使用着帧资源 UINT64 Fence = 0; };
可以看到我们在帧资源中将常量缓冲区分为pass 与 object, 这是基于资源的更新频率对常量资源进行分组,每次渲染过程我们都要更新pass缓冲区,而对于object来说,只有当发生变化的时候才需要更新,具体代码我们待会再看。
回到cpu与gpu的同步上来,首先创建初始化帧资源数组:
void ShapesApp::BuildFrameResources() { for(int i = 0; i < gNumFrameResources; ++i) { mFrameResources.push_back(std::make_unique<FrameResource>(md3dDevice.Get(), 1, (UINT)mAllRitems.size())); 其中1代表着一个帧资源1个pass缓冲区 第二个是所有渲染物体的数目 } }
cpu端更新第n帧:
void ShapesApp::Update(const GameTimer& gt) { OnKeyboardInput(gt); UpdateCamera(gt); 循环帧资源数组 mCurrFrameResourceIndex = (mCurrFrameResourceIndex + 1) % gNumFrameResources; mCurrFrameResource = mFrameResources[mCurrFrameResourceIndex].get(); 等待gpu完成围栏点之前的所有命令 if(mCurrFrameResource->Fence != 0 && mFence->GetCompletedValue() < mCurrFrameResource->Fence) { HANDLE eventHandle = CreateEventEx(nullptr, false, false, EVENT_ALL_ACCESS); ThrowIfFailed(mFence->SetEventOnCompletion(mCurrFrameResource->Fence, eventHandle)); WaitForSingleObject(eventHandle, INFINITE); CloseHandle(eventHandle); } 更新常量缓冲区 UpdateObjectCBs(gt); UpdateMainPassCB(gt); }
绘制第n帧:
void ShapesApp::draw(const GameTimer& gt){ 添加围栏值 将命令标记到此围栏点 mCurrFrameResource->Fence = ++mCurrentFence; 向命令队列中添加一条设置新围栏点的命令 由于这条命令要交给gpu处理,所以gpu处理完signal之前的所有命令之前,它不会设置新的围栏点 mCommandQueue->Signal(mFence.Get(), mCurrentFence); }
其实这种方法也有着缺陷,如果gpu处理命令的速度大于cpu提交命令列表的速度,则还是要等待cpu,理想的情况是cpu处理帧的速度大于gpu,这样cpu可以有空闲时间来处理游戏逻辑的其它部分,此方法的最大好处是cpu可以持续向gpu提供数据
渲染项
渲染项是一个轻量型结构 用于存储绘制物体所需要数据:
struct RenderItem { RenderItem() = default; 世界矩阵 XMFLOAT4X4 World = MathHelper::Identity4x4(); // 一个脏标记用于记录是否需要更新物体缓冲区 因为每个帧资源都有各自独立的物体缓冲区 所以脏标记的数目要设置和帧资源数目一致 int NumFramesDirty = gNumFrameResources; // 当前渲染项对应object缓冲区索引 UINT ObjCBIndex = -1; 该渲染项参与绘制的几何体 MeshGeometry* Geo = nullptr; //图元拓扑类型 D3D12_PRIMITIVE_TOPOLOGY PrimitiveType = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST; // DrawIndexedInstanced 方法的参数 UINT IndexCount = 0; UINT StartIndexLocation = 0; int BaseVertexLocation = 0; };
渲染项的具体使用之后介绍
渲染过程中用到的常量数据
我们需要更新hlsl中用到的cbuffer:
cbuffer cbPerObject : register(b0) { float4x4 gWorld; }; cbuffer cbPass : register(b1) { float4x4 gView; float4x4 gInvView; float4x4 gProj; float4x4 gInvProj; float4x4 gViewProj; float4x4 gInvViewProj; float3 gEyePosW; float cbPerObjectPad1; float2 gRenderTargetSize; float2 gInvRenderTargetSize; float gNearZ; float gFarZ; float gTotalTime; float gDeltaTime; };
更新object缓冲区 与 pass缓冲区 这里利用了前一节介绍的uploadbuffer的方法 从cpu端更新数据:
void ShapesApp::UpdateObjectCBs(const GameTimer& gt) { auto currObjectCB = mCurrFrameResource->ObjectCB.get(); for(auto& e : mAllRitems) { 每个帧资源都需要更新物体缓冲区 if(e->NumFramesDirty > 0) { XMMATRIX world = XMLoadFloat4x4(&e->World); ObjectConstants objConstants; XMStoreFloat4x4(&objConstants.World, XMMatrixTranspose(world)); currObjectCB->CopyData(e->ObjCBIndex, objConstants); // Next FrameResource need to be updated too. e->NumFramesDirty--; } } } void ShapesApp::UpdateMainPassCB(const GameTimer& gt) { XMMATRIX view = XMLoadFloat4x4(&mView); XMMATRIX proj = XMLoadFloat4x4(&mProj); XMMATRIX viewProj = XMMatrixMultiply(view, proj); XMMATRIX invView = XMMatrixInverse(&XMMatrixDeterminant(view), view); XMMATRIX invProj = XMMatrixInverse(&XMMatrixDeterminant(proj), proj); XMMATRIX invViewProj = XMMatrixInverse(&XMMatrixDeterminant(viewProj), viewProj); XMStoreFloat4x4(&mMainPassCB.View, XMMatrixTranspose(view)); XMStoreFloat4x4(&mMainPassCB.InvView, XMMatrixTranspose(invView)); XMStoreFloat4x4(&mMainPassCB.Proj, XMMatrixTranspose(proj)); XMStoreFloat4x4(&mMainPassCB.InvProj, XMMatrixTranspose(invProj)); XMStoreFloat4x4(&mMainPassCB.ViewProj, XMMatrixTranspose(viewProj)); XMStoreFloat4x4(&mMainPassCB.InvViewProj, XMMatrixTranspose(invViewProj)); mMainPassCB.EyePosW = mEyePos; mMainPassCB.RenderTargetSize = XMFLOAT2((float)mClientWidth, (float)mClientHeight); mMainPassCB.InvRenderTargetSize = XMFLOAT2(1.0f / mClientWidth, 1.0f / mClientHeight); mMainPassCB.NearZ = 1.0f; mMainPassCB.FarZ = 1000.0f; mMainPassCB.TotalTime = gt.TotalTime(); mMainPassCB.DeltaTime = gt.DeltaTime(); auto currPassCB = mCurrFrameResource->PassCB.get(); currPassCB->CopyData(0, mMainPassCB); }
绘制多种几何体
在这里就不再介绍柱体 球体 正方体的过程 设计到一些几何知识
直接进入几何体的绘制阶段
创建顶点与索引缓冲区
将所有几何体的顶点缓冲区 与 索引缓冲区,合成一个大的顶点缓冲区与 索引缓冲区,之后使用drawindexinstanced方法绘制 需要记录每个几何体起始索引 索引数 以及起始顶点
void ShapesApp::BuildShapeGeometry() { GeometryGenerator geoGen; GeometryGenerator::MeshData box = geoGen.CreateBox(1.5f, 0.5f, 1.5f, 3); GeometryGenerator::MeshData grid = geoGen.CreateGrid(20.0f, 30.0f, 60, 40); GeometryGenerator::MeshData sphere = geoGen.CreateSphere(0.5f, 20, 20); GeometryGenerator::MeshData cylinder = geoGen.CreateCylinder(0.5f, 0.3f, 3.0f, 20, 20); // 计算各几何体的起始顶点 UINT boxVertexOffset = 0; UINT gridVertexOffset = (UINT)box.Vertices.size(); UINT sphereVertexOffset = gridVertexOffset + (UINT)grid.Vertices.size(); UINT cylinderVertexOffset = sphereVertexOffset + (UINT)sphere.Vertices.size(); // 存储起始索引 UINT boxIndexOffset = 0; UINT gridIndexOffset = (UINT)box.Indices32.size(); UINT sphereIndexOffset = gridIndexOffset + (UINT)grid.Indices32.size(); UINT cylinderIndexOffset = sphereIndexOffset + (UINT)sphere.Indices32.size(); 定义各子网格结构体 SubmeshGeometry boxSubmesh; boxSubmesh.IndexCount = (UINT)box.Indices32.size(); boxSubmesh.StartIndexLocation = boxIndexOffset; boxSubmesh.BaseVertexLocation = boxVertexOffset; SubmeshGeometry gridSubmesh; gridSubmesh.IndexCount = (UINT)grid.Indices32.size(); gridSubmesh.StartIndexLocation = gridIndexOffset; gridSubmesh.BaseVertexLocation = gridVertexOffset; SubmeshGeometry sphereSubmesh; sphereSubmesh.IndexCount = (UINT)sphere.Indices32.size(); sphereSubmesh.StartIndexLocation = sphereIndexOffset; sphereSubmesh.BaseVertexLocation = sphereVertexOffset; SubmeshGeometry cylinderSubmesh; cylinderSubmesh.IndexCount = (UINT)cylinder.Indices32.size(); cylinderSubmesh.StartIndexLocation = cylinderIndexOffset; cylinderSubmesh.BaseVertexLocation = cylinderVertexOffset; 将各顶点 各索引合并 子网格合并为一个大的meshgeometry auto totalVertexCount = box.Vertices.size() + grid.Vertices.size() + sphere.Vertices.size() + cylinder.Vertices.size(); std::vector<Vertex> vertices(totalVertexCount); UINT k = 0; for(size_t i = 0; i < box.Vertices.size(); ++i, ++k) { vertices[k].Pos = box.Vertices[i].Position; vertices[k].Color = XMFLOAT4(DirectX::Colors::DarkGreen); } for(size_t i = 0; i < grid.Vertices.size(); ++i, ++k) { vertices[k].Pos = grid.Vertices[i].Position; vertices[k].Color = XMFLOAT4(DirectX::Colors::ForestGreen); } for(size_t i = 0; i < sphere.Vertices.size(); ++i, ++k) { vertices[k].Pos = sphere.Vertices[i].Position; vertices[k].Color = XMFLOAT4(DirectX::Colors::Crimson); } for(size_t i = 0; i < cylinder.Vertices.size(); ++i, ++k) { vertices[k].Pos = cylinder.Vertices[i].Position; vertices[k].Color = XMFLOAT4(DirectX::Colors::SteelBlue); } std::vector<std::uint16_t> indices; indices.insert(indices.end(), std::begin(box.GetIndices16()), std::end(box.GetIndices16())); indices.insert(indices.end(), std::begin(grid.GetIndices16()), std::end(grid.GetIndices16())); indices.insert(indices.end(), std::begin(sphere.GetIndices16()), std::end(sphere.GetIndices16())); indices.insert(indices.end(), std::begin(cylinder.GetIndices16()), std::end(cylinder.GetIndices16())); const UINT vbByteSize = (UINT)vertices.size() * sizeof(Vertex); const UINT ibByteSize = (UINT)indices.size() * sizeof(std::uint16_t); auto geo = std::make_unique<MeshGeometry>(); geo->Name = "shapeGeo"; ThrowIfFailed(D3DCreateBlob(vbByteSize, &geo->VertexBufferCPU)); CopyMemory(geo->VertexBufferCPU->GetBufferPointer(), vertices.data(), vbByteSize); ThrowIfFailed(D3DCreateBlob(ibByteSize, &geo->IndexBufferCPU)); CopyMemory(geo->IndexBufferCPU->GetBufferPointer(), indices.data(), ibByteSize); geo->VertexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(), mCommandList.Get(), vertices.data(), vbByteSize, geo->VertexBufferUploader); geo->IndexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(), mCommandList.Get(), indices.data(), ibByteSize, geo->IndexBufferUploader); geo->VertexByteStride = sizeof(Vertex); geo->VertexBufferByteSize = vbByteSize; geo->IndexFormat = DXGI_FORMAT_R16_UINT; geo->IndexBufferByteSize = ibByteSize; geo->DrawArgs["box"] = boxSubmesh; geo->DrawArgs["grid"] = gridSubmesh; geo->DrawArgs["sphere"] = sphereSubmesh; geo->DrawArgs["cylinder"] = cylinderSubmesh; mGeometries[geo->Name] = std::move(geo); }
定义具体渲染项
在完成构建几何体之后 我们根据上一步创建的meshgeometry 来提取submeshgeometry 然后 里面的信息 根据需要创建相应的渲染项 并填写相应的内容
void ShapesApp::BuildRenderItems() { auto boxRitem = std::make_unique<RenderItem>(); XMStoreFloat4x4(&boxRitem->World, XMMatrixScaling(2.0f, 2.0f, 2.0f)*XMMatrixTranslation(0.0f, 0.5f, 0.0f)); boxRitem->ObjCBIndex = 0; boxRitem->Geo = mGeometries["shapeGeo"].get(); boxRitem->PrimitiveType = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST; boxRitem->IndexCount = boxRitem->Geo->DrawArgs["box"].IndexCount; boxRitem->StartIndexLocation = boxRitem->Geo->DrawArgs["box"].StartIndexLocation; boxRitem->BaseVertexLocation = boxRitem->Geo->DrawArgs["box"].BaseVertexLocation; mAllRitems.push_back(std::move(boxRitem)); auto gridRitem = std::make_unique<RenderItem>(); gridRitem->World = MathHelper::Identity4x4(); gridRitem->ObjCBIndex = 1; gridRitem->Geo = mGeometries["shapeGeo"].get(); gridRitem->PrimitiveType = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST; gridRitem->IndexCount = gridRitem->Geo->DrawArgs["grid"].IndexCount; gridRitem->StartIndexLocation = gridRitem->Geo->DrawArgs["grid"].StartIndexLocation; gridRitem->BaseVertexLocation = gridRitem->Geo->DrawArgs["grid"].BaseVertexLocation; mAllRitems.push_back(std::move(gridRitem)); UINT objCBIndex = 2; for(int i = 0; i < 5; ++i) { auto leftCylRitem = std::make_unique<RenderItem>(); auto rightCylRitem = std::make_unique<RenderItem>(); auto leftSphereRitem = std::make_unique<RenderItem>(); auto rightSphereRitem = std::make_unique<RenderItem>(); XMMATRIX leftCylWorld = XMMatrixTranslation(-5.0f, 1.5f, -10.0f + i*5.0f); XMMATRIX rightCylWorld = XMMatrixTranslation(+5.0f, 1.5f, -10.0f + i*5.0f); XMMATRIX leftSphereWorld = XMMatrixTranslation(-5.0f, 3.5f, -10.0f + i*5.0f); XMMATRIX rightSphereWorld = XMMatrixTranslation(+5.0f, 3.5f, -10.0f + i*5.0f); XMStoreFloat4x4(&leftCylRitem->World, rightCylWorld); leftCylRitem->ObjCBIndex = objCBIndex++; leftCylRitem->Geo = mGeometries["shapeGeo"].get(); leftCylRitem->PrimitiveType = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST; leftCylRitem->IndexCount = leftCylRitem->Geo->DrawArgs["cylinder"].IndexCount; leftCylRitem->StartIndexLocation = leftCylRitem->Geo->DrawArgs["cylinder"].StartIndexLocation; leftCylRitem->BaseVertexLocation = leftCylRitem->Geo->DrawArgs["cylinder"].BaseVertexLocation; 此处省略 } for(auto& e : mAllRitems) mOpaqueRitems.push_back(e.get()); }
定义常量缓冲区视图
之后由于我们现在有3个pass常量缓冲区 3n个object常量缓冲区 总共3n+3个常量缓冲区 所以就需要 3n+3个cbv 同时也要拓展描述符堆的大小:
void ShapesApp::BuildDescriptorHeaps() { UINT objCount = (UINT)mOpaqueRitems.size(); UINT numDescriptors = (objCount+1) * gNumFrameResources; mPassCbvOffset = objCount * gNumFrameResources; D3D12_DESCRIPTOR_HEAP_DESC cbvHeapDesc; cbvHeapDesc.NumDescriptors = numDescriptors; 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))); }
void ShapesApp::BuildConstantBufferViews() { UINT objCBByteSize = d3dUtil::CalcConstantBufferByteSize(sizeof(ObjectConstants)); UINT objCount = (UINT)mOpaqueRitems.size(); 每个帧资源中的每个object都需要一个cbv for(int frameIndex = 0; frameIndex < gNumFrameResources; ++frameIndex) { auto objectCB = mFrameResources[frameIndex]->ObjectCB->Resource(); for(UINT i = 0; i < objCount; ++i) { D3D12_GPU_VIRTUAL_ADDRESS cbAddress = objectCB->GetGPUVirtualAddress(); // 每个物体的偏移 cbAddress += i*objCBByteSize; // 计算在描述符堆中的偏移 int heapIndex = frameIndex*objCount + i; auto handle = CD3DX12_CPU_DESCRIPTOR_HANDLE(mCbvHeap->GetCPUDescriptorHandleForHeapStart()); handle.Offset(heapIndex, mCbvSrvUavDescriptorSize); D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc; cbvDesc.BufferLocation = cbAddress; cbvDesc.SizeInBytes = objCBByteSize; md3dDevice->CreateConstantBufferView(&cbvDesc, handle); } } UINT passCBByteSize = d3dUtil::CalcConstantBufferByteSize(sizeof(PassConstants)); 每个帧资源都要一个pass 描述符 for(int frameIndex = 0; frameIndex < gNumFrameResources; ++frameIndex) { auto passCB = mFrameResources[frameIndex]->PassCB->Resource(); D3D12_GPU_VIRTUAL_ADDRESS cbAddress = passCB->GetGPUVirtualAddress(); 计算偏移 int heapIndex = mPassCbvOffset + frameIndex; auto handle = CD3DX12_CPU_DESCRIPTOR_HANDLE(mCbvHeap->GetCPUDescriptorHandleForHeapStart()); handle.Offset(heapIndex, mCbvSrvUavDescriptorSize); D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc; cbvDesc.BufferLocation = cbAddress; cbvDesc.SizeInBytes = passCBByteSize; md3dDevice->CreateConstantBufferView(&cbvDesc, handle); } }
绘制
最后一步是绘制每个渲染项 :
void ShapesApp::DrawRenderItems(ID3D12GraphicsCommandList* cmdList, const std::vector<RenderItem*>& ritems) { UINT objCBByteSize = d3dUtil::CalcConstantBufferByteSize(sizeof(ObjectConstants)); auto objectCB = mCurrFrameResource->ObjectCB->Resource(); for(size_t i = 0; i < ritems.size(); ++i) { auto ri = ritems[i]; cmdList->IASetVertexBuffers(0, 1, &ri->Geo->VertexBufferView()); cmdList->IASetIndexBuffer(&ri->Geo->IndexBufferView()); cmdList->IASetPrimitiveTopology(ri->PrimitiveType); UINT cbvIndex = mCurrFrameResourceIndex*(UINT)mOpaqueRitems.size() + ri->ObjCBIndex; auto cbvHandle = CD3DX12_GPU_DESCRIPTOR_HANDLE(mCbvHeap->GetGPUDescriptorHandleForHeapStart()); cbvHandle.Offset(cbvIndex, mCbvSrvUavDescriptorSize); cmdList->SetGraphicsRootDescriptorTable(0, cbvHandle); cmdList->DrawIndexedInstanced(ri->IndexCount, 1, ri->StartIndexLocation, ri->BaseVertexLocation, 0); } }
细探根签名
根签名由一系列根参数构成 根参数主要有以下三种类型

我们可以创建出任意组合的根签名 只要不超过64 DWORD大小 根常量使用方便 无需使用相应的常量缓冲区 与 cbv堆,但是假如我们使用根常量存储mvp矩阵,16个float元素需要16个DWORD 即需要16个根常量 大幅消耗了根签名的空间 所以在使用时我们要灵活组合
根签名结构体定义:
typedef struct D3D12_ROOT_PARAMETER { D3D12_ROOT_PARAMETER_TYPE ParameterType; union { D3D12_ROOT_DESCRIPTOR_TABLE DescriptorTable; D3D12_ROOT_CONSTANTS Constants; D3D12_ROOT_DESCRIPTOR Descriptor; } ; D3D12_SHADER_VISIBILITY ShaderVisibility; } D3D12_ROOT_PARAMETER;
其中ParameterType的定义是根参数的类型,包括描述符表,根常量,cbv根描述符,srv根描述符,uav根描述符:

ShaderVisibility代表着着色器可见性:

创建 DescriptorTable Constants Descriptor
DescriptorTable :
描述符表的定义可以借助CD3DX12_DESCRIPTOR_RANGE的init方法
struct CD3DX12_DESCRIPTOR_RANGE : public D3D12_DESCRIPTOR_RANGE { CD3DX12_DESCRIPTOR_RANGE() { } explicit CD3DX12_DESCRIPTOR_RANGE(const D3D12_DESCRIPTOR_RANGE &o) : D3D12_DESCRIPTOR_RANGE(o) {} CD3DX12_DESCRIPTOR_RANGE( D3D12_DESCRIPTOR_RANGE_TYPE rangeType, UINT numDescriptors, UINT baseShaderRegister, UINT registerSpace = 0, UINT offsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND) { Init(rangeType, numDescriptors, baseShaderRegister, registerSpace, offsetInDescriptorsFromTableStart); } inline void Init( D3D12_DESCRIPTOR_RANGE_TYPE rangeType, UINT numDescriptors, UINT baseShaderRegister, UINT registerSpace = 0, UINT offsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND) { Init(*this, rangeType, numDescriptors, baseShaderRegister, registerSpace, offsetInDescriptorsFromTableStart); } }
其中D3D12_DESCRIPTOR_RANGE_TYPE rangeType定义为:

numDescriptors代表着范围内描述符的数量
baseShaderRegister:


然后使用InitAsDescriptorTable创建 :
CD3DX12_DESCRIPTOR_RANGE cbvTable0; cbvTable0.Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 1, 0); CD3DX12_DESCRIPTOR_RANGE cbvTable1; cbvTable1.Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 1, 1); CD3DX12_ROOT_PARAMETER slotRootParameter[2]; slotRootParameter[0].InitAsDescriptorTable(1, &cbvTable0); slotRootParameter[1].InitAsDescriptorTable(1, &cbvTable1);
根描述符与根常量的定义可以直接使用如下方法创建:
static inline void InitAsConstants( _Out_ D3D12_ROOT_PARAMETER &rootParam, UINT num32BitValues, UINT shaderRegister, UINT registerSpace = 0, D3D12_SHADER_VISIBILITY visibility = D3D12_SHADER_VISIBILITY_ALL) { rootParam.ParameterType = D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS; rootParam.ShaderVisibility = visibility; CD3DX12_ROOT_CONSTANTS::Init(rootParam.Constants, num32BitValues, shaderRegister, registerSpace); } static inline void InitAsConstantBufferView( _Out_ D3D12_ROOT_PARAMETER &rootParam, UINT shaderRegister, UINT registerSpace = 0, D3D12_SHADER_VISIBILITY visibility = D3D12_SHADER_VISIBILITY_ALL) { rootParam.ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV; rootParam.ShaderVisibility = visibility; CD3DX12_ROOT_DESCRIPTOR::Init(rootParam.Descriptor, shaderRegister, registerSpace); }
例子:


不同类型的根签名绑定着色器寄存器
将不同类型的根签名绑定着色器寄存器需要使用不同的命令:
根常量:ID3D12GraphicsCommandList::SetComputeRoot32BitConstants
https://learn.microsoft.com/zh-cn/windows/win32/api/d3d12/nf-d3d12-id3d12graphicscommandlist-setcomputeroot32bitconstants
根描述符:ID3D12GraphicsCommandList::SetComputeRootConstantBufferView
https://learn.microsoft.com/zh-cn/windows/win32/api/d3d12/nf-d3d12-id3d12graphicscommandlist-setcomputerootconstantbufferview
描述符表:ID3D12GraphicsCommandList::SetComputeRootDescriptorTable
https://learn.microsoft.com/zh-cn/windows/win32/api/d3d12/nf-d3d12-id3d12graphicscommandlist-setcomputerootdescriptortable
其中根常量与根描述符都不需要涉及描述符堆