一、初始化D3D库
启用 DirectX数学库
x86需要启用SSE2指令集,所有平台均需将浮点模型设置为fast。默认为: 精度 (/fp:precise)。
#include <DirectXMath.h>
#include <DirectXPackedVector.h>
启用调试模式下的内存泄漏检测
// Enable run-time memory check for debug builds.
#if defined(DEBUG) | defined(_DEBUG)_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#define new new(_CLIENT_BLOCK, __FILE__, __LINE__)
#endif#if defined(DEBUG) | defined(_DEBUG)_CrtDumpMemoryLeaks();
#endif
启用D3D调试接口和调试层。
#if defined(DEBUG) || defined(_DEBUG) // Enable the D3D12 debug layer.{ComPtr<ID3D12Debug> debugController;ThrowIfFailed(D3D12GetDebugInterface(IID_PPV_ARGS(debugController.GetAddressOf())));debugController->EnableDebugLayer();}
#endif
1、创建DXGI对象
ComPtr<IDXGIFactory4> mdxgiFactory;
CreateDXGIFactory1(IID_PPV_ARGS(&mdxgiFactory));
2、创建设备
ComPtr<ID3D12Device> md3dDevice; 使用高性能显卡
//枚举设备,
//requestHighPerformanceAdapter设置为true
//ComPtr<IDXGIFactory4> factory;ComPtr<IDXGIAdapter1> adapter;ComPtr<IDXGIFactory6> factory6;
if (SUCCEEDED(pFactory->QueryInterface(IID_PPV_ARGS(&factory6))))
{for (UINT adapterIndex = 0;SUCCEEDED(factory6->EnumAdapterByGpuPreference(adapterIndex,requestHighPerformanceAdapter == true ? DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE : DXGI_GPU_PREFERENCE_UNSPECIFIED,IID_PPV_ARGS(&adapter)));++adapterIndex){DXGI_ADAPTER_DESC1 desc;adapter->GetDesc1(&desc);if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE){// Don't select the Basic Render Driver adapter.// If you want a software adapter, pass in "/warp" on the command line.continue;}// Check to see whether the adapter supports Direct3D 12, but don't create the// actual device yet.if (SUCCEEDED(D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_11_0, _uuidof(ID3D12Device), nullptr))){break;}}
}if(adapter.Get() == nullptr)
{for (UINT adapterIndex = 0; SUCCEEDED(pFactory->EnumAdapters1(adapterIndex, &adapter)); ++adapterIndex){DXGI_ADAPTER_DESC1 desc;adapter->GetDesc1(&desc);if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE){// Don't select the Basic Render Driver adapter.// If you want a software adapter, pass in "/warp" on the command line.continue;}// Check to see whether the adapter supports Direct3D 12, but don't create the// actual device yet.if (SUCCEEDED(D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_11_0, _uuidof(ID3D12Device), nullptr))){break;}}
}ComPtr<IDXGIAdapter1> hardwareAdapter;
D3D12CreateDevice( hardwareAdapter.Get(), // default adapterD3D_FEATURE_LEVEL_12_0, IID_PPV_ARGS(&md3dDevice));
3、创建围栏
围栏用于CPU和GPU之间的同步,它可以强制CPU等待GPU完成所有的渲染指令。ComPtr<ID3D12Fence> mFence;
md3dDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&mFence));
4、检查多采样质量级别
D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS msQualityLevels;
msQualityLevels.Format = mBackBufferFormat;
msQualityLevels.SampleCount = 4; //每像素采样次数
msQualityLevels.Flags = D3D12_MULTISAMPLE_QUALITY_LEVELS_FLAG_NONE;
msQualityLevels.NumQualityLevels = 0; //输入输出参数,输入时为0
ThrowIfFailed(md3dDevice->CheckFeatureSupport(D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS,&msQualityLevels,sizeof(msQualityLevels)));
m4xMsaaQuality = msQualityLevels.NumQualityLevels; //支持的最高质量级别, 使用时-1
5、创建命令队列、命令列表和命令分配器
多个命令列表可以关联到一个命令分配器,但不能同时记录命令,必须先关闭其他的命令列表。创建命令列表时,它处于Open状态,需要先Close。
在GPU执行完命令分配器中的所有命令前,不能重置命令分配器。 注意:命令队列是线程安全的,其他两种均不是线程安全的。
void D3DApp::CreateCommandObjects()
{D3D12_COMMAND_QUEUE_DESC queueDesc = {};queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;ThrowIfFailed(md3dDevice->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&mCommandQueue)));ThrowIfFailed(md3dDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT,IID_PPV_ARGS(mDirectCmdListAlloc.GetAddressOf())));ThrowIfFailed(md3dDevice->CreateCommandList(0,D3D12_COMMAND_LIST_TYPE_DIRECT,mDirectCmdListAlloc.Get(), // Associated command allocatornullptr, // Initial PipelineStateObjectIID_PPV_ARGS(mCommandList.GetAddressOf())));// Start off in a closed state. This is because the first time we refer // to the command list we will Reset it, and it needs to be closed before// calling Reset.// 关闭此命令列表在Allocator中记录命令,其他命令列表可以使用其AllocatormCommandList->Close();
}
二、资源管理
1、概述
GPU资源都存于堆中,本质是具有特定属性的GPU显存块。在绘制之前,需要将一次绘制调用的相关资源链接到渲染流水线,GPU通过描述符访问绑定到流水线上的资源(显存)。
创建描述符堆->创建资源->创建视图,将描述符堆的元素指向资源:
1.1 创建描述符堆
填充描述符堆描述结构D3D12_DESCRIPTOR_HEAP_DESC,然后调用md3dDevice->CreateDescriptorHeap,保存到ComPtr<ID3D12DescriptorHeap>中。
D3D12_DESCRIPTOR_HEAP_DESC
{D3D12_DESCRIPTOR_HEAP_TYPE Type; //类型UINT NumDescriptors; //描述符的数量D3D12_DESCRIPTOR_HEAP_FLAGS Flags;UINT NodeMask;
}
描述符可以看作GPU资源的句柄(handle)?描述符为GPU解释资源,以及资源将如何使用。描述符堆可看作描述符的数组,描述符堆类型主要有四种:
//常量缓冲区、着色器资源、无序访问资源
D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV,
//采样器资源
D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER,
//渲染目标
D3D12_DESCRIPTOR_HEAP_TYPE_RTV,
//深度\模板缓冲区
D3D12_DESCRIPTOR_HEAP_TYPE_DSV,
1.2 创建资源
填充资源描述结构 D3D12_RESOURCE_DESC ,
struct D3D12_RESOURCE_DESC
{D3D12_RESOURCE_DIMENSION Dimension;//资源种类 D3D12_RESOURCE_DIMENSION_BUFFER,// D3D12_RESOURCE_DIMENSION_TEXTURE2D等UINT64 Alignment;UINT64 Width; //空间大小UINT Height;UINT16 DepthOrArraySize;UINT16 MipLevels;DXGI_FORMAT Format; //格式DXGI_SAMPLE_DESC SampleDesc;D3D12_TEXTURE_LAYOUT Layout; //D3D12_TEXTURE_LAYOUT_ROW_MAJORD3D12_RESOURCE_FLAGS Flags;
}
然后调用 md3dDevice->CreateCommittedResource等函数创建资源,在指定的堆(默认堆、上传堆)中预留空间,使用ComPtr<ID3D12Resource>访问资源。创建函数包括:CreateCommittedResource、CreatePlacedResource、CreateReservedResource。
//根据提供的属性创建一个资源和一个堆,并将资源提交到这个堆中。
md3dDevice->CreateCommittedResource(&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),D3D12_HEAP_FLAG_NONE,&depthStencilDesc,D3D12_RESOURCE_STATE_COMMON,&optClear,IID_PPV_ARGS(mDepthStencilBuffer.GetAddressOf())
);
资源的类型主要有:后台缓冲区、深度模板缓冲区,顶点缓冲区、索引缓冲区,常量缓冲区、着色器资源缓冲区等。 数据种类主要是
D3D12_RESOURCE_DIMENSION_BUFFER = 1,
D3D12_RESOURCE_DIMENSION_TEXTURE1D = 2,
D3D12_RESOURCE_DIMENSION_TEXTURE2D = 3,
D3D12_RESOURCE_DIMENSION_TEXTURE3D = 4
1.3 创建视图(描述符)
填充视图描述结构,不同类型的资源,视图描述结构不同。 描述符的大小是硬件依赖的,需要运行时查询,它用于在描述符堆中偏移,以找到需要的描述符。
mSamplerDescriptorSize = md3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER);
mRtvDescriptorSize = md3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
mDsvDescriptorSize = md3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_DSV);
mCbvSrvUavDescriptorSize = md3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
1.3.1 常量缓冲区视图
填充描述结构D3D12_CONSTANT_BUFFER_VIEW_DESC,
struct D3D12_CONSTANT_BUFFER_VIEW_DESC
{D3D12_GPU_VIRTUAL_ADDRESS BufferLocation;//资源的GPU虚拟地址UINT SizeInBytes; //所指向资源的大小
}
然后创建视图
md3dDevice->CreateConstantBufferView(&cbvDesc, //描述mCbvHeap->GetCPUDescriptorHandleForHeapStart() //描述符的内存地址
);
1.3.2 深度模板缓冲区视图
填充描述结构D3D12_DEPTH_STENCIL_VIEW_DESC,
struct D3D12_DEPTH_STENCIL_VIEW_DESC{DXGI_FORMAT Format;D3D12_DSV_DIMENSION ViewDimension;D3D12_DSV_FLAGS Flags;union {D3D12_TEX1D_DSV Texture1D;D3D12_TEX1D_ARRAY_DSV Texture1DArray;D3D12_TEX2D_DSV Texture2D;D3D12_TEX2D_ARRAY_DSV Texture2DArray;D3D12_TEX2DMS_DSV Texture2DMS;D3D12_TEX2DMS_ARRAY_DSV Texture2DMSArray;} ;
}
然后创建视图:
md3dDevice->CreateDepthStencilView(
mDepthStencilBuffer.Get(), //GPU资源
&dsvDesc, //描述
mDsvHeap->GetCPUDescriptorHandleForHeapStart() //描述符内存地址
);
1.3.3 渲染目标视图
渲染目标资源在创建交换链时创建,一般有2个。
//描述符的内存地址
CD3DX12_CPU_DESCRIPTOR_HANDLE
rtvHeapHandle(mRtvHeap->GetCPUDescriptorHandleForHeapStart()); for (UINT i = 0; i < SwapChainBufferCount; i++)
{ThrowIfFailed(mSwapChain->GetBuffer(i, IID_PPV_ARGS(&mSwapChainBuffer[i])));md3dDevice->CreateRenderTargetView(mSwapChainBuffer[i].Get(), //GPU资源nullptr, rtvHeapHandle);rtvHeapHandle.Offset(1, mRtvDescriptorSize); //描述符堆中的下一个描述符
}
1.3.4 采样器资源
2、交换链
ComPtr<IDXGISwapChain> mSwapChain; 创建交换链时同时也创建了2个渲染目标缓冲器资源。存储了前台和后台缓冲区两种纹理。
void D3DApp::CreateSwapChain()
{// Release the previous swapchain we will be recreating.mSwapChain.Reset();DXGI_SWAP_CHAIN_DESC sd;sd.BufferDesc.Width = mClientWidth;sd.BufferDesc.Height = mClientHeight;sd.BufferDesc.RefreshRate.Numerator = 60;sd.BufferDesc.RefreshRate.Denominator = 1;sd.BufferDesc.Format = mBackBufferFormat;sd.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;sd.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;sd.SampleDesc.Count = m4xMsaaState ? 4 : 1;//质量减1sd.SampleDesc.Quality = m4xMsaaState ? (m4xMsaaQuality - 1) : 0;sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;sd.BufferCount = SwapChainBufferCount;sd.OutputWindow = mhMainWnd;sd.Windowed = true;sd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;// Note: Swap chain uses queue to perform flush.ThrowIfFailed(mdxgiFactory->CreateSwapChain(mCommandQueue.Get(),&sd,mSwapChain.GetAddressOf()));//当前的后备缓冲区编号ComPtr<IDXGISwapChain3> m_swapChain;ThrowIfFailed(mSwapChain.As(&m_swapChain));auto m_frameIndex = m_swapChain->GetCurrentBackBufferIndex();}
3、渲染目标缓冲区描述符堆和视图
//1-1 渲染目标缓冲区的描述符堆
D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc;
rtvHeapDesc.NumDescriptors = SwapChainBufferCount;
rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
rtvHeapDesc.NodeMask = 0;
ThrowIfFailed(md3dDevice->CreateDescriptorHeap(&rtvHeapDesc, IID_PPV_ARGS(mRtvHeap.GetAddressOf())));
//1-2 创建渲染缓冲区的视图,将资源与描述符堆中的描述符进行关联
CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHeapHandle(mRtvHeap->GetCPUDescriptorHandleForHeapStart());
for (UINT i = 0; i < SwapChainBufferCount; i++)
{ThrowIfFailed(mSwapChain->GetBuffer(i, IID_PPV_ARGS(&mSwapChainBuffer[i])));md3dDevice->CreateRenderTargetView(mSwapChainBuffer[i].Get(), nullptr, rtvHeapHandle);rtvHeapHandle.Offset(1, mRtvDescriptorSize);
}
4、深度模板缓冲区
深度缓冲区也是纹理资源,但是存储的不是图像而是特定像素的深度信息。
//2-1 创建深度模板缓冲区资源
D3D12_RESOURCE_DESC depthStencilDesc;
depthStencilDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
depthStencilDesc.Alignment = 0;
depthStencilDesc.Width = mClientWidth;
depthStencilDesc.Height = mClientHeight;
depthStencilDesc.DepthOrArraySize = 1;
depthStencilDesc.MipLevels = 1;// Correction 11/12/2016: SSAO chapter requires an SRV to the depth buffer to read from
// the depth buffer. Therefore, because we need to create two views to the same resource:
// 1. SRV format: DXGI_FORMAT_R24_UNORM_X8_TYPELESS
// 2. DSV Format: DXGI_FORMAT_D24_UNORM_S8_UINT
// we need to create the depth buffer resource with a typeless format.
depthStencilDesc.Format = DXGI_FORMAT_R24G8_TYPELESS;
depthStencilDesc.SampleDesc.Count = m4xMsaaState ? 4 : 1;
depthStencilDesc.SampleDesc.Quality = m4xMsaaState ? (m4xMsaaQuality - 1) : 0;
depthStencilDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
depthStencilDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;D3D12_CLEAR_VALUE optClear;
optClear.Format = mDepthStencilFormat;
optClear.DepthStencil.Depth = 1.0f;
optClear.DepthStencil.Stencil = 0;
//创建资源并提交到默认堆(显存)中
ThrowIfFailed(md3dDevice->CreateCommittedResource(&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),D3D12_HEAP_FLAG_NONE,&depthStencilDesc,D3D12_RESOURCE_STATE_COMMON, //资源的初始状态&optClear,IID_PPV_ARGS(mDepthStencilBuffer.GetAddressOf())));
创建描述符堆和视图
// 2-2 深度模板缓冲区的描述符堆
D3D12_DESCRIPTOR_HEAP_DESC dsvHeapDesc;
dsvHeapDesc.NumDescriptors = 1;
dsvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV;
dsvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
dsvHeapDesc.NodeMask = 0;
ThrowIfFailed(md3dDevice->CreateDescriptorHeap(&dsvHeapDesc, IID_PPV_ARGS(mDsvHeap.GetAddressOf())));
//2-3 创建视图
// Create descriptor to mip level 0 of entire resource using the format of the resource.
D3D12_DEPTH_STENCIL_VIEW_DESC dsvDesc;
dsvDesc.Flags = D3D12_DSV_FLAG_NONE;
dsvDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D;
dsvDesc.Format = mDepthStencilFormat;
dsvDesc.Texture2D.MipSlice = 0;
md3dDevice->CreateDepthStencilView(mDepthStencilBuffer.Get(), &dsvDesc, DepthStencilView());DepthStencilView() 返回深度模板描述符堆中的第一个描述符。
5、常量着色器无序访问缓冲区
创建描述符堆和视图
D3D12_DESCRIPTOR_HEAP_DESC cbvHeapDesc;
cbvHeapDesc.NumDescriptors = 1;
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)));
常量缓冲区,常量的大小必须是 256字节的整数倍,常量缓冲区通常在每帧都会改变,放到上传堆中。常量缓冲区资源是D3D12_RESOURCE_DIMENSION_BUFFER类型。
struct ObjectConstants
{XMFLOAT4X4 WorldViewProj = MathHelper::Identity4x4();
};template<typename T>
class UploadBuffer
{
public:UploadBuffer(ID3D12Device* device, UINT elementCount, bool isConstantBuffer) : mIsConstantBuffer(isConstantBuffer){mElementByteSize = sizeof(T);// Constant buffer elements need to be multiples of 256 bytes.// This is because the hardware can only view constant data // at m*256 byte offsets and of n*256 byte lengths. // typedef struct D3D12_CONSTANT_BUFFER_VIEW_DESC {// UINT64 OffsetInBytes; // multiple of 256// UINT SizeInBytes; // multiple of 256// } D3D12_CONSTANT_BUFFER_VIEW_DESC;if(isConstantBuffer)mElementByteSize = d3dUtil::CalcConstantBufferByteSize(sizeof(T));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)));ThrowIfFailed(mUploadBuffer->Map(0, nullptr, reinterpret_cast<void**>(&mMappedData)));// We do not need to unmap until we are done with the resource. However, we must not write to// the resource while it is in use by the GPU (so we must use synchronization techniques).}UploadBuffer(const UploadBuffer& rhs) = delete;UploadBuffer& operator=(const UploadBuffer& rhs) = delete;~UploadBuffer(){if(mUploadBuffer != nullptr)mUploadBuffer->Unmap(0, nullptr);mMappedData = nullptr;}ID3D12Resource* Resource()const{return mUploadBuffer.Get();}void CopyData(int elementIndex, const T& data){memcpy(&mMappedData[elementIndex*mElementByteSize], &data, sizeof(T));}private:Microsoft::WRL::ComPtr<ID3D12Resource> mUploadBuffer;BYTE* mMappedData = nullptr;UINT mElementByteSize = 0;bool mIsConstantBuffer = false;
};
创建常量缓冲区资源(上传堆中),创建常量缓冲区视图。常量缓冲区视图描述显存中的地址和大小。创建视图需要CPU和GPU访问地址。
void BoxApp::BuildConstantBuffers()
{mObjectCB = std::make_unique<UploadBuffer<ObjectConstants>>(md3dDevice.Get(), 1, true);UINT objCBByteSize = d3dUtil::CalcConstantBufferByteSize(sizeof(ObjectConstants));D3D12_GPU_VIRTUAL_ADDRESS cbAddress = mObjectCB->Resource()->GetGPUVirtualAddress();// Offset to the ith object constant buffer in the buffer.int boxCBufIndex = 0;cbAddress += boxCBufIndex*objCBByteSize;D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc;cbvDesc.BufferLocation = cbAddress;cbvDesc.SizeInBytes = d3dUtil::CalcConstantBufferByteSize(sizeof(ObjectConstants));md3dDevice->CreateConstantBufferView(&cbvDesc,mCbvHeap->GetCPUDescriptorHandleForHeapStart());
}
更新常量缓冲区中的数据
void BoxApp::Update(const GameTimer& gt)
{// Convert Spherical to Cartesian coordinates.float x = mRadius*sinf(mPhi)*cosf(mTheta);float z = mRadius*sinf(mPhi)*sinf(mTheta);float y = mRadius*cosf(mPhi);// Build the view matrix.XMVECTOR pos = XMVectorSet(x, y, z, 1.0f);XMVECTOR target = XMVectorZero();XMVECTOR up = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f);XMMATRIX view = XMMatrixLookAtLH(pos, target, up);XMStoreFloat4x4(&mView, view);XMMATRIX world = XMLoadFloat4x4(&mWorld);XMMATRIX proj = XMLoadFloat4x4(&mProj);XMMATRIX worldViewProj = world*view*proj;// Update the constant buffer with the latest worldViewProj matrix.ObjectConstants objConstants;XMStoreFloat4x4(&objConstants.WorldViewProj, XMMatrixTranspose(worldViewProj));mObjectCB->CopyData(0, objConstants);
}
通常使用两种常量缓冲区,一种记录渲染场景需要的数据(如视图矩阵,投影矩阵等公共数据),一种记录场景中物体需要的数据(如世界变换矩阵)。
6、根签名
根签名描述了着色器程序需要访问资源的描述。绘制命令执行前,绑定到渲染流水线的资源,会被映射到着色器的对应输入寄存器中。 例如顶点和像素着色器需要访问绑定到寄存器b0的常量缓冲区。根签名要与使用它的着色器相兼容。
根签名是一个绑定约定,由应用程序定义,着色器使用它来定位他们需要访问的资源。可以理解为着色器函数的输入参数定义,函数签名。
根签名实际是描述了常量(类似默认参数)、常量缓冲区(CBV)、资源(SRV,纹理)、无序访问缓冲(UAV,随机读写缓冲)、采样器(Sample)等的寄存器(Register)存储规划的一个结构体。同时它还描述了每种资源针对每个阶段Shader的可见性。
根参数
根签名是一个根参数的数组,根参数可以是根常数,根描述符或描述符表。
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;
}
6.1 描述符表
描述符表是在描述符堆中连续的范围。
struct D3D12_ROOT_DESCRIPTOR_TABLE
{UINT NumDescriptorRanges;const D3D12_DESCRIPTOR_RANGE *pDescriptorRanges;
}
下面的根参数的第一个元素是描述符表,包括 2 CBVs, 3 SRVs and 1 UAV。
//cbuffer cbA : register(b0) { … };
//cbuffer cbB : register(b1) { … };//cbuffer cbC : register(t0) { … };
//cbuffer cbD : register(t1) { … };
//cbuffer cbE : register(t2) { … };//cbuffer cbF : register(u0) { … };
//cbuffer cbG : register(u1) { … };----------------------------------------------------------------
D3D12_DESCRIPTOR_RANGE cvbRange[3];cvbRange[0].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_CBV;
cvbRange[0].NumDescriptors = 2;
cvbRange[0].RegisterSpace = 0;
cvbRange[0].BaseShaderRegister = 0;
cvbRange[0].OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND;//0cvbRange[1].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV;
cvbRange[1].NumDescriptors = 3;
cvbRange[1].RegisterSpace = 0;
cvbRange[1].BaseShaderRegister = 0;
cvbRange[1].OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND;//2cvbRange[1].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_UAV;
cvbRange[1].NumDescriptors = 1;
cvbRange[1].RegisterSpace = 0;
cvbRange[1].BaseShaderRegister = 0;
cvbRange[1].OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND;//5D3D12_ROOT_PARAMETER rootParameter[3];
rootParameter[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
rootParameter[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
rootParameter[0].DescriptorTable.NumDescriptorRanges = 3;
rootParameter[0].DescriptorTable.pDescriptorRanges = cvbRange;--------------------------------------------------------------------
ID3D12DescriptorHeap* descriptorHeaps[] = { mCbvHeap.Get() };
mCommandList->SetDescriptorHeaps(_countof(descriptorHeaps), descriptorHeaps);mCommandList->SetGraphicsRootSignature(mRootSignature.Get());mCommandList->SetGraphicsRootDescriptorTable(0,
mCbvHeap->GetGPUDescriptorHandleForHeapStart()
);
应用程序设置描述符表使用: SetGraphicsRootDescriptorTable
void BoxApp::BuildRootSignature()
{// Shader programs typically require resources as input (constant buffers,// textures, samplers). The root signature defines the resources the shader// programs expect. If we think of the shader programs as a function, and// the input resources as function parameters, then the root signature can be// thought of as defining the function signature. // Root parameter can be a table, root descriptor or root constants.CD3DX12_ROOT_PARAMETER slotRootParameter[1];// Create a single descriptor table of CBVs.CD3DX12_DESCRIPTOR_RANGE cbvTable;cbvTable.Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 1, 0);slotRootParameter[0].InitAsDescriptorTable(1, &cbvTable);// A root signature is an array of root parameters.CD3DX12_ROOT_SIGNATURE_DESC rootSigDesc(1, //根参数的数量 slotRootParameter, //根参数数组0, nullptr, D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);// create a root signature with a single slot which points to a descriptor range consisting of a single constant bufferComPtr<ID3DBlob> serializedRootSig = nullptr;ComPtr<ID3DBlob> errorBlob = nullptr;HRESULT hr = D3D12SerializeRootSignature(&rootSigDesc, D3D_ROOT_SIGNATURE_VERSION_1,serializedRootSig.GetAddressOf(), errorBlob.GetAddressOf());if(errorBlob != nullptr){::OutputDebugStringA((char*)errorBlob->GetBufferPointer());}ThrowIfFailed(hr);ThrowIfFailed(md3dDevice->CreateRootSignature(0,serializedRootSig->GetBufferPointer(),serializedRootSig->GetBufferSize(),IID_PPV_ARGS(&mRootSignature)));
}
6.2 根常数
struct D3D12_ROOT_CONSTANTS
{UINT ShaderRegister;UINT RegisterSpace;UINT Num32BitValues;
}
应用程序设置根常数使用 : SetGraphicsRoot32BitConstants
cbuffer cbSettings : register(b2)
{// We cannot have an array entry in a constant buffer that gets// mapped onto root constants, so list each element.int gBlurRadius;// Support up to 11 blur weights.float w0;float w1;float w2;
};rootParameter[2].Constants.RegisterSpace = 0;
rootParameter[2].Constants.ShaderRegister = 0;
rootParameter[2].Constants.Num32BitValues = 4;// Application code: to set the constants to register b0.
// weights中有三个元素
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);
6.3 根描述符
struct D3D12_ROOT_DESCRIPTOR
{UINT ShaderRegister;UINT RegisterSpace;
}
应用程序设置跟描述符使用: SetGraphicsRootConstantBufferView
//cbuffer cbPass : register(b3) { … };rootParameter[1].Descriptor.RegisterSpace = 0;
rootParameter[1].Descriptor.ShaderRegister = 3;cmdList->SetGraphicsRootConstantBufferView(
2, // root parameter index
objCBAddress); // 资源的显存地址 D3D12_GPU_VIRTUAL_ADDRESS
7、输入布局
用于验证顶点格式和顶点着色器输入签名是否一致。
//顶点格式,CPU端
struct Vertex
{XMFLOAT3 Pos;XMFLOAT4 Color;
}; mInputLayout ={{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }};//作色器语言,GPU端
struct VertexIn
{float3 PosL : POSITION;float4 Color : COLOR;
};
8、流水线状态对象
创建流水线状态对象时,需要指定输入补给描述和顶点着色器,D3D会验证两者是否匹配。
验证输入布局描述和顶点着色器输入。输入布局描述一种顶点格式。
验证根签名。根签名可以很复杂,包含很项目。
void BoxApp::BuildPSO()
{D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc;ZeroMemory(&psoDesc, sizeof(D3D12_GRAPHICS_PIPELINE_STATE_DESC));psoDesc.InputLayout = { mInputLayout.data(), (UINT)mInputLayout.size() };psoDesc.pRootSignature = mRootSignature.Get();psoDesc.VS = { reinterpret_cast<BYTE*>(mvsByteCode->GetBufferPointer()), mvsByteCode->GetBufferSize() };psoDesc.PS = { reinterpret_cast<BYTE*>(mpsByteCode->GetBufferPointer()), mpsByteCode->GetBufferSize() };psoDesc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);psoDesc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);psoDesc.DepthStencilState = CD3DX12_DEPTH_STENCIL_DESC(D3D12_DEFAULT);psoDesc.SampleMask = UINT_MAX;psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;psoDesc.NumRenderTargets = 1;psoDesc.RTVFormats[0] = mBackBufferFormat;psoDesc.SampleDesc.Count = m4xMsaaState ? 4 : 1;psoDesc.SampleDesc.Quality = m4xMsaaState ? (m4xMsaaQuality - 1) : 0;psoDesc.DSVFormat = mDepthStencilFormat;ThrowIfFailed(md3dDevice->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&mPSO)));
}
光栅器状态D3D12_RASTERIZER_DESC,定义三角形填充模式,剔除规则等。
9、顶点和索引缓冲区
9.1 创建资源
GPU资源是D3D12_RESOURCE_DIMENSION_BUFFER类型,通常放到默认堆,设置值时需要使用中介的上传缓冲区。
ID3DBlob类型描述的是一段普通的内存块。
void BoxApp::BuildBoxGeometry()
{std::array<Vertex, 8> vertices ={Vertex({ XMFLOAT3(-1.0f, -1.0f, -1.0f), XMFLOAT4(Colors::White) }),Vertex({ XMFLOAT3(-1.0f, +1.0f, -1.0f), XMFLOAT4(Colors::Black) }),Vertex({ XMFLOAT3(+1.0f, +1.0f, -1.0f), XMFLOAT4(Colors::Red) }),Vertex({ XMFLOAT3(+1.0f, -1.0f, -1.0f), XMFLOAT4(Colors::Green) }),Vertex({ XMFLOAT3(-1.0f, -1.0f, +1.0f), XMFLOAT4(Colors::Blue) }),Vertex({ XMFLOAT3(-1.0f, +1.0f, +1.0f), XMFLOAT4(Colors::Yellow) }),Vertex({ XMFLOAT3(+1.0f, +1.0f, +1.0f), XMFLOAT4(Colors::Cyan) }),Vertex({ XMFLOAT3(+1.0f, -1.0f, +1.0f), XMFLOAT4(Colors::Magenta) })};std::array<std::uint16_t, 36> indices ={// front face0, 1, 2,0, 2, 3,// back face4, 6, 5,4, 7, 6,// left face4, 5, 1,4, 1, 0,// right face3, 2, 6,3, 6, 7,// top face1, 5, 6,1, 6, 2,// bottom face4, 0, 3,4, 3, 7};const UINT vbByteSize = (UINT)vertices.size() * sizeof(Vertex);const UINT ibByteSize = (UINT)indices.size() * sizeof(std::uint16_t);mBoxGeo = std::make_unique<MeshGeometry>();mBoxGeo->Name = "boxGeo";ThrowIfFailed(D3DCreateBlob(vbByteSize, &mBoxGeo->VertexBufferCPU));CopyMemory(mBoxGeo->VertexBufferCPU->GetBufferPointer(), vertices.data(), vbByteSize);ThrowIfFailed(D3DCreateBlob(ibByteSize, &mBoxGeo->IndexBufferCPU));CopyMemory(mBoxGeo->IndexBufferCPU->GetBufferPointer(), indices.data(), ibByteSize);mBoxGeo->VertexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(),mCommandList.Get(), vertices.data(), vbByteSize, mBoxGeo->VertexBufferUploader);mBoxGeo->IndexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(),mCommandList.Get(), indices.data(), ibByteSize, mBoxGeo->IndexBufferUploader);mBoxGeo->VertexByteStride = sizeof(Vertex);mBoxGeo->VertexBufferByteSize = vbByteSize;mBoxGeo->IndexFormat = DXGI_FORMAT_R16_UINT;mBoxGeo->IndexBufferByteSize = ibByteSize;SubmeshGeometry submesh;submesh.IndexCount = (UINT)indices.size();submesh.StartIndexLocation = 0;submesh.BaseVertexLocation = 0;mBoxGeo->DrawArgs["box"] = submesh;
}
d3dUtil::CreateDefaultBuffer使用上传堆作为中介来创建一个默认堆,并将顶点和索引数据拷贝到默认堆中。
Microsoft::WRL::ComPtr<ID3D12Resource> d3dUtil::CreateDefaultBuffer(ID3D12Device* device,ID3D12GraphicsCommandList* cmdList,const void* initData,UINT64 byteSize,Microsoft::WRL::ComPtr<ID3D12Resource>& uploadBuffer)
{ComPtr<ID3D12Resource> defaultBuffer;// Create the actual default buffer resource.ThrowIfFailed(device->CreateCommittedResource(&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),D3D12_HEAP_FLAG_NONE,&CD3DX12_RESOURCE_DESC::Buffer(byteSize),D3D12_RESOURCE_STATE_COMMON,nullptr,IID_PPV_ARGS(defaultBuffer.GetAddressOf())));// In order to copy CPU memory data into our default buffer, we need to create// an intermediate upload heap. ThrowIfFailed(device->CreateCommittedResource(&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),D3D12_HEAP_FLAG_NONE,&CD3DX12_RESOURCE_DESC::Buffer(byteSize),D3D12_RESOURCE_STATE_GENERIC_READ,nullptr,IID_PPV_ARGS(uploadBuffer.GetAddressOf())));// Describe the data we want to copy into the default buffer.D3D12_SUBRESOURCE_DATA subResourceData = {};subResourceData.pData = initData;subResourceData.RowPitch = byteSize;subResourceData.SlicePitch = subResourceData.RowPitch;// Schedule to copy the data to the default buffer resource. At a high level, the helper function UpdateSubresources// will copy the CPU memory into the intermediate upload heap. Then, using ID3D12CommandList::CopySubresourceRegion,// the intermediate upload heap data will be copied to mBuffer.cmdList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(defaultBuffer.Get(), D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_COPY_DEST));UpdateSubresources<1>(cmdList, defaultBuffer.Get(), uploadBuffer.Get(), 0, 0, 1, &subResourceData);cmdList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(defaultBuffer.Get(),D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_GENERIC_READ));// Note: uploadBuffer has to be kept alive after the above function calls because// the command list has not been executed yet that performs the actual copy.// The caller can Release the uploadBuffer after it knows the copy has been executed.return defaultBuffer;
}
UpdateSubresources函数来自D3DX12.h,将数据从CPU复制到上传堆,再将上传堆复制到默认堆。
9.2 创建资源视图
无需创建描述符堆。
D3D12_VERTEX_BUFFER_VIEW VertexBufferView()const{D3D12_VERTEX_BUFFER_VIEW vbv;vbv.BufferLocation = VertexBufferGPU->GetGPUVirtualAddress();vbv.StrideInBytes = VertexByteStride;vbv.SizeInBytes = VertexBufferByteSize;return vbv;}D3D12_INDEX_BUFFER_VIEW IndexBufferView()const{D3D12_INDEX_BUFFER_VIEW ibv;ibv.BufferLocation = IndexBufferGPU->GetGPUVirtualAddress();ibv.Format = IndexFormat;ibv.SizeInBytes = IndexBufferByteSize;return ibv;}
10、资源状态转换
资源在创建时指定初始状态,D3D将其转换为另一种状态供GPU使用,转换资源屏障告诉GPU等待状态转换完成再使用资源。
//Transition the resource from its initial state to be used as a depth buffer.
mCommandList->ResourceBarrier(1,
&CD3DX12_RESOURCE_BARRIER::Transition(mDepthStencilBuffer.Get(),
D3D12_RESOURCE_STATE_COMMON,
D3D12_RESOURCE_STATE_DEPTH_WRITE)
);
三、渲染
绘制前,设置相关的状态,包括视口,裁剪矩形,后台缓冲区状态转换到渲染状态,清空后备缓冲区和深度模板缓冲区,绑定渲染目标资源、绑定顶点缓冲区和索引缓冲区资源,设置作色器使用资源的描述符堆、根签名,绑定资源。
void BoxApp::Draw(const GameTimer& gt)
{// Reuse the memory associated with command recording.// We can only reset when the associated command lists have finished execution on the GPU.ThrowIfFailed(mDirectCmdListAlloc->Reset());// A command list can be reset after it has been added to the command queue via ExecuteCommandList.// Reusing the command list reuses memory.// 设置流水线状态对象ThrowIfFailed(mCommandList->Reset(mDirectCmdListAlloc.Get(), mPSO.Get()));mCommandList->RSSetViewports(1, &mScreenViewport);mCommandList->RSSetScissorRects(1, &mScissorRect);// Indicate a state transition on the resource usage.mCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(CurrentBackBuffer(),D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET));// Clear the back buffer and depth buffer.mCommandList->ClearRenderTargetView(CurrentBackBufferView(), Colors::LightSteelBlue, 0, nullptr);mCommandList->ClearDepthStencilView(DepthStencilView(), D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL, 1.0f, 0, 0, nullptr);// Specify the buffers we are going to render to.// 将后备缓冲区资源绑定到渲染流水线的outputMerget阶段mCommandList->OMSetRenderTargets(1, &CurrentBackBufferView(), true, &DepthStencilView());//将顶点、索引缓冲区资源绑定到IA阶段mCommandList->IASetVertexBuffers(0, 1, &mBoxGeo->VertexBufferView());mCommandList->IASetIndexBuffer(&mBoxGeo->IndexBufferView());mCommandList->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);//设置描述符堆ID3D12DescriptorHeap* descriptorHeaps[] = { mCbvHeap.Get() };mCommandList->SetDescriptorHeaps(_countof(descriptorHeaps), descriptorHeaps);//设置根签名mCommandList->SetGraphicsRootSignature(mRootSignature.Get()); //设置使用根描述符表的第几个根描述符,与根签名对应//将描述符表与渲染流水线绑定mCommandList->SetGraphicsRootDescriptorTable(0, mCbvHeap->GetGPUDescriptorHandleForHeapStart());//资源绑定结束,绘制调用mCommandList->DrawIndexedInstanced(mBoxGeo->DrawArgs["box"].IndexCount, 1, 0, 0, 0);// Indicate a state transition on the resource usage.mCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(CurrentBackBuffer(),D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT));// Done recording commands.ThrowIfFailed(mCommandList->Close());// Add the command list to the queue for execution.ID3D12CommandList* cmdsLists[] = { mCommandList.Get() };mCommandQueue->ExecuteCommandLists(_countof(cmdsLists), cmdsLists);// swap the back and front buffersThrowIfFailed(mSwapChain->Present(0, 0));mCurrBackBuffer = (mCurrBackBuffer + 1) % SwapChainBufferCount;// Wait until frame commands are complete. This waiting is inefficient and is// done for simplicity. Later we will show how to organize our rendering code// so we do not have to wait per frame.FlushCommandQueue();
}
渲染流水线的各个阶段:输入装配阶段IA, 顶点着色器阶段VS\外壳着色器阶段HS\镶嵌阶段TS\域着色器阶段DS\几何着色器阶段GS(流输出阶段SO)\光栅化阶段RS\像素着色器阶段PS\输出合并器阶段OM。
参考:
DirectX12(D3D12)基础教程(一)——基础教程-CSDN博客https://blog.csdn.net/u014038143/article/details/82730776