D3D12可编程渲染流水线(一)基本概念

一、初始化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 adapter
    D3D_FEATURE_LEVEL_12_0,    IID_PPV_ARGS(&md3dDevice));

3、创建围栏

围栏用于CPU和GPU之间的同步,它可以强制CPU等待GPU完成所有的渲染指令。ComPtr<ID3D12Fence> mFence;
ID3D12CommandQueue::Signal 方法从 GPU 端设置围栏值,而 ID3D12Fence::Signal 方法则从 CPU 端设置围栏值。
ID3D12CommandQueue::Signal(mFence, n + 1) 实际上只是在命令列表的结尾处添加了一条命令,使围栏mFence值变为n + 1(n初始值为0)。而在GPU处理完命令队列中Signal(fence, n + 1)之前的所有命令以前,CPU端调用 mFence->GetCompletedValue() 方法会一直返回值 n。
ID3D12Fence的三个主要函数:
1、ID3D12Fence::GetCompletedValue 可以获取当前围栏对象的值。
2、ID3D12Fence::Signal 将设定围栏对象为指定的值。(CPU端)。
3、ID3D12Fence::SetEventOnCompletion(UINT64 NewFenceValue,  HANDLE m_fenceEvent)当mFence到达新值NewFenceValue时,将会触发m_fenceEvent事件。

md3dDevice->CreateFence(0/*初始值*/, D3D12_FENCE_FLAG_NONE,  IID_PPV_ARGS(&mFence));


// Signal and increment the fence value.
const UINT64 fence = m_fenceValue; //1

fenceValue = m_fence->GetCompletedValue(); //当前值 0

ThrowIfFailed(m_commandQueue->Signal(m_fence.Get(), fence)); // GPU端设置值 1
m_fenceValue++; 
fenceValue = m_fence->GetCompletedValue(); 
//调用GetCompletedValue时如果GPU没有执行完signal设置的命令,返回 0
// 如果已经执行完,返回1

// Wait until the previous frame is finished.
if (m_fence->GetCompletedValue() < fence)
{
    ThrowIfFailed(m_fence->SetEventOnCompletion(fence, m_fenceEvent));    

    //如果没有执行完,持续等待GPU,直到GPU执行完signal设置的命令,触发m_fenceEvent,结束等待
    WaitForSingleObject(m_fenceEvent, INFINITE);
}

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 allocator
		nullptr,                   // Initial PipelineStateObject
		IID_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中记录命令,其他命令列表可以使用其Allocator
	mCommandList->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_MAJOR
    D3D12_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、交换链

前台缓冲区(front buffer)和后台缓冲区(back buffer)构成了交换链。交换链用于管理用于渲染目标的后备缓冲区和用于显示的前台缓冲区资源。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;
    //质量减1
	sd.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的可见性。

6.1 根参数

根签名是一个根参数的数组,根参数可以是根常数,根描述符或描述符表。

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.2 描述符表

描述符表是在描述符堆中连续的范围。

 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;//0

cvbRange[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;//2

cvbRange[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;//5

D3D12_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 buffer
	ComPtr<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.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

6.4 根常数

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);

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 face
		0, 1, 2,
		0, 2, 3,

		// back face
		4, 6, 5,
		4, 7, 6,

		// left face
		4, 5, 1,
		4, 1, 0,

		// right face
		3, 2, 6,
		3, 6, 7,

		// top face
		1, 5, 6,
		1, 6, 2,

		// bottom face
		4, 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)
);

三、渲染

绘制前,设置相关的状态,包括视口,裁剪矩形,后台缓冲区状态转换到渲染状态,清空后备缓冲区和深度模板缓冲区,绑定渲染目标资源、绑定顶点缓冲区和索引缓冲区资源,设置作色器使用资源的描述符堆、根签名,绑定资源。

常量缓冲区数据格式:

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

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;
};

对应的HLSL数据结构:

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;
};

创建每帧的资源 (包括动态顶点缓冲区,每帧都要修改的顶点数据,如波浪顶点):

