【DX12龙书】第四五六章笔记

COM

我们可以把Component Object Model (COM)当作一个接口,或是当作一个C++类使用。COM是会记录被引用的数量的,当我们不再需要一个COM物体的时候,我们需要释放它,因为所有COM接口都继承自IUnknown COM,而后者提供了释放函数。我们需要释放它而不是删除它,COM物体将会再它们的引用数量到0的时候自动释放。

为了帮助管理COM物体的生命周期, the Windows Runtime Library (WRL) provides the Microsoft::WRL::ComPtr class (#include <wrl.h>),可以被认为是COM物体的智能指针。当ComPtr实例离开了域的时候,将会自动为COM物体调用Release方法。这本书中将使用三个主要的ComPtr方法:

1.Get:返回COM接口的指针,一般是要把COM接口当成参数传递给函数时用到

ComPtr<ID3D12RootSignature> mRootSignature;
…
// SetGraphicsRootSignature expects
ID3D12RootSignature* argument.
mCommandList->SetGraphicsRootSignature(mRootSignature.Get());


2. GetAddressOf:返回COM接口的指针的地址。通常用于让一个函数返回COM接口指针。

 

ComPtr<ID3D12CommandAllocator>
mDirectCmdListAlloc;
…ThrowIfFailed(md3dDevice->CreateCommandAllocator(
D3D12_COMMAND_LIST_TYPE_DIRECT,
mDirectCmdListAlloc.GetAddressOf()));


3. Reset: 把ComPtr实例设置为空指针,然后给对应的COM接口的引用数量减少1.

COM接口通常用前缀大写I修饰,比如一个指令集叫做ID3D12GraphicsCommandList

Descriptor

在渲染的过程中,GPU会从资源中读取(比如储存了几何体位置信息的缓存),或是写入(比如back , depth , stencil 这三种buffer。但在我们发布绘制命令之前,我们需要将资源绑定至渲染管线,然后它们才能被Draw call引用。有些资源每次draw call都会改变,所以我们每个draw call都应该更新绑定。然而不能直接绑定。因此使用一个descriptor 物体来引用这些资源,它可以被认为是一个像GPU描述资源的轻量级结构。我们在draw call中指定需要引用的descriptor,也就相当于把资源绑定到了渲染管线。

为什么要用这么间接的方法呢?因为GPU资源本质上一个内存块。资源保持不变所以它们可以被用于渲染管线的不同阶段,比如一个render target,然后用作shader resource(先被采样作后作为着色器的纹理)。但是有时候我们只想这个资源的一部分,以及有时候资源创建时并未指定类型,这样我们直接使用资源就很困难了。

因此就有了descriptor了。除了定义资源数据之外,Descriptor还向GPU描述了这个资源,告诉D3D这个资源将会被如果使用,也就是绑定到渲染管线的哪个阶段,这样我们就能只只使用这个资源的一部分了。如果资源创建时并未指定类型,那么我们创建descriptor时必须指定类型。

view和descriptor是同义词,比如常数缓存view 和常数缓存descriptor是同一个东西。Descritor也有自己的类型,包括:
1. CBV/SRV/UAV descriptors describe constant buffers, shader resources and
unordered access view resources.
2. Sampler descriptors describe sampler resources (used in texturing).
3. RTV descriptors describe render target resources.
4. DSV descriptors describe depth/stencil resources.

而Descriptor heap是一组单一类型的descriptors。我们也可以让多个descriptor只引用同样的资源。例如让它们引用资源的不同子区域。之前提到,资源可以被绑定到渲染管线的不同区域,这样每个区域也都需要一个descritpor。descriptor应该在初始化时间就创建了,因为要运行类型检查。

指令集和指令队列

GPU有一个指令队列。CPU使用指令集把指令通过D3D API提交到指令队列上。要注意一组指令被提交到指令队列上后不会被立即执行,直到GPU准备好。当指令队列为空,则GPU除了等待什么也干不了。如果指令队列太满,CPU也只能慢慢等待。这是不好的情况。


我们需要先填充D3D12_COMMAND_QUEUE_DESC结构体来描述指令队列,然后用 ID3D12Device::CreateCommandQueue来创建指令队列。
In Direct3D 12, the command queue is represented by the ID3D12CommandQueue
interface. It is created by filling out a D3D12_COMMAND_QUEUE_DESC structure
describing the queue and then calling ID3D12Device::CreateCommandQueue.
The way we create our command queue in this book is as follows:

Microsoft::WRL::ComPtr<ID3D12CommandQueue> mCommandQueue;
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)));

