• d3d12龙书阅读----绘制几何体(下)


    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 CmdListAlloc;
    
        同理 每个帧资源也要有自己的常量缓冲区
        std::unique_ptr> PassCB = nullptr;
        std::unique_ptr> 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(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 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 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();
    	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();
    	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();
        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();
    		auto rightCylRitem = std::make_unique();
    		auto leftSphereRitem = std::make_unique();
    		auto rightSphereRitem = std::make_unique();
    
    		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& 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);
        }
    }
    

    细探根签名

    根签名由一系列根参数构成 根参数主要有以下三种类型
    img

    我们可以创建出任意组合的根签名 只要不超过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根描述符:
    img
    ShaderVisibility代表着着色器可见性:
    img

    创建 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定义为:
    img
    numDescriptors代表着范围内描述符的数量
    baseShaderRegister:
    img

    img
    然后使用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);
    }
    

    例子:
    img
    img

    不同类型的根签名绑定着色器寄存器

    将不同类型的根签名绑定着色器寄存器需要使用不同的命令:

    根常量: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
    其中根常量与根描述符都不需要涉及描述符堆

  • 相关阅读:
    电脑基础知识-电脑不认新硬盘时该怎么办?
    顶顶通语音识别使用说明
    WWDC 2024及其AI功能的引入对中国用户和开发者的影响
    代码简洁之道(译)
    Docker学习2——Docker高级
    安装SQL Server详细教程
    信息通信企业开展数据安全管理工作的指引与实践
    Python 基础(十四):类和对象
    如何搭建高效又稳定的数据填报平台?_光点科技
    我的创作纪念日
  • 原文地址:https://www.cnblogs.com/dyccyber/p/18185325