PassCB = std::make_unique<UploadBuffer<PassConstants>>(device, passCount, true);

ObjectCB = std::make_unique<UploadBuffer<ObjectConstants>>(device, objectCount, true);

WavesVB = std::make_unique<UploadBuffer<Vertex>>(device, waveVertCount, false);

1、使用根描述符的根签名

1.1  创建根签名

无需创建描述符堆,直接在根参数设置。

CD3DX12_ROOT_PARAMETER slotRootParameter[2];

// Create root CBV.
slotRootParameter[0].InitAsConstantBufferView(0);  //对应b0寄存器
slotRootParameter[1].InitAsConstantBufferView(1);  //对应b1寄存器

// A root signature is an array of root parameters.
CD3DX12_ROOT_SIGNATURE_DESC rootSigDesc(2, slotRootParameter, 0, nullptr, D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);

 1.2 更新数据

//更新当前帧,每个物体的常量数据(世界矩阵)
XMMATRIX world = XMLoadFloat4x4(&e->World);

ObjectConstants objConstants;
XMStoreFloat4x4(&objConstants.World, XMMatrixTranspose(world));

currObjectCB->CopyData(e->ObjCBIndex, objConstants);

 1.3 绘制调用

//设置根签名
mCommandList->SetGraphicsRootSignature(mRootSignature.Get());

// Bind per-pass constant buffer.  We only need to do this once per-pass.
auto passCB = mCurrFrameResource->PassCB->Resource();
//绑定pass常量缓冲区资源到b1寄存器
mCommandList->SetGraphicsRootConstantBufferView(1, passCB->GetGPUVirtualAddress());

绘制每个物体

//绘制单个物体
auto ri = ritems[i];

cmdList->IASetVertexBuffers(0, 1, &ri->Geo->VertexBufferView());
cmdList->IASetIndexBuffer(&ri->Geo->IndexBufferView());
cmdList->IASetPrimitiveTopology(ri->PrimitiveType);

D3D12_GPU_VIRTUAL_ADDRESS objCBAddress = objectCB->GetGPUVirtualAddress();
objCBAddress += ri->ObjCBIndex*objCBByteSize; //该物体常量缓冲区的位置

//绑定物体的常量缓冲区资源到b0
cmdList->SetGraphicsRootConstantBufferView(0, objCBAddress);

cmdList->DrawIndexedInstanced(ri->IndexCount, 1, ri->StartIndexLocation, ri->BaseVertexLocation, 0);

 2、使用描述符堆的根签名

2.1 创建描述符堆

每一帧中的每一个物体均有一个描述符,每趟也有一个描述符

//每一帧场景中物体描述符数量
UINT objCount = (UINT)mOpaqueRitems.size();

// 每趟渲染有一个常量数据,总共3帧
UINT numDescriptors = (objCount+1) * 3;

// 每趟的描述符放在物体描述符后面,偏移量
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)));

 2.2 创建描述符

描述符放在一个堆中,资源在各自的显存中。

void ShapesApp::BuildConstantBufferViews()
{
    UINT objCBByteSize = d3dUtil::CalcConstantBufferByteSize(sizeof(ObjectConstants));

    UINT objCount = (UINT)mOpaqueRitems.size();

    // 先设置物体描述符,对应到常量缓冲区的位置.
    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();

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

            // Offset to the object cbv in the descriptor heap.
            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));

    // 最后三个描述符对应每帧的趟描述符
    for(int frameIndex = 0; frameIndex < gNumFrameResources; ++frameIndex)
    {
        //每帧趟常量缓冲区的起始地址
        auto passCB = mFrameResources[frameIndex]->PassCB->Resource();
        D3D12_GPU_VIRTUAL_ADDRESS cbAddress = passCB->GetGPUVirtualAddress();

        // Offset to the pass cbv in the descriptor heap.
        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);
    }
}

2.3  创建根签名

CD3DX12_DESCRIPTOR_RANGE cbvTable0;
cbvTable0.Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 1, 0);  //对应b0寄存器