IID_PPV_ARGS宏定义为

#define IID_PPV_ARGS(ppType) __uuidof(**(ppType)),
IID_PPV_ARGS_Helper(ppType)

__uuidof(**(ppType))计算了 (**(ppType))类型的COM接口的ID。而在上面的代码中,IID_PPV_ARGS_Helper函数把ppType映射为void**。我们这样做的原因是DX12的很多API都需要一个接口的COM ID。

另一个 ExecuteCommandLists用于给将要进入指令队列的指令集添加指令。指令集将按顺序从第一个开始执行。我们这里使用ID3D12GraphicsCommandList来表示一个指令集,它继承自ID3D12CommandList 接口。

void ID3D12CommandQueue::ExecuteCommandLists(
// Number of commands lists in the arrayUINT Count,
// Pointer to the first element in an array ofcommand lists
ID3D12CommandList *const *ppCommandLists);

The ID3D12GraphicsCommandList有很多添加命令的方法。比如下面这些代码将设置view port,清除render target view,并且发布draw call。

// mCommandList pointer to ID3D12CommandList
mCommandList->RSSetViewports(1, &mScreenViewport);
mCommandList->ClearRenderTargetView(mBackBufferView,
Colors::LightSteelBlue, 0, nullptr);
mCommandList->DrawIndexedInstanced(36, 1, 0, 0, 0);
// Done recording commands.
mCommandList->Close();

当我们放完了指令后,需要调用 ID3D12GraphicsCommandList::Close方法来告诉程序我们已经完成了。与指令集有关的是一个memory backing 类叫做ID3D12CommandAllocator,当指令被指令集记录后,这些指令将被存储在相关联的指令allocator里。A command allocator is created from the ID3D12Device:

HRESULT ID3D12Device::CreateCommandAllocator(
D3D12_COMMAND_LIST_TYPE type,
REFIID riid,
void **ppCommandAllocator);
  1. type:可以与这个指令allocator相关联的指令集。这本书里将包括两个,一个是 D3D12_COMMAND_LIST_TYPE_DIRECT,包含了可以被GPU直接执行的指令。D3D12_COMMAND_LIST_TYPE_BUNDLE,将指令集指定为bundle。Specifies the command list
    represents a bundle. There is some CPU overhead in building a command list, so Direct3D 12 provides an optimization that allows us to record a sequence of commands into a so-called bundle. After a bundle has been recorded, the driver will preprocess the commands to optimize their execution during rendering. Therefore, bundles should be recorded at initialization time. The use of bundles should be thought of as an optimization to use if profiling shows building particular command lists are taking significant time. The Direct3D 12 drawing API is already very efficient, so you should not need to use bundles often, and you should only use them if you can demonstrate a performance gain by them; that is to say, do not use them by default. We do not use bundles in this book; see the DirectX 12 documentation for further details.
  2. riid:ID3D12CommandAllocator 想要创建的COM ID。
  3. ppCommandAllocator:将要创建的指令allocator的指针。

Command lists are also created from the ID3D12Device:
 

HRESULT ID3D12Device::CreateCommandList(UINT nodeMask,
D3D12_COMMAND_LIST_TYPE type,
ID3D12CommandAllocator *pCommandAllocator,
ID3D12PipelineState *pInitialState,
REFIID riid,
void **ppCommandList);

1. nodeMask: 单个GPU则设置为0。多个GPU则需要设置为与这个指令集相关联的物理GPU。
2. type: 指令集的类型
3. pCommandAllocator: 之前创建的指令allocator,类型必须匹配。
4. pInitialState: 指令集的初始管线状态。
5. riid: ID3D12CommandList 接口想要创建的COM ID.
6. ppCommandList: 将要创建的指令集的指针.

渲染管线概述

Input Assemebler:读取几何体的顶点和索引数据,并把它们装配成geometric primitives (e.g., triangles, lines)。

