- 注意!本文是在下几年前入门期间所写(young and naive),其中许多表述可能不正确,为防止误导,请各位读者仔细鉴别。
用DX12画一些基础形状
Frame Resource
之前做的都是每一帧都flush掉command list,但是这样的话效率很低,我们希望在gpu工作的时候cpu能够接着发下一帧的命令,但是由于command allocator如果被reset掉的话gpu就找不到当前帧需要的资源了,所以每一帧要有自己的command list allocator、自己的constant buffer和一个记录的fence值,用来标识这个frame resource对应的哪个fence。
CPU更新资源的时候,要先等这个frame resource被释放,才能更新这个frame resource里的资源。如下
// Cycle through the circular frame resource array.
mCurrFrameResourceIndex = (mCurrFrameResourceIndex + 1) % gNumFrameResources;
mCurrFrameResource = mFrameResources[mCurrFrameResourceIndex].get();
// Has the GPU finished processing the commands of the current frame resource?
// If not, wait until the GPU has completed commands up to this fence point.
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);
同样的,每帧渲染结束后,不再是flush掉command list,取而代之的是要signal一下fence。
注意由于gpu一般远比cpu忙碌(这也是我们希望的结果,我们不希望gpu来等cpu),所以一般是cpu依然会等待gpu,cpu设置为可以超过gpu几帧(在这里设置为3帧,我们也不希望cpu超过gpu太多),所以这样做只能避免在gpu完成上一帧时,而cpu还没发完下一帧的命令,故每帧开头gpu会等待cpu,用frame resource是为了避免这个,并不能避免cpu等待gpu。
Render Item
如果要渲染大量物体,自己封装一个保存要渲染物体的信息的类是很方便的,这个render item类里面保存了cb的index,vb和ib的start location和index count,还有拓扑方式、世界矩阵,以及一个dirty值来标识这个物体经历了变动(这个dirty值在书上这章还没用,具体有什么用有待以后补全)。
Pass Constants
每一帧的update里面要更新render item数量个的object cb和 pass数量的pass cb,然后在draw里面传入,具体代码不再罗列。
创建几何体
GeometryGenerator.h/.cpp里面都有,创建基础几何体的vb和ib代码如下
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);
//
// We are concatenating all the geometry into one big vertex/index buffer. So
// define the regions in the buffer each submesh covers.
//
// Cache the vertex offsets to each object in the concatenated vertex buffer.
UINT boxVertexOffset = 0;
UINT gridVertexOffset = (UINT)box.Vertices.size();
UINT sphereVertexOffset = gridVertexOffset + (UINT)grid.Vertices.size();
UINT cylinderVertexOffset = sphereVertexOffset + (UINT)sphere.Vertices.size();
// Cache the starting index for each object in the concatenated index buffer.
UINT boxIndexOffset = 0;
UINT gridIndexOffset = (UINT)box.Indices32.size();
UINT sphereIndexOffset = gridIndexOffset + (UINT)grid.Indices32.size();
UINT cylinderIndexOffset = sphereIndexOffset + (UINT)sphere.Indices32.size();
// Define the SubmeshGeometry that cover different
// regions of the vertex/index buffers.
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;
//
// Extract the vertex elements we are interested in and pack the
// vertices of all the meshes into one vertex buffer.
//
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,Render Item包含了MeshGeometry和一些别的参数,故还要用这些MeshGeometry创建同样数量的Render Item。
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;
XMStoreFloat4x4(&rightCylRitem->World, leftCylWorld);
rightCylRitem->ObjCBIndex = objCBIndex++;
rightCylRitem->Geo = mGeometries["shapeGeo"].get();
rightCylRitem->PrimitiveType = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
rightCylRitem->IndexCount = rightCylRitem->Geo->DrawArgs["cylinder"].IndexCount;
rightCylRitem->StartIndexLocation = rightCylRitem->Geo->DrawArgs["cylinder"].StartIndexLocation;
rightCylRitem->BaseVertexLocation = rightCylRitem->Geo->DrawArgs["cylinder"].BaseVertexLocation;
XMStoreFloat4x4(&leftSphereRitem->World, leftSphereWorld);
leftSphereRitem->ObjCBIndex = objCBIndex++;
leftSphereRitem->Geo = mGeometries["shapeGeo"].get();
leftSphereRitem->PrimitiveType = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
leftSphereRitem->IndexCount = leftSphereRitem->Geo->DrawArgs["sphere"].IndexCount;
leftSphereRitem->StartIndexLocation = leftSphereRitem->Geo->DrawArgs["sphere"].StartIndexLocation;
leftSphereRitem->BaseVertexLocation = leftSphereRitem->Geo->DrawArgs["sphere"].BaseVertexLocation;
XMStoreFloat4x4(&rightSphereRitem->World, rightSphereWorld);
rightSphereRitem->ObjCBIndex = objCBIndex++;
rightSphereRitem->Geo = mGeometries["shapeGeo"].get();
rightSphereRitem->PrimitiveType = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
rightSphereRitem->IndexCount = rightSphereRitem->Geo->DrawArgs["sphere"].IndexCount;
rightSphereRitem->StartIndexLocation = rightSphereRitem->Geo->DrawArgs["sphere"].StartIndexLocation;
rightSphereRitem->BaseVertexLocation = rightSphereRitem->Geo->DrawArgs["sphere"].BaseVertexLocation;
mAllRitems.push_back(std::move(leftCylRitem));
mAllRitems.push_back(std::move(rightCylRitem));
mAllRitems.push_back(std::move(leftSphereRitem));
mAllRitems.push_back(std::move(rightSphereRitem));
}
// All the render items are opaque.
for(auto& e : mAllRitems)
mOpaqueRitems.push_back(e.get());
shape demo
用GeometryGenerator创建的一些简单几何体,线框模式渲染效果如下。
用DX12画一个简单的风景图案
三种Root Parameter
之前使用过descriptor table这种root parameter,事实上有三种root parameter,有Descriptor Table、Root Descriptor、Root Constant这三种,Descriptor Table需要传入一系列堆里的cbv,Root Descriptor则只需要传入一个不用在堆里的cbv,而Root Constant只需要传入一个常数即可,然而如果分别传很多个不在堆里的cbv或者直接传很多个常量的话,开销会很大,所以如果传入的数据很多,要省开销的话,就最好放在一个descriptor table里。开销如下:
- Descriptor Table : 1 DWORD
- Root Descriptor : 2 DWORDS
- Root Constant : 1 DWORD per 32-bit constant
Descriptor Table
之前已经用过Descriptor Table了,故不再赘述,注意如果要用一个Descriptor Table来声明多个view输入,那么这些view必须在heap中连续存储,不想连续存储的话,就要用多个Descriptor Table。一个例子如下:
//Create a table with 2 CBVs, 3 SRVs, and 1 UAV.
CD3DX12_DEXCRIPTOR_RANGE descRange[3];
descRange[0].Init(
D3D12_DESCRIPTOR_RANGE_TYPE_CBV, // descriptor type
2, // decriptor count
0, // base shader register arguments are bound to for this root parameter
0, // register space
2);// offset from start of table
descRange[1].Init(
D3D12_DESCRIPTOR_RANGE_TYPE_SRV, // descriptor type
3, // decriptor count
0, // base shader register arguments are bound to for this root parameter
0, // register space
2);// offset from start of table
descRange[1].Init(
D3D12_DESCRIPTOR_RANGE_TYPE_UAV, // descriptor type
1, // decriptor count
0, // base shader register arguments are bound to for this root parameter
0, // register space
2);// offset from start of table
slotRootParameter[0].InitAsDescriptorTable(
3, descRange, D3D12_SHADER_VISIBILITY_ALL);
参数中的base shader register,例如cbv的这个参数如果是0,则是从b0开始的两个,即b0和b1,如果是1,则是从b1开始,即b1和b2。而space一般在shader中不指定的话默认是0,例如可以有
Texture2D gNormalMap : register(t0, space0);
Texture2D gNormalMap : register(t0, space1);
而前者相当于Texture2D gNormalMap : register(t0),因为不指定就默认是space0。
Root Descriptor
声明RootSignature时,用
slotRootParameter[0].InitAsConstantBufferView(UINT shaderRegister);
slotRootParameter[0].InitAsShaderResourceView(UINT shaderRegister);
slotRootParameter[0].InitAsUnorderedAccessView(UINT shaderRegister);
这三个来声明CBV/SRV/UAV,参数意思是寄存器的index,例如声明cbv时参数如果是0则是b0。
渲染时要传入参数的时候,用cmdList->SetGraphicsRootConstantBufferView,例如
UINT objCBByteSize = d3dUtil::CalcConstantBufferByteSize(sizeof(ObjectConstants));
D3D12_GPU_VIRTUAL_ADDRESS objCBAddress =
objectCB->GetGPUVirtualAddress();
// Offset to the constants for this object in the buffer.
objCBAddress += ri->ObjCBIndex * objCBByteSize;
cmdList->SetGraphicsRootConstantBufferView(
0, // root parameter index
objCBAddress);
注意,如果用Root Descriptor的话就不需要额外再创建CBV了,传值的时候直接传gpu地址就行,这里的这个Root Parameter本身就是CBV了。
Root Constant
声明的时候用
slotRootParameter[0].InitAsConstants(num32BitValues, shaderRegister);
其中num是32位数的个数,shaderRegister如果是0就是指b0。
传入参数的时候,用ID3D12GraphicsCommandList::SetGraphicsRoot32BitConstants(
UINT RootParameterIndex,
UINT Num32BitValuesToSet,
const void *pSrcData,
UINT DestOffsetIn32BitValues);
第一个参数是root parameter的index,第二个是要传入的constant数的数量,第三个则是连续存储32位constant的首地址指针,第四个则是offset,比如可以分两次传入,第一次的offset是0,第二次的offset位第一次的结尾,即从第一次传入后开始接着写入,具体见以下例子。
例子如下
// Root signature definition
CD3DX12_ROOT_PARAMETER slotRootParameter[1];
slotRootParameter[0].InitAsContants(12,0);
...
// set the constants to register b0.
auto weights = CalcGaussWeights(2.5f);
int blurRadius = (int)weights.size() / 2;
cmdList->SetGraphicsRoot32BitConstants(0, 1, &blurRadius, 0);
cmdList->SetGraphicsRoot32BitConstants(0, (UINT)weights.size(),
weights.data(), 1); //注意这个1是指位移1个32位constant。
// HLSL code
cbuffer cbSettings : register(b0)
{
int gBlurRadius;
float w0;
float w1;
float w2;
float w3;
float w4;
float w5;
float w6;
float w7;
float w8;
float w9;
float w10;
}
动态Vertex Buffer
可以这样写:
auto currWavesVB = mCurrFrameResource->WavesVB.get();
for(int i=0; i< mWaves->VertexCount(); i++)
{
Vertex v;
v.Pos = mWaves->Position(i);
v.Color = XMFLOAT4(DirectX::Colors::Blue);
currWavesVB->CopyData(i,v);
}
mWavesRitem->Geo->VertexBufferGPU = currWavesVB->Resource();
渲染风景画
利用动态vb渲染波浪的图案,利用一个简单的grid变化高度来形成山脉的形状,就可以制作出一幅简单的风景画。
这个demo中用root descriptor代替了descriptor table所以不再需要建立cbv的heap。
最后渲染结果如下图。