CD3DX12_DESCRIPTOR_RANGE cbvTable1;
cbvTable1.Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 1, 1);  //对应b1寄存器

// Root parameter can be a table, root descriptor or root constants.
CD3DX12_ROOT_PARAMETER slotRootParameter[2];

// Create root CBVs.
slotRootParameter[0].InitAsDescriptorTable(1, &cbvTable0);
slotRootParameter[1].InitAsDescriptorTable(1, &cbvTable1);

// A root signature is an array of root parameters.
CD3DX12_ROOT_SIGNATURE_DESC rootSigDesc(2, slotRootParameter, 0, nullptr, 
     D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);

2.4 更新数据

更新当前帧的趟常量缓冲区数据,直接更新上传堆。

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);

2.5 绘制调用

//设置当前绘制调用使用的描述符堆
ID3D12DescriptorHeap* descriptorHeaps[] = { mCbvHeap.Get() };
mCommandList->SetDescriptorHeaps(_countof(descriptorHeaps), descriptorHeaps);

//设置根签名
mCommandList->SetGraphicsRootSignature(mRootSignature.Get());

//当前帧趟常量缓冲区的位置
int passCbvIndex = mPassCbvOffset + mCurrFrameResourceIndex;
auto passCbvHandle = CD3DX12_GPU_DESCRIPTOR_HANDLE(mCbvHeap->GetGPUDescriptorHandleForHeapStart());

passCbvHandle.Offset(passCbvIndex, mCbvSrvUavDescriptorSize);

//通过趟常量缓冲区的描述符将常量缓冲区资源绑定到b1
mCommandList->SetGraphicsRootDescriptorTable(1, passCbvHandle);

绘制每个物体 

//绘制每个物体
auto ri = ritems[i];

cmdList->IASetVertexBuffers(0, 1, &ri->Geo->VertexBufferView());
cmdList->IASetIndexBuffer(&ri->Geo->IndexBufferView());
cmdList->IASetPrimitiveTopology(ri->PrimitiveType);

// Offset to the CBV in the descriptor heap for this object and for this frame resource.
UINT cbvIndex = mCurrFrameResourceIndex*(UINT)mOpaqueRitems.size() + ri->ObjCBIndex;

//当前物体的常量缓冲区描述符的位置
auto cbvHandle = CD3DX12_GPU_DESCRIPTOR_HANDLE(mCbvHeap->GetGPUDescriptorHandleForHeapStart());
cbvHandle.Offset(cbvIndex, mCbvSrvUavDescriptorSize);

//通过物体常量缓冲区描述符将常量缓冲区资源绑定到b0
cmdList->SetGraphicsRootDescriptorTable(0, cbvHandle);

cmdList->DrawIndexedInstanced(ri->IndexCount, 1, ri->StartIndexLocation, 
     ri->BaseVertexLocation, 0);