Tessellation :将三角形划分为更多的三角形。优点包括我们可以很方便地设置LOD,近处的多细分一点,远处的少细分一点。以及我们只需要在low poly上计算动画和物理动画,但实际渲染用的是细分后的。这些操作将在GPU中完成。

光栅化阶段

使用D3D12_RASTERIZER_DESC来表达一个光栅化状态组,用于配置渲染管线的光栅化阶段。

typedef struct D3D12_RASTERIZER_DESC {
D3D12_FILL_MODE FillMode; // Default:
D3D12_FILL_SOLID
D3D12_CULL_MODE CullMode; // Default:
D3D12_CULL_BACK
BOOL FrontCounterClockwise; // Default: false
INT DepthBias; // Default: 0
FLOAT DepthBiasClamp; // Default: 0.0fFLOAT SlopeScaledDepthBias; // Default: 0.0f
BOOL DepthClipEnable; // Default: true
BOOL ScissorEnable; // Default: false
BOOL MultisampleEnable; // Default: false
BOOL AntialiasedLineEnable; // Default: false
UINT ForcedSampleCount; // Default: 0
// Default:
D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF
D3D12_CONSERVATIVE_RASTERIZATION_MODE
ConservativeRaster;
} D3D12_RASTERIZER_DESC;
  1. FillMode:D3D12_FILL_WIREFRAME用于线框,D3D12_FILL_SOLID用于固体
  2. CullMode:D3D12_CULL_NONE来禁用culling。D3D12_CULL_BACK 来cull掉后向三角形。D3D12_CULL_FRONT类似。
  3. FrontCounterClockwise:前向三角形的索引顺序是否应该为true?
  4. ScissorEnable

CD3DX12_RASTERIZER_DESC则是D3D12_RASTERIZER_DESC的升级版。In particular, it has a constructor that takes an object of type CD3D12_DEFAULT, which is just a dummy type used for overloading to indicate the rasterizer state members should be initialized to the default values. CD3D12_DEFAULT and D3D12_DEFAULT are defined like so:

struct CD3D12_DEFAULT {};
extern const DECLSPEC_SELECTANY CD3D12_DEFAULT
D3D12_DEFAULT;

D3D12_DEFAULT is used in several of the Direct3D convenience classes.

管线状态物体

我们已经知道了如何创建像素和顶点着色器,如何创建input layout,但要它们弄到一起,我们还需要一个pipeline state object (PSO)
如下:

typedef struct D3D12_GRAPHICS_PIPELINE_STATE_DESC
{ ID3D12RootSignature *pRootSignature;
D3D12_SHADER_BYTECODE VS;
D3D12_SHADER_BYTECODE PS;
D3D12_SHADER_BYTECODE DS;
D3D12_SHADER_BYTECODE HS;
D3D12_SHADER_BYTECODE GS;
D3D12_STREAM_OUTPUT_DESC StreamOutput;
D3D12_BLEND_DESC BlendState;
UINT SampleMask;
D3D12_RASTERIZER_DESC RasterizerState;
D3D12_DEPTH_STENCIL_DESC DepthStencilState;
D3D12_INPUT_LAYOUT_DESC InputLayout;
D3D12_PRIMITIVE_TOPOLOGY_TYPE PrimitiveTopologyType;
UINT NumRenderTargets;
DXGI_FORMAT RTVFormats[8];
DXGI_FORMAT DSVFormat;
DXGI_SAMPLE_DESC SampleDesc;
} D3D12_GRAPHICS_PIPELINE_STATE_DESC;
  1. pRootSignature:绑定至PSO的根签名的指针。必须与着色器兼容
  2. VS:要绑定的顶点着色器。使用D3D12_SHADER_BYTECODE来描述.typedef struct D3D12_SHADER_BYTECODE {
    const BYTE *pShaderBytecode;
    SIZE_T BytecodeLength;
    } D3D12_SHADER_BYTECODE;
  3. PS: The pixel shader to bind.
  4. DS: The domain shader to bind (we will discuss this type of shader in a later chapter).
  5. HS: The hull shader to bind (we will discuss this type of shader in a later chapter).
  6. GS: The geometry shader to bind (we will discuss this type of shader in a later
  7. StreamOutput: Used for an advanced technique called stream-out. We just zeroout this field for now.
  8. BlendState: Specifies the blend state which configures blending. We will discuss this state group in a later chapter; for now, specify the default CD3DX12_BLEND_DESC(D3D12_DEFAULT)
  9. SampleMask:多采样最多能使用32个采样,而Mask类似于开关控制将会使用那种采样。通常使用0xffffffff也就是什么都不用。
  10. DepthStencilState: Specifies the depth/stencil state which configures the depth/stencil test. We will discuss this state group in a later chapter; for now, specify the default CD3DX12_DEPTH_STENCIL_DESC(D3D12_DEFAULT).
  11. InputLayout: An input layout description which is simply an array of D3D12_INPUT_ELEMENT_DESC elements, and the number of elements in the array.
    typedef struct D3D12_INPUT_LAYOUT_DESC
    {
    const D3D12_INPUT_ELEMENT_DESC
    *pInputElementDescs;
    UINT NumElements;
    } D3D12_INPUT_LAYOUT_DESC;
  12. PrimitiveTopologyType: Specifies the primitive topology type.
    typedef enum D3D12_PRIMITIVE_TOPOLOGY_TYPE {
    D3D12_PRIMITIVE_TOPOLOGY_TYPE_UNDEFINED = 0,
    D3D12_PRIMITIVE_TOPOLOGY_TYPE_POINT = 1,
    D3D12_PRIMITIVE_TOPOLOGY_TYPE_LINE = 2,
    D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE = 3,
    D3D12_PRIMITIVE_TOPOLOGY_TYPE_PATCH = 4
    } D3D12_PRIMITIVE_TOPOLOGY_TYPE;
  13. NumRenderTargets: The number of render targets we are using simultaneously.
  14. RTVFormats: The render target formats. This is an array to support writing to multiple render targets simultaneously. This should match the settings of the render target we are using the PSO with.
  15. DSVFormat: The format of the depth/stencil buffer. This should match the settings of the depth/stencil buffer we are using the PSO with.
  16. SampleDesc: Describes the multisample count and quality level. This should match the settings of the render target we are using.

当我们填完D3D12_GRAPHICS_PIPELINE_STATE_DESC实例后,就可以使用ID3D12Device::CreateGraphicsPipelineState来创建渲染管线了。

ComPtr<ID3D12RootSignature> mRootSignature;
std::vector<D3D12_INPUT_ELEMENT_DESC> mInputLayout;
ComPtr<ID3DBlob> mvsByteCode;
ComPtr<ID3DBlob> mpsByteCode;
…
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 = CD3D12_RASTERIZER_DESC(D3D12_DEFAULT);
psoDesc.BlendState = CD3D12_BLEND_DESC(D3D12_DEFAULT);
psoDesc.DepthStencilState = CD3D12_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;
ComPtr<ID3D12PipelineState> mPSO;
md3dDevice->CreateGraphicsPipelineState(&psoDesc,IID_PPV_ARGS(&mPSO)));

Because PSO validation and creation can be time consuming, PSOs should be generated at initialization time. One exception to this might be to create a PSO at runtime on demand the first time it is referenced; then store it in a collection such as a hash table so it can quickly be fetched for future use.

PSO中的状态相互关联。为了防止频繁改变状态所带来的性能下降,DX12采用了延迟编程。直到发布draw call命令时才能直到PSO最终长什么样。

并不是所有的渲染状态都在PSO中,比如Viewport和scissor retangle就与PSO互相独立。

D3D本质上是个状态机,如果不改变的话那么就会一直停留在某个状态。如果你需要用一个PSO绘制一些物体,而用另一个PSO绘制另一个物体,那么代码就看起来像下面这样。

// Reset specifies initial PSO.
mCommandList->Reset(mDirectCmdListAlloc.Get(),
mPSO1.Get())
/* …draw objects using PSO 1… */
// Change PSO
mCommandList->SetPipelineState(mPSO2.Get());
/* …draw objects using PSO 2… */
// Change PSO
mCommandList->SetPipelineState(mPSO3.Get());
/* …draw objects using PSO 3… */

当PSO绑定至指令集后,只有重写PSO或重设指令集后才能改变PSO。但尽量不要改变PSO,在同一时间绘制使用用一个PSO的物体。而且不要每次Draw caldl都改变一些PSO

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值