一个完整的渲染过程 

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 buffers
	ThrowIfFailed(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博客icon-default.png?t=N7T8https://blog.csdn.net/u014038143/article/details/82730776

在DirectX 12渲染超大纹理需要采用纹理切割的技术,将大纹理切分成多个小纹理进行渲染。具体步骤如下: 1. 创建一个用于渲染的纹理对象,可以使用如下代码: ``` D3D12_HEAP_PROPERTIES heapProps = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT); D3D12_RESOURCE_DESC resDesc = CD3DX12_RESOURCE_DESC::Tex2D(format, width, height, 1, mipLevels); resDesc.Flags |= D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET; resDesc.Flags |= D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; ThrowIfFailed(device->CreateCommittedResource( &heapProps, D3D12_HEAP_FLAG_NONE, &resDesc, D3D12_RESOURCE_STATE_RENDER_TARGET, nullptr, IID_PPV_ARGS(&m_RenderTarget))); ``` 其中,format为纹理格式,width和height为纹理大小,mipLevels为mipmap级数。 2. 创建一个渲染目标视图和一个无序访问视图,用于后续的渲染数据拷贝操作。可以使用如下代码: ``` D3D12_RENDER_TARGET_VIEW_DESC rtvDesc = {}; rtvDesc.Format = format; rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D; CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(m_RTVDescriptorHeap->GetCPUDescriptorHandleForHeapStart(), m_frameIndex, m_RTVDescriptorSize); device->CreateRenderTargetView(m_RenderTarget.Get(), &rtvDesc, rtvHandle); D3D12_UNORDERED_ACCESS_VIEW_DESC uavDesc = {}; uavDesc.Format = format; uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D; uavDesc.Texture2D.MipSlice = 0; CD3DX12_CPU_DESCRIPTOR_HANDLE uavHandle(m_UAVDescriptorHeap->GetCPUDescriptorHandleForHeapStart(), m_frameIndex, m_UAVDescriptorSize); device->CreateUnorderedAccessView(m_RenderTarget.Get(), nullptr, &uavDesc, uavHandle); ``` 其中,m_RTVDescriptorHeap和m_UAVDescriptorHeap为渲染目标视图和无序访问视图的描述符堆。 3. 将大纹理切割成多个小纹理,每个小纹理大小为256x256,循环遍历每个小纹理进行渲染。可以使用如下代码: ``` const int nTilesX = (width + 255) / 256; const int nTilesY = (height + 255) / 256; for (int y = 0; y < nTilesY; ++y) { for (int x = 0; x < nTilesX; ++x) { // Set viewport and scissor rect for current tile D3D12_VIEWPORT viewport = { x * 256.0f, y * 256.0f, 256.0f, 256.0f, 0.0f, 1.0f }; D3D12_RECT scissorRect = { x * 256, y * 256, (x + 1) * 256, (y + 1) * 256 }; // Set render target and clear color D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle(m_RTVDescriptorHeap->GetCPUDescriptorHandleForHeapStart(), m_frameIndex, m_RTVDescriptorSize); device->OMSetRenderTargets(1, &rtvHandle, FALSE, nullptr); float clearColor[] = { 0.0f, 0.0f, 0.0f, 1.0f }; commandList->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr); // Draw using current viewport and scissor rect commandList->RSSetViewports(1, &viewport); commandList->RSSetScissorRects(1, &scissorRect); // Draw code goes here } } ``` 其中,m_frameIndex为当前帧的索引,commandList为渲染命令列表,需要在每个小纹理的渲染之前将渲染目标视图和清除颜色设置为当前纹理对应的视图和颜色。 4. 将渲染目标内容拷贝到CPU可访问的纹理中,可以使用如下代码: ``` D3D12_RESOURCE_BARRIER barrier = CD3DX12_RESOURCE_BARRIER::Transition(m_RenderTarget.Get(), D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_COPY_SOURCE); commandList->ResourceBarrier(1, &barrier); CD3DX12_TEXTURE_COPY_LOCATION src(m_RenderTarget.Get(), 0); CD3DX12_TEXTURE_COPY_LOCATION dst(m_StagingTexture.Get(), D3D12CalcSubresource(0, 0, 1)); commandList->CopyTextureRegion(&dst, 0, 0, 0, &src, nullptr); barrier = CD3DX12_RESOURCE_BARRIER::Transition(m_RenderTarget.Get(), D3D12_RESOURCE_STATE_COPY_SOURCE, D3D12_RESOURCE_STATE_RENDER_TARGET); commandList->ResourceBarrier(1, &barrier); ``` 其中,m_StagingTexture为CPU可访问的纹理,需要将渲染目标从渲染状态转换为拷贝状态,然后使用CopyTextureRegion函数将渲染目标内容拷贝到CPU可访问的纹理中,最后再将渲染目标从拷贝状态转换为渲染状态。 通过以上步骤,就可以在DirectX 12渲染超大纹理了。需要注意的是,纹理切割的大小和数量需要根据实际场景进行调整,以确保渲染效果和性能的平衡。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值