Chapter 6

6.1 顶点与输入布局

Direct3D 的顶点可以包含除空间坐标外的其他数据.如:

struct Vertex1
{
    XMFLOAT3 Pos;
    XMFLOAT4 Color;
};
struct Vertex2
{
    XMFLOAT3 Pos;
    XMFLOAT3 Normal;
    XMFLOAT2 Tex0;
    XMFLOAT2 Tex1;
};

一旦定义了一个顶点结构体,我们需要给 Direct3D 提供一个我们的顶点结构体的说明因此它直到每个成分是做什么的.该说明以用 D3D12_INPUT_LAYOUT_DESC 结构体表示的 input layout description 的形式提供给 Direct3D .

typedef struct D3D12_INPUT_LAYOUT_DESC
{
    const D3D12_INPUT_ELEMENT_DESC *pInputElementDescs;//数组
    UINT NumElements;//个数
} D3D12_INPUT_LAYOUT_DESC;

输入布局说明仅是一个 D3D12_INPUT_ELEMENT_DESC 元素的数组 和 数组里的元素个数.

D3D12INPUT_ELEMENT_DESC 数组里的每个元素说明和对应每个顶点结构体中的成分.所以如果顶点结构体有两个成分,对应的 D3D12INPUT_ELEMENT_DESC 数组则有2个元素. D3D12_INPUT_ELEMENT_DESC 结构体定义如下:

typedef struct D3D12_INPUT_ELEMENT_DESC
{
    LPCSTR SemanticName;
    UINT SemanticIndex;
    DXGI_FORMAT Format;
    UINT InputSlot;
    UINT AlignedByteOffset;
    D3D12_INPUT_CLASSIFICATION InputSlotClass;
    UINT InstanceDataStepRate;
} D3D12_INPUT_ELEMENT_DESC;
  1. SemanticName: 用于关联元素的字符串,它可以使任意合法的变量名.语义用于映射顶点结构体的元素带顶点着色器输入签名的元素.参考 Figure 6.1.

  2. SemanticIndex: 用于连接到语义的索引.目的参考 Figure 6.1.比如,一个顶点着色器可能有多个纹理坐标集合(set of texture coordinates),所以与其介绍一个新的语义名,不如我们值连接一个索引到末端以区分两个纹理坐标集合.一个在着色器代码里没有指定索引的语义默认为索引0,比如 POSITION 等价于 POSITION0.

  3. Format: DXGI_FORMAT 枚举类型的一个成员,指定这个顶点元素的格式(数据类型).

    常见的栗子:

    DXGI_FORMAT_R32_FLOAT

    DXGI_FORMAT_R32G32_FLOAT

    DXGI_FORMAT_R32G32B32_FLOAT

    DXGI_FORMAT_R32G32B32A32_FLOAT

    DXGI_FORMAT_R8_UINT

    DXGI_FORMAT_R16G16_SINT

    DXGI_FORMAT_R32G32B32_UINT

    DXGI_FORMAT_R8G8B8A8_SINT

    DXGI_FORMAT_R8G8B8A8_UINT

  4. InputSlot: 指定该元素来自的输入座.Direct3D 支持16个输入座(索引为0-15),从中你可以传入顶点数据.目前我们只用输入座0(也就是说所有的顶点元素来自同一个输入座).

  5. AlignedByteOffset: 从 C++ 指定的输入座的顶点结构体到顶点元素的字节上的位移(差点看不懂自己在写什么).比如,在下面的顶点结构体中,元素 Pos 有一个 0-byte 位移(因为它与顶点结构体的开端一致),元素 Normal 有 12-byte 位移因为我们必须跳过 Pos 的字节来得到 Normal 的开端,元素 Tex0 有 24-byte 位移因为我们必须跳过前面的 Pos 和 Normal 的字节.很容易推到,元素 Tex1 有 32-byte 位移.

    struct Vertex2
    {
        XMFLOAT3 Pos; // 0-byte offset
        XMFLOAT3 Normal; // 12-byte offset
        XMFLOAT2 Tex0; // 24-byte offset
        XMFLOAT2 Tex1; // 32-byte offset
    };

  6. InputSlotClass: 暂时指定 D3D12_INPUT_PER_VERTEX_DATA.另外可选的参数用于高级实例.

  7. InstanceDataStepRate: 暂时指定为 0.别的值只用于高级应用.


6.2 顶点缓冲

为了令 GPU 访问顶点数组,它们需要被放置于名为缓冲的 GPU 资源(ID3D12Resource).缓冲没有 multidimensional,mipmaps,filters,或者 multisampling support.我们将会在需要提供给 GPU 一个元素的数组比如顶点时使用缓冲.

像以往那样,通过填写 D3D12_RESOURCE_DESC 创建 ID3D12Resource 类,然后调用 ID3D12Device::CreateCommittedResource 方法.Direct3D 12 提供一个 C++ 封装类派生自 D3D12RESOURCE_DESC 和提供方便的构造函数和方法的 CD3DX12_RESOURCE_DESC.特别地,它提供以下方法简化可说明一个缓冲的 D3D12_RESOURCE_DESC 的构造.

static inline CD3DX12_RESOURCE_DESC Buffer(
    UINT64 width,
    D3D12_RESOURCE_FLAGS flags = D3D12_RESOURCE_FLAG_NONE,
    UINT64 alignment = 0 )
{
    return CD3DX12_RESOURCE_DESC(
        D3D12_RESOURCE_DIMENSION_BUFFER,
        alignment, width, 1, 1, 1,
        DXGI_FORMAT_UNKNOWN, 1, 0,
        D3D12_TEXTURE_LAYOUT_ROW_MAJOR, flags );
}

对于缓冲,其宽指的是缓冲中的字节数.比如,一个缓冲存储着 64 floats,则宽为 64*sizeof(float).

CD3DX12_RESOURCE_DESC 类也提供构造一个 D3D12_RESOURCE_DESC 的便利方法,它通过说明纹理资源和查询资源的信息:

  1. CD3DX12_RESOURCE_DESC::Tex1D
  2. CD3DX12_RESOURCE_DESC::Tex2D
  3. CD3DX12_RESOURCE_DESC::Tex3D

回想第四章的同样由 ID3D12Resource 对象表示的深度/模版缓冲,它是一个2D纹理.所有在 Direct3D 12 的资源都有 ID3D12Resource 接口来表示.对比Direct3D 11,它有不同的接口对应不同的资源比如 ID3D11Buffer 和 ID3D11Texture2D.资源的类型由 D3D12RESOURCE_DESC::D3D12_RESOURCE_DIMENSION 域来表示.比如,缓冲有尺寸 D3D12_RESOURCE_DIMENSION_BUFFER 和2D纹理有尺寸 D3D12_RESOURCE_DIMENSION_TEXTURE2D.

对于静态几何,我们把顶点缓冲放入默认堆(D3D12_HEAP_TYPE_DEFAULT)已达到最佳性能,这是大多数情况.顶点缓冲初始化后,只有 GPU 需要读取顶点缓冲来描绘几何,所以默认堆够用.然而,如果 CPU 不能在默认堆写入顶点缓冲,我们该怎么初始化顶点缓冲?

除了创建实在的顶点缓冲资源,我们需要用 D3D12_HEAP_TYPE_UPLOAD 堆类型创建一个中间的上传缓冲(upload buffer)资源.创建完后,我们从系统内存复制定点数据到上传缓冲(§4.3.8),然后我们从上传缓冲复制顶点数据到实在的顶点缓冲.

因为一个中间的上传缓冲被要求初始化默认堆的数据(带有 D3D12_HEAP_TYPE_DEFAULT 堆类型的缓冲),我们在 d3dUtil.h/.cpp 建立以下多功能函数来避免每次需要一个默认缓冲时重复实现.

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

D3D12_SUBRESOURCE_DATA 结构体定义如下:

typedef struct D3D12_SUBRESOURCE_DATA
{
    const void *pData;
    LONG_PTR RowPitch;
    LONG_PTR SlicePitch;
} D3D12_SUBRESOURCE_DATA;
  1. pData: 指向包含初始化缓冲数据的系统内存的指针.如果缓冲可以存储 n 个顶点,那么系统数组必须包换至少 n 个顶点 因此 整个缓冲才得以初始化.

  2. RowPitch: 对于缓冲,指的是我们将要拷贝字节的数据的大小.

  3. SlicePitch: 对于缓冲,指的是我们将要拷贝字节的数据的大小.(同上)

接下来的代码展示该类如何被用于创建一个存储8个(立方的)顶点的默认缓冲,其中每个顶点有不同的颜色:

Vertex vertices[] =
{
    { XMFLOAT3(-1.0f, -1.0f, -1.0f), XMFLOAT4(Colors::White) },
    { XMFLOAT3(-1.0f, +1.0f, -1.0f), XMFLOAT4(Colors::Black) },
    { XMFLOAT3(+1.0f, +1.0f, -1.0f), XMFLOAT4(Colors::Red) },
    { XMFLOAT3(+1.0f, -1.0f, -1.0f), XMFLOAT4(Colors::Green) },
    { XMFLOAT3(-1.0f, -1.0f, +1.0f), XMFLOAT4(Colors::Blue) },
    { XMFLOAT3(-1.0f, +1.0f, +1.0f), XMFLOAT4(Colors::Yellow) },
    { XMFLOAT3(+1.0f, +1.0f, +1.0f), XMFLOAT4(Colors::Cyan) },
    { XMFLOAT3(+1.0f, -1.0f, +1.0f), XMFLOAT4(Colors::Magenta) }
};
const UINT64 vbByteSize = 8 * sizeof(Vertex);
ComPtr<ID3D12Resource> VertexBufferGPU = nullptr;
ComPtr<ID3D12Resource> VertexBufferUploader = nullptr;
VertexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(),
    mCommandList.Get(), vertices, vbByteSize,
    VertexBufferUploader);

其中 Vertex 类型和颜色定义如下:

struct Vertex
{
    XMFLOAT3 Pos;
    XMFLOAT4 Color;
};

为了绑定一个顶点缓冲道管线,我们需要创建一个顶点缓冲视图到顶点缓冲资源(create a vertex buffer view to the vertex buffer resource).不像 RTV,我们不需要一个对于顶点缓冲视图的说明符堆.顶点缓冲视图由 D3D12_VERTEX_BUFFER_VIEW_DESC 结构体表示:

typedef struct D3D12_VERTEX_BUFFER_VIEW
{
    D3D12_GPU_VIRTUAL_ADDRESS BufferLocation;
    UINT SizeInBytes;
    UINT StrideInBytes;
} D3D12_VERTEX_BUFFER_VIEW;
  1. BufferLocation: 我们想要创建一个视图到达的顶点缓冲资源的虚拟地址.可以使用 ID3D12Resource::GetGPUVirtualAddress 方法来获得.
  2. SizeInBytes: The number of bytes to view in the vertex buffer starting from BufferLocation.
  3. StrideInBytes: 每个顶点元素的字节大小.

当我们创建好顶点缓冲后我们必须创建一个视图给它,我们可以绑定它到一个管线的输入座(input slot)以传入顶点到管线的输入组装阶段.这由以下方法完成:

void ID3D12GraphicsCommandList::IASetVertexBuffers(
UINT StartSlot,
UINT NumBuffers,
const D3D12_VERTEX_BUFFER_VIEW *pViews);
  1. StartSlot: The input slot to start binding vertex buffers to. There are 16 input slots indexed from 0-15.
  2. NumBuffers: The number of vertex buffers we are binding to the input slots. If the start slot has index k and we are binding n buffers, then we are binding buffers to input slots I k , I k +1,…,I k +n-1.
  3. pViews: Pointer to the first element of an array of vertex buffers views.

下面是一个调用示范:

D3D12_VERTEX_BUFFER_VIEW vbv;
vbv.BufferLocation = VertexBufferGPU-
>GetGPUVirtualAddress();
vbv.StrideInBytes = sizeof(Vertex);
vbv.SizeInBytes = 8 * sizeof(Vertex);

D3D12_VERTEX_BUFFER_VIEW vertexBuffers[1] = { vbv };
mCommandList->IASetVertexBuffers(0, 1, vertexBuffers);

IASetVertexBuffers 方法可能稍显复杂,因为它支持设置一个顶点缓冲的数组来使输入座多样化.但是,我们只用一个输入座.课后作业里会有使用两个输入座的习题.

一个顶点缓冲将会一直绑定到一个输入座直到你改变它.所以如果你想用多于一个输入座的话,可以这样结构:

ID3D12Resource* mVB1; // stores vertices of type Vertex1
ID3D12Resource* mVB2; // stores vertices of type Vertex2
D3D12_VERTEX_BUFFER_VIEW_DESC mVBView1; // view to mVB1
D3D12_VERTEX_BUFFER_VIEW_DESC mVBView2; // view to mVB2
/*…Create the vertex buffers and views…*/
mCommandList->IASetVertexBuffers(0, 1, &VBView1);
/* …draw objects using vertex buffer 1… */
mCommandList->IASetVertexBuffers(0, 1, &mVBView2);
/* …draw objects using vertex buffer 2… */

设置一个顶点缓冲到一个输入座并不描绘它们,它只令顶点做好被传入管线的准备,最后一步来实现描绘顶点的步骤是使用 ID3D12GraphicsCommandList::DrawInstanced 方法:

void ID3D12CommandList::DrawInstanced(
    UINT VertexCountPerInstance,
    UINT InstanceCount,
    UINT StartVertexLocation,
    UINT StartInstanceLocation);
  1. VertexCountPerInstance:(每个实例)将要描绘的顶点的数目.
  2. InstanceCount:用于高级应用,现在我们设置为1(只描绘一个实例).
  3. StartVertexLocation:指定在将要描绘的顶点缓冲的第一个顶点的索引(0开始数起).
  4. StartInstanceLocation:设为0.高级应用.

6.3 索引和索引缓冲

与顶点类似,为了让 GPU 访问索引数组,它们需要被放置在 GPU 资源的缓冲中.我们把一个用来存储索引的缓冲称为索引缓冲.因为我们的 d3dUtil::CreateDefaultBuffer 函数在经由 void** 的通用数据中起作用,我们可以使用相同的方法来创建索引缓冲(或者其它任意默认缓冲).

为了绑定索引缓冲到管线,我们需要创建一个索引缓冲视图给索引缓冲资源.和顶点缓冲的情况一样,我们并不需要一个索引缓冲视图的说明符堆.一个索引缓冲视图可以由 D3D12_INDEX_BUFFER_VIEW 结构体表示:

typedef struct D3D12_INDEX_BUFFER_VIEW
{
    D3D12_GPU_VIRTUAL_ADDRESS BufferLocation;
    UINT SizeInBytes;
    DXGI_FORMAT Format;
} D3D12_INDEX_BUFFER_VIEW;
  1. BufferLocation: 我们想要创建一个视图指向的顶端缓冲资源的虚拟地址.我们可以使用ID3D12Resource::GetGPUVirtualAddress 来获得它.
  2. SizeInBytes: The number of bytes to view in the index buffer starting from BufferLocation.
  3. Format:索引的格式,对于 16-bit 索引,必须 DXGI_FORMAT_R16_UINT,而 32-bit 则 DXGI_FORMAT_R32UINT.你应该使用 16-bit 索引来减少内存和带宽,只在需要 32-bit 范围时使用 32-bit.

和顶点缓冲和其他 Direct3D资源的情况一样,在我们可以使用它之前,我们需要绑定它到管线.一个索引缓冲通过 ID3D12CommandList::SetIndexBuffer 方法来绑定到输入组装阶段.接下来的代码展示如何创建一个定义立方体的三角形的索引缓冲,创建一个对于它的视图,绑定它到管线:

std::uint16_t 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 ibByteSize = 36 * sizeof(std::uint16_t);
ComPtr<ID3D12Resource> IndexBufferGPU = nullptr;
ComPtr<ID3D12Resource> IndexBufferUploader = nullptr;
IndexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(),
mCommandList.Get(), indices), ibByteSize, IndexBufferUploader);
D3D12_INDEX_BUFFER_VIEW ibv;
ibv.BufferLocation = IndexBufferGPU->GetGPUVirtualAddress();
ibv.Format = DXGI_FORMAT_R16_UINT;
ibv.SizeInBytes = ibByteSize;
mCommandList->IASetIndexBuffer(&ibv);

最后,当使用索引的时候,我们必须使用 ID3D12GraphicsCommandList::DrawIndexedInstanced 方法,而不是 DrawInstanced:

void ID3D12GraphicsCommandList::DrawIndexedInstanced(
    UINT IndexCountPerInstance,
    UINT InstanceCount,
    UINT StartIndexLocation,
    INT BaseVertexLocation,
    UINT StartInstanceLocation);
  1. IndexCountPerInstance: The number of indices to draw (per instance).
  2. InstanceCount: Used for an advanced technique called instancing; for now, set this to 1 as we only draw one instance.
  3. StartIndexLocation: Index to an element in the index buffer that marks the starting point from which to begin reading indices.
  4. BaseVertexLocation: An integer value to be added to the indices used in this draw call before the vertices are fetched.
  5. StartInstanceLocation: Used for an advanced technique called instancing; for now, set this to 0.


6.4 以顶点着色器为例

下面是一个简单的顶点着色器的实现:

cbuffer cbPerObject : register(b0)
{
    float4x4 gWorldViewProj;
};
void VS(float3 iPosL : POSITION,
    float4 iColor : COLOR,
    out float4 oPosH : SV_POSITION,
    out float4 oColor : COLOR)
{
// Transform to homogeneous clip space.
    oPosH = mul(float4(iPosL, 1.0f), gWorldViewProj);
// Just pass vertex color into the pixel shader.
    oColor = iColor;
}

着色器是使用 HLSL语言来编写旳,它长得和 C++ 挺像.我们教授 HLSL 和编程着色器的方法就以示例为基础.着色器的格式为 .hlsl.

顶点着色器的函数名为 VS.注意你可以给定顶点着色器任意合法的函数名.顶点着色器有四个参数,前两个是输入参数,后两个是输出参数(由关键字 out 指出).HLSL 没有引用或指针.在 HLSL 中,函数总是内联的.前两个输入参数组成了顶点着色器的输入签名(input signature)和对应了我们定制的正用于描绘的顶点着色器的数据成员.参数的语义":POSITION"和":COLOR"用于映射在顶点结构体的元素到顶点着色器的输出参数.参见 Figure 6.4

输出参数同样有附带的语义(":SV_POSITION"和":COLOR").这是用于映射顶点着色器的输出到相应的下一阶段的输入(集合着色器或像素着色器).注意 SV_POSITION 语义是特殊的(SV表示系统值system value)用于表示在齐次裁剪空间里控制着顶点位置的顶点着色器输出元素.我们必须附着 SV_POSITION 语义到位置输出,因为 GPU 需要直到该值(涉及到其他的属性,比如 clipping, depth testing 和 rasterization)非系统值的输出参数的语义名称可以是任意合法语义名称.

第一行通过乘以 4 x 4 的矩阵 gWorldViewProj 来实现从本地空间转换顶点位置到齐次裁剪空间:

// Transform to homogeneous clip space.
oPosH = mul(float4(iPosL, 1.0f), gWorldViewProj);

构造函数语法 float4(iPosL, 1.0f) 构造了一个4D向量,它等价于 float4(iPosL.x, iPosL.y, iPosL.z, 1.0f).因为我们知道那些是点而非向量的顶点的位置,我们放置一个 1 在第四个元素(w = 1).float2 和 float3类型分别表示2D和3D向量.矩阵变量 gWorldViewProj 存在于常量缓冲.内置函数 mul 是用于向量-矩阵乘法.顺便说一下,mul 函数为不同大小的矩阵乘法重载.比如,你可以用于相乘两个 4 x 4 矩阵,两个 3 x 3 矩阵,或者一个 1 x 3向量和一个 3 x 3矩阵.在渲染体里的最后一行指示赋值输入颜色到输出参数因此颜色会被传入到管线的下一阶段:

oColor = iColor;

我们可以使用返回类型和输入签名的结构体等价地重写上面的顶点着色器(相对于一场串参数列表):

cbuffer cbPerObject : register(b0)
{
    float4x4 gWorldViewProj;
};
struct VertexIn
{
    float3 PosL : POSITION;
    float4 Color : COLOR;
};
    struct VertexOut
{
    float4 PosH : SV_POSITION;
    float4 Color : COLOR;
};
    VertexOut VS(VertexIn vin)
{
    VertexOut vout;
// Transform to homogeneous clip space.
    vout.PosH = mul(float4(vin.PosL, 1.0f),gWorldViewProj);
// Just pass vertex color into the pixel shader.
    vout.Color = vin.Color;
    return vout;
}

如果这里没有几何着色器,那么顶点着色器必须输出在齐次裁剪空间的顶点位置(SV_POSITION),因为齐次裁剪空间是当顶点离开顶点着色器时硬件所期望顶点存在的地方(没有几何着色器的情况下),如果有的话,那么输出到齐次裁剪空间就变成几何着色器的任务.

顶点着色器(或几何着色器)并不进行透视变换(perspective divide),它只进行投影矩阵(projection matrix)部分,透视变换将会在后面由硬件进行.


6.4.1 输入布局说明和输入签名连接(Input Layout Description and Input Signature Linking)

从 Figure 6.4 注意到正被传入到管线的顶点的不同属性间存在连接(linking),这由输入布局说明定义.如果你传入没有提供所有顶点渲染器期待的顶点,这就会导致错误.比如,下面的顶点着色器输入签名和顶点数据是不兼容的:

//––––—
// C++ app code
//––––—
struct Vertex
{
    XMFLOAT3 Pos;
    XMFLOAT4 Color;
};
D3D12_INPUT_ELEMENT_DESC desc[] =
{
    {“POSITION”, 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0,
        D3D12_INPUT_PER_VERTEX_DATA, 0},
    {“COLOR”, 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12,
        D3D12_INPUT_PER_VERTEX_DATA, 0}
};
//––––—
// Vertex shader
//––––—
struct VertexIn
{
    float3 PosL : POSITION;
    float4 Color : COLOR;
    float3 Normal : NORMAL;//注意
};
struct VertexOut
{
    float4 PosH : SV_POSITION;
    float4 Color : COLOR;
};
VertexOut VS(VertexIn vin) { … }

当我们创建一个 ID3D12PipelineState 对象时,我们必须指定输入布局说明和顶点着色器.Direct3D 会接下来确认输入布局说明和顶点着色器的兼容性.

顶点数据和输入签名不需要十分匹配.对于定点数据,它需要的是提供所有顶点着色器期待的数据.因此,对于顶点数据要说,提供顶点着色器不使用的额外数据也是允许的.以下是兼容的栗子:

//––––—
// C++ app code
//––––—
struct Vertex
{
    XMFLOAT3 Pos;
    XMFLOAT4 Color;
    XMFLOAT3 Normal;
};
D3D12_INPUT_ELEMENT_DESC desc[] =
{
    {“POSITION”, 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0,
        D3D12_INPUT_PER_VERTEX_DATA, 0},
    {“COLOR”, 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12,
        D3D12_INPUT_PER_VERTEX_DATA, 0},
    { “NORMAL”, 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 28,
        D3D12_INPUT_PER_VERTEX_DATA, 0 }
};
//––––—
// Vertex shader
//––––—
struct VertexIn
{
    float3 PosL : POSITION;
    float4 Color : COLOR;
};
struct VertexOut
{
    float4 PosH : SV_POSITION;
    float4 Color : COLOR;
};
VertexOut VS(VertexIn vin) { … }

现在考虑这种情况,顶点结构体和输入签名已经匹配了顶点元素,但是对于颜色属性类型却有所不同:

//––––—
// C++ app code
//––––—
struct Vertex
{
XMFLOAT3 Pos;
XMFLOAT4 Color;
};
D3D12_INPUT_ELEMENT_DESC desc[] =
{
{“POSITION”, 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0,
D3D12_INPUT_PER_VERTEX_DATA, 0},
 {“COLOR”, 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12,
D3D12_INPUT_PER_VERTEX_DATA, 0}
};
//––––—
// Vertex shader
//––––—
struct VertexIn
{
float3 PosL : POSITION;
int4 Color : COLOR;
};
struct VertexOut
{
float4 PosH : SV_POSITION;
float4 Color : COLOR;
};
VertexOut VS(VertexIn vin) { … }

这是合法的,因为 Direct3D 允许在输入寄存器(input registers)里的比特被重新解释.然而,VC++ 的调试输出窗口却给出了警告:

D3D12 WARNING: ID3D11Device::CreateInputLayout: The provided input signature expects to read an element with SemanticName/Index: ‘COLOR’/0 and component(s) of the type ‘int32’. However, the matching entry in the Input Layout declaration, element[1], specifies mismatched format: ‘R32G32B32A32_FLOAT’. This is not an error, since behavior is well defined: The element format determines what data conversion algorithm gets applied before it shows up in a shader register. Independently, the shader input signature defines how the shader will interpret the data that has been placed in its input registers, with no change in the bits stored. It is valid for the application to reinterpret data as a different type once it is in the vertex shader, so this warning is issued just in case reinterpretation was not intended by the author.


6.5 以像素着色器为例


6.6 常量缓冲

6.6.1 创建常量缓冲

常量缓冲是一种 GPU 资源(ID3D12Resource),其中它的数据内容可以在着色器程序中被引用.纹理和其他的缓冲资源类型也可以在着色器程序中被引用.比如以顶点着色器为例:

cbuffer cbPerObject : register(b0)
{
    float4x4 gWorldViewProj;
};

它提到了一个命名为 cbPerObjectcbuffer 对象(常量缓冲).在这个栗子中,常量缓冲储存着一个 4 x 4 矩阵(gWorldViewProj),它表示.....在 HLSL 中,一个 4 x 4 矩阵是由内置类型 float4x4 来声明的.同理也有 float3x4float2x2 等.

不像顶点缓冲和索引缓冲,常量缓冲经常每一帧就更新一次(CPU 处理).举个例子,如果每一帧摄影机都在动,那么在每一帧常量缓冲都需要更新新的 view 矩阵.因此,我们在上传堆而不是默认堆创建常量缓冲所以我们才可以从 CPU 那更新内容.

常量缓冲同样由特别的硬件需要,它们的大小必须是最小硬件分配大小(256 bytes)的倍数.

通常我们需要乘同样类型的常量缓冲.比如,上面的常量缓冲 cbPerObject 存储着每个对象都不同的常量,所以如果我们有 n 个对象,我们就需要 n 个该类型的常量缓冲.以下代码展示如何创建一个存储 NumElements 许多常量缓冲的缓冲:

struct ObjectConstants
{
    DirectX::XMFLOAT4X4 WorldViewProj = MathHelper::Identity4x4();
};
UINT elementByteSize =
    d3dUtil::CalcConstantBufferByteSize(sizeof(ObjectConstants));
ComPtr<ID3D12Resource> mUploadCBuffer;
device->CreateCommittedResource(
    &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
    D3D12_HEAP_FLAG_NONE,
    &CD3DX12_RESOURCE_DESC::Buffer(mElementByteSize *NumElements),
    D3D12_RESOURCE_STATE_GENERIC_READ,
    nullptr,
    IID_PPV_ARGS(&mUploadCBuffer));

我们可以把 mUploadCBuffer 当作存储一个 ObjectConstants 类型的常量缓冲的数组(填充以达到一个 256 bytes 的倍数).当描绘一个对象时,我们只需要绑定一个常量缓冲视图(CBV)到一个存储着该对象的常量的缓冲的子区域.注意,我们将经常称呼 mUploadCBuffer 为一个常量缓冲因为它存储着一个常量缓冲的数组.

utility函数 d3dUtil::CalcConstantBufferByteSize 使用算法来使缓冲的比特大小达到一个 256bytes 的倍数.

UINT d3dUtil::CalcConstantBufferByteSize(UINT byteSize)
{
// Constant buffers must be a multiple of the minimum hardware
// allocation size (usually 256 bytes). So round up to nearest
// multiple of 256. We do this by adding 255 and then masking off
// the lower 2 bytes which store all bits < 256.
// Example: Suppose byteSize = 300.
// (300 + 255) & ˜255
// 555 & ˜255
// 0x022B & ˜0x00ff
// 0x022B & 0xff00
// 0x0200
// 512
    return (byteSize + 255) & ˜255;
}

即使我们分配了256倍数的常量数据,其实在 HLSL 结构中是不必要显式填充相应的常量数据,因为它是隐式完成的:

// Implicitly padded to 256 bytes.
cbuffer cbPerObject : register(b0)
{
    float4x4 gWorldViewProj;
};
// Explicitly padded to 256 bytes.
cbuffer cbPerObject : register(b0)
{
    float4x4 gWorldViewProj;
    float4x4 Pad0;
    float4x4 Pad1;
    float4x4 Pad1;
};

Diret3D 12 介绍了着色模型(shader model) 5.1.着色模型 5.1 介绍了可替代的 HLSL 语法来定义一个常量缓冲:

struct ObjectConstants
{
    float4x4 gWorldViewProj;
    uint matIndex;
};
ConstantBuffer<ObjectConstants> gObjConstants : register(b0);

这个常量缓冲的数据元素指示定义在一个分离的结构体,然后该常量缓冲从该结构体中创建.Fields of the constant buffer are then accessed in the shader using data member syntax:

uint index = gObjConstants.matIndex;

6.6.2 更新常量缓冲

因为一个常量缓冲随着 D3D12_HEAP_TYPE_UPLOAD 创建,我们可以从 CPU 上传数据到常量缓冲资源.为了实现这一功能,我们首先必须要得到一个指向资源数据的指针,这可以通过 Map 方法来完成:

ComPtr<ID3D12Resource> mUploadBuffer;
BYTE* mMappedData = nullptr;
mUploadBuffer->Map(0, nullptr, reinterpret_cast<void**>(&mMappedData));

第一个参数是一个子资源的索引.对于一个缓冲而言,唯一的子资源就是缓冲自身,所以我们设置为0.第二个参数是一个可选的指向 D3D12_RANGE 结构体(说明map里的内存范围)的指针 ,指定null既映射整个资源,第二个参数返回一个被映射的数据的指针.为了从系统内存里复制数据到常量缓冲,我们可以使用 memcpy:

memcpy(mMappedData, &data, dataSizeInBytes);

当我们完成一个常量缓冲,我们应该在释放内存前解除映射 Unmap :

if(mUploadBuffer != nullptr)
    mUploadBuffer->Unmap(0, nullptr);
mMappedData = nullptr;

Unmap 的第一个参数是一个子资源的索引,对于缓冲来说就是0.第二个参数是....一样的.


6.6.3 上传缓冲辅助程序(Upload Buffer Helper)

创建一个上传缓冲的轻量封装是十分便利的,我们在 UploadBuffer.h 定义接下来的类使上传缓冲使用起来更容易.它处理一个上传缓冲资源的构造和析构,映射和解除映射资源,还有提供 CopyData 方法在缓冲里上传一个特定的元素.我们在需要从 CPU 改变一个上传缓冲的内容时使用 CopyData 方法.注意该类可用于任意上传缓冲,而不是一定要常量缓冲.然而,如果我们确实用于常量缓冲,我们需要通过 isConstantBuffer 构造器参数来指明它.如果它正存储着一个常量缓冲,它就会自动填充内容来使每个缓冲达到一个 256 bytes 的倍数.

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

我们在 Update 方法里更新某个view矩阵(每帧):

void BoxApp::OnMouseMove(WPARAM btnState, int x, int
y)
{
if((btnState & MK_LBUTTON) != 0)
{
// Make each pixel correspond to a quarter of a
degree.
float dx =
XMConvertToRadians(0.25f*static_cast<float> (x -
mLastMousePos.x));
float dy =
XMConvertToRadians(0.25f*static_cast<float> (y -
mLastMousePos.y));
// Update angles based on input to orbit camera
around box.
mTheta += dx;
mPhi += dy;
// Restrict the angle mPhi.
mPhi = MathHelper::Clamp(mPhi, 0.1f,
MathHelper::Pi - 0.1f);
}
else if((btnState & MK_RBUTTON) != 0)
{
// Make each pixel correspond to 0.005 unit in the
scene.
 float dx = 0.005f*static_cast<float>(x -
mLastMousePos.x);
float dy = 0.005f*static_cast<float>(y -
mLastMousePos.y);
// Update the camera radius based on input.
mRadius += dx - dy;
// Restrict the radius.
mRadius = MathHelper::Clamp(mRadius, 3.0f, 15.0f);
}
mLastMousePos.x = x;
mLastMousePos.y = y;
}
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.6.4 常量缓冲说明符

同理,常量缓冲说明符存在于一个 D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV 类型的说明符堆.这个堆可以存储常量缓冲,着色器资源和无序访问说明符的混合体.为了存储这些说明符的新类型我们需要创建一个该类型的新说明符堆:

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;
ComPtr<ID3D12DescriptorHeap> mCbvHeap;
md3dDevice->CreateDescriptorHeap(&cbvHeapDesc,
    IID_PPV_ARGS(&mCbvHeap));

这些代码和创建渲染目标,深度/模板缓冲说明符堆十分相似.但是,一个显著的不同点在于我们指定 D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE 的 flag 来指明这些说明符将会被着色器程序访问.在 demo 里,我们没有 SRV 或 UAV 说明符,我们指示描绘一个对象,因此,我们需要这个堆里的一个说明符来存储一个 CBV.

一个常量缓冲视图是通过填写一个 D3D12_CONSTANT_BUFFER_VIEW_DESC 实例和调用 ID3D12Device::CreateConstantBufferView 来创建的:

/ Constant data per-object.
struct ObjectConstants
{
XMFLOAT4X4 WorldViewProj =
MathHelper::Identity4x4();
};
// Constant buffer to store the constants of n object.
std::unique_ptr<UploadBuffer<ObjectConstants>>
mObjectCB = nullptr;
mObjectCB =
std::make_unique<UploadBuffer<ObjectConstants>>(
md3dDevice.Get(), n, true);
UINT objCBByteSize =
d3dUtil::CalcConstantBufferByteSize(sizeof(ObjectConstants));
// Address to start of the buffer (0th constant
buffer).
D3D12_GPU_VIRTUAL_ADDRESS cbAddress = mObjectCB-
>Resource()->GetGPUVirtualAddress();
// Offset to the ith object constant buffer in the
buffer.
int boxCBufIndex = i;
cbAddress += boxCBufIndex*objCBByteSize;
D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc;
cbvDesc.BufferLocation = cbAddress;
cbvDesc.SizeInBytes =
d3dUtil::CalcConstantBufferByteSize(sizeof(ObjectConstants));
md3dDevice->CreateConstantBufferView(
&cbvDesc,
mCbvHeap->GetCPUDescriptorHandleForHeapStart());

D3D12_CONSTANT_BUFFER_VIEW_DESC 结构体说明了一个绑定到 HLSL 常量缓冲结构体的常量缓冲资源的子集.正如提到的那样,一般地一个常量缓冲春初者为 n 个对象都有一个常量的数组,但我们可以通过使用 BufferLocationSizeInBytes 来得到一个到第 i 个对象的常量数据的视图.D3D12CONSTANT_BUFFER_VIEW_DESC::SizeInBytesD3D12CONSTANT_BUFFER_VIEW_DESC::OffsetInBytes 成员一定是个 256 bytes 的倍数.别入,如果你指定64,那就会有错误(略).


6.6.5 根签名和说明符表

总的来说,不同的着色器程序在一个描绘调用执行前期待不同的将要被绑定到渲染管线的资源.资源被绑定到特定的寄存器插槽(可以被着色器程序访问).比如,之前的顶点和像素着色器值期待一个将要绑定到寄存器 b0 的常量缓冲.更高级的应用会在后面展开:

// Texture resource bound to texture register slot 0.
Texture2D gDiffuseMap : register(t0);
// Sampler resources bound to sampler register slots
0-5.
SamplerState gsamPointWrap : register(s0);
SamplerState gsamPointClamp : register(s1);
SamplerState gsamLinearWrap : register(s2);
SamplerState gsamLinearClamp : register(s3);
SamplerState gsamAnisotropicWrap : register(s4);
SamplerState gsamAnisotropicClamp : register(s5);
// cbuffer resource bound to cbuffer register slots 0-
2
cbuffer cbPerObject : register(b0)
{
float4x4 gWorld;
float4x4 gTexTransform;
};
// Constant data that varies per material.
cbuffer cbPass : register(b1)
{
float4x4 gView;
float4x4 gProj;
[…] // Other fields omitted for brevity.
};
cbuffer cbMaterial : register(b2)
{
float4 gDiffuseAlbedo;
float3 gFresnelR0;
float gRoughness;
float4x4 gMatTransform;
};

根签名(root signature)定义在一个描绘调用被执行前应用程序会绑定到渲染管线的资源是什么和那些资源映射到着色器输入寄存器的哪里.跟签名必须与着色器兼容.当光线状态类型被创建时它会进行确认(§6.9).不同的描绘丢奥用可能使用不同的着色器程序,它们要求不同的根签名.

如果我们把着色器程序看作是一个函数,把着色器期望的输入资源看作是一个函数参数,那么根签名就可以当作是定义了一个函数签名.通过绑定不同的资源当作参数(arguments),着色器的输出将会不同.所以,举个例子,一个顶点着色器会依赖于实在的被输入到着色器的顶点和绑定的资源.

一个根签名在 Direct3D 里头通过 ID3D12RootSignature 接口表示.它由说明着色器为描绘期望的资源的根参数的数组定义.一个根参数可以是根常量,根说明符,或者说明符表.说明附表指定了一个在说明符堆里连续的说明符范围.

以下代码创建了有一个根参数(足以存储一个 CBV 的说明附表)的根签名:

// 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, // Number of descriptors in table
0);// base shader register arguments are bound to
for this root parameter
slotRootParameter[0].InitAsDescriptorTable(
1, // Number of ranges
&cbvTable); // Pointer to array of ranges
// 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());
ThrowIfFailed(md3dDevice->CreateRootSignature(
0,
serializedRootSig->GetBufferPointer(),
serializedRootSig->GetBufferSize(),
IID_PPV_ARGS(&mRootSignature)));

CD3DX12ROOT_PARAMETERCD3DX12_DESCRIPTOR_RANGE 暂不解释.我们先考察以下代码:

CD3DX12_ROOT_PARAMETER slotRootParameter[1];
CD3DX12_DESCRIPTOR_RANGE cbvTable;
cbvTable.Init(
D3D12_DESCRIPTOR_RANGE_TYPE_CBV, // table type
1, // Number of descriptors in table
0);// base shader register arguments are bound to
for this root parameter
slotRootParameter[0].InitAsDescriptorTable(
1, // Number of ranges
&cbvTable); // Pointer to array of ranges

它创建一个期待一个 CBV 的说明附表(绑定到常量缓冲寄存器 0 既 register(b0) )的根参数.

根签名只定义应用程序将要绑定到渲染管线的资源是什么.它不做任何实质的资源绑定.一旦跟签名在指令列表里设置了,我们使用 ID3D12GraphicsCommandList::SetGraphicsRootDescriptorTable 来绑定一个说明符表到管线:

void ID3D12GraphicsCommandList::SetGraphicsRootDescriptorTable(
    UINT RootParameterIndex,
    D3D12_GPU_DESCRIPTOR_HANDLE BaseDescriptor);
  1. RootParameterIndex: Index of the root parameter we are setting.
  2. BaseDescriptor: Handle to a descriptor in the heap that specifies the first descriptor in the table being set. For example, if the root signature specified that this table had five descriptors, then BaseDescriptor and the next four descriptors in the heap are being set to this root table.

下面代码设置根签名和 CBV 堆到指令队列,还有设置了我们想要绑定到管线的说明符表:

mCommandList-
>SetGraphicsRootSignature(mRootSignature.Get());
ID3D12DescriptorHeap* descriptorHeaps[] = {
mCbvHeap.Get() };
mCommandList-
>SetDescriptorHeaps(_countof(descriptorHeaps),
descriptorHeaps);
// Offset the CBV we want to use for this draw call.
CD3DX12_GPU_DESCRIPTOR_HANDLE cbv(mCbvHeap -
>GetGPUDescriptorHandleForHeapStart());
cbv.Offset(cbvIndex, mCbvSrvUavDescriptorSize);
mCommandList->SetGraphicsRootDescriptorTable(0, cbv);

为了性能着想,要使跟签名尽可能小,和减少每渲染帧改变根签名的次数.

根签名的内容(the descriptor tables, root constants and root descriptors).....暂略

如果你改变了根签名,那么你会丢失所有存在的绑定.也就是说,你需要重新绑定所有的资源到新的根签名期望的管线里去.


6.7 编译着色器

在 Direct3D 中,着色器程序必须首先被编译成一个便携的字节码(bytecode).图形设备会接收这些字节码然后再次编译成优化的机器码.当即时运算时,我们可以通过以下函数编译着色器:

HRESULT D3DCompileFromFile(
    LPCWSTR pFileName,
    const D3D_SHADER_MACRO *pDefines,
    ID3DInclude *pInclude,
    LPCSTR pEntrypoint,
    LPCSTR pTarget,
    UINT Flags1,
    UINT Flags2,
    ID3DBlob **ppCode,
    ID3DBlob **ppErrorMsgs);
  1. pFileName: 包含我们想要编译的 HLSL 源代码的 .hlsl 文件的名字.
  2. pDefines: 我们不使用的高级应用,指定 null.
  3. pInclude: 我们不使用的高级应用,指定 null.
  4. pEntrypoint: 着色器入口点的函数名称.一个 .hlsl 可以包含多个着色器程序(比如一个顶点着色器和一个像素着色器),因此我们需要指定我们想要编译的特定的着色器的入口点.
  5. pTarget: 一个指定我们正在使用的着色器程序类型和版本的字符串,版本设为 5.0 and 5.1. a) vs_5_0 and vs_5_1: Vertex shader 5.0 and 5.1, respectively. b) hs_5_0 and hs_5_1: Hull shader 5.0 and 5.1, respectively. c) ds_5_0 and ds_5_1: Domain shader 5.0 and 5.1, respectively. d) gs_5_0 and gs_5_1: Geometry shader 5.0 and 5.1, respectively. e) ps_5_0 and ps_5_1: Pixel shader 5.0 and 5.1, respectively. f) cs_5_0 and cs_5_1: Compute shader 5.0 and 5.1, respectively.
  6. Flags1: 制定如何编译这些着色器代码的 flags.我们目前只用两种: a) D3DCOMPILE_DEBUG: Compiles the shaders in debug mode. b) D3DCOMPILE_SKIP_OPTIMIZATION: Instructs the compiler to skip optimizations (useful for debugging).
  7. Flags2: 我们不使用的高级应用.
  8. ppCode: 返回一个指向存储着被编译着色器对象的机器码的 ID3DBlob 数据结构体的指针.
  9. ppErrorMsgs: 返回一个指向存储着一个包含编译错误信息字符串的 ID3DBlob 数据结构体的指针.(如果有的话)

ID3DBlob 类型只是有两种方法的一大块内存:

  1. LPVOID GetBufferPointer: Returns a void* to the data, so it must be casted to the appropriate type before use (see the example below).
  2. SIZE_T GetBufferSize: Returns the byte size of the buffer.

为了支持错误输出,我们在 d3dUtil.h/.cpp 实现一下辅助函数来在即时运算里编译着色器:

ComPtr<ID3DBlob> d3dUtil::CompileShader(
const std::wstring& filename,
const D3D_SHADER_MACRO* defines,
const std::string& entrypoint,
 const std::string& target)
{
// Use debug flags in debug mode.
    UINT compileFlags = 0;
#if defined(DEBUG) || defined(_DEBUG)
compileFlags = D3DCOMPILE_DEBUG |
D3DCOMPILE_SKIP_OPTIMIZATION;
#endif
    HRESULT hr = S_OK;
  ComPtr<ID3DBlob> byteCode = nullptr;
    ComPtr<ID3DBlob> errors;
    hr = D3DCompileFromFile(filename.c_str(), defines,
        D3D_COMPILE_STANDARD_FILE_INCLUDE,
        entrypoint.c_str(), target.c_str(), compileFlags,
        0, &byteCode, &errors);
// Output errors to debug window.
    if(errors != nullptr)
        OutputDebugStringA((char*)errors->GetBufferPointer());
    ThrowIfFailed(hr);
    return byteCode;
}
//Here is an example of calling this function:
ComPtr<ID3DBlob> mvsByteCode = nullptr;
ComPtr<ID3DBlob> mpsByteCode = nullptr;
mvsByteCode = 
    d3dUtil::CompileShader(L”Shaders\color.hlsl”,
    nullptr, “VS”, “vs_5_0”);
mpsByteCode =
    d3dUtil::CompileShader(L”Shaders\color.hlsl”,
    nullptr, “PS”, “ps_5_0”);

6.7.1 离线编译

为被编译着色器使用 .cso 扩展是一种实用措施.

要离线编译,我们使用伴随 DirectX 的 FXC 工具.这是一个命令行工具.为了编译一个存储在 color.hlsl 的顶点和像素着色器(入口点为 VS,PS). 对于 debug 我们这样写:

fxc “color.hlsl” /Od /Zi /T vs_5_0 /E “VS” /Fo “color_vs.cso” /Fc “color_vs.asm”
fxc “color.hlsl” /Od /Zi /T ps_5_0 /E “PS” /Fo “color_ps.cso” /Fc “color_ps.asm”

为了编译一个存储在 color.hlsl 的顶点和像素着色器(入口点为 VS,PS). 对于 release 我们这样写:

fxc “color.hlsl” /T vs_5_0 /E “VS” /Fo “color_vs.cso” /Fc “color_vs.asm”
fxc “color.hlsl” /T ps_5_0 /E “PS” /Fo “color_ps.cso” /Fc “color_ps.asm”
  1. /Od: 关闭优化(对 debug 有利).
  2. /Zi: 开启 debug 信息.
  3. /T : 着色器类型和目标版本.
  4. /E : 着色器入口点.
  5. /Fo : 被编译着色器对象的字节码.
  6. /Fc : 输出一个汇编文件(assembly file),对于 debug 有利,检查.....

既然已经可以离线编译了,那就不需要在即时运算时编译(也就是说我们不需要调用 D3DCompileFromFile).但是,我们仍需从 .cso 文件加载被编译着色器对象的字节码到我们的应用程序.这可以通过使用标准 C++ 文件输入来完成:

ComPtr<ID3DBlob> d3dUtil::LoadBinary(const
std::wstring& filename)
{
    std::ifstream fin(filename, std::ios::binary);
    fin.seekg(0, std::ios_base::end);
    std::ifstream::pos_type size = (int)fin.tellg();
    fin.seekg(0, std::ios_base::beg);
    ComPtr<ID3DBlob> blob;
    ThrowIfFailed(D3DCreateBlob(size, blob.GetAddressOf()));
    fin.read((char*)blob->GetBufferPointer(), size);
    fin.close();
    return blob;
}
…
ComPtr<ID3DBlob> mvsByteCode =
    d3dUtil::LoadBinary(L”Shaders\color_vs.cso”);
ComPtr<ID3DBlob> mpsByteCode =
    d3dUtil::LoadBinary(L”Shaders\color_ps.cso”);

6.7.2 生成程序集


6.7.3 使用 VS 编译着色器


6.8 光栅化阶段


6.9 管线阶段对象


6.10 几何辅助结构体


6.11 BOX DEMO

//*********************************************************************
// BoxApp.cpp by Frank Luna (C) 2015 All Rights Reserved.
//
// Shows how to draw a box in Direct3D 12.
//
// Controls:
// Hold the left mouse button down and move the mouse to rotate.
// Hold the right mouse button down and move the mouse to zoom in and
// out.
//*********************************************************************
#include “../../Common/d3dApp.h”
#include “../../Common/MathHelper.h”
#include “../../Common/UploadBuffer.h”
using Microsoft::WRL::ComPtr;
using namespace DirectX;
using namespace DirectX::PackedVector;
struct Vertex
{
    XMFLOAT3 Pos;
    XMFLOAT4 Color;
};
struct ObjectConstants
{
    XMFLOAT4X4 WorldViewProj = MathHelper::Identity4x4();
};
class BoxApp : public D3DApp
{
public:
    BoxApp(HINSTANCE hInstance);
    BoxApp(const BoxApp& rhs) = delete;
    BoxApp& operator=(const BoxApp& rhs) = delete;
    ˜BoxApp();
    virtual bool Initialize()override;
private:
    virtual void OnResize()override;
    virtual void Update(const GameTimer& gt)override;
    virtual void Draw(const GameTimer& gt)override;
    virtual void OnMouseDown(WPARAM btnState, int x, int y)override;
    virtual void OnMouseUp(WPARAM btnState, int x, int y)override;
    virtual void OnMouseMove(WPARAM btnState, int x, int y)override;
    void BuildDescriptorHeaps();
    void BuildConstantBuffers();
    void BuildRootSignature();
    void BuildShadersAndInputLayout();
    void BuildBoxGeometry();
    void BuildPSO();
private:
    ComPtr<ID3D12RootSignature> mRootSignature =
nullptr;
    ComPtr<ID3D12DescriptorHeap> mCbvHeap = nullptr;
    std::unique_ptr<UploadBuffer<ObjectConstants> > mObjectCB = nullptr;
    std::unique_ptr<MeshGeometry> mBoxGeo = nullptr;
    ComPtr<ID3DBlob> mvsByteCode = nullptr;
    ComPtr<ID3DBlob> mpsByteCode = nullptr;
    std::vector<D3D12_INPUT_ELEMENT_DESC> mInputLayout;
    ComPtr<ID3D12PipelineState> mPSO = nullptr;
    XMFLOAT4X4 mWorld = MathHelper::Identity4x4();
    XMFLOAT4X4 mView = MathHelper::Identity4x4();
    XMFLOAT4X4 mProj = MathHelper::Identity4x4();
    float mTheta = 1.5f*XM_PI;
    float mPhi = XM_PIDIV4;
    float mRadius = 5.0f;
    POINT mLastMousePos;
};
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE prevInstance,
    PSTR cmdLine, int showCmd)
{
// Enable run-time memory check for debug builds.
#if defined(DEBUG) | defined(_DEBUG)
 _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF |
_CRTDBG_LEAK_CHECK_DF );
#endif
    try
    {
        BoxApp theApp(hInstance);
        if(!theApp.Initialize())
            return 0;
        return theApp.Run();
    }
    catch(DxException& e)
    {
        MessageBox(nullptr, e.ToString().c_str(), L”HR
            Failed”, MB_OK);
        return 0;
    }
}
BoxApp::BoxApp(HINSTANCE hInstance) : D3DApp(hInstance) {}
BoxApp::˜BoxApp(){}
bool BoxApp::Initialize()
{
    if(!D3DApp::Initialize())
        return false;
// Reset the command list to prep for initialization ommands.
    ThrowIfFailed(mCommandList->Reset(mDirectCmdListAlloc.Get(), nullptr));
    BuildDescriptorHeaps();
    BuildConstantBuffers();
    BuildRootSignature();
    BuildShadersAndInputLayout();
    BuildBoxGeometry();
    BuildPSO();
// Execute the initialization commands.
    ThrowIfFailed(mCommandList->Close());
    ID3D12CommandList* cmdsLists[] = { mCommandList.Get() };
    mCommandQueue->ExecuteCommandLists(_countof(cmdsLists), cmdsLists);
// Wait until initialization is complete.
    FlushCommandQueue();
    return true;
}
void BoxApp::OnResize()
{
    D3DApp::OnResize();
// The window resized, so update the aspect ratio and recompute the
// projection matrix.
    XMMATRIX P = XMMatrixPerspectiveFovLH(0.25f*MathHelper::Pi,
        AspectRatio(), 1.0f, 1000.0f);
    XMStoreFloat4x4(&mProj, P);
}
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);
}
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.
    mCommandList->OMSetRenderTargets(1,
        &CurrentBackBufferView(),
        true, &DepthStencilView());
    ID3D12DescriptorHeap* descriptorHeaps[] = { mCbvHeap.Get() };
    mCommandList-
        >SetDescriptorHeaps(_countof(descriptorHeaps),descriptorHeaps);
    mCommandList-
        >SetGraphicsRootSignature(mRootSignature.Get());
    mCommandList->IASetVertexBuffers(0, 1, &mBoxGeo-
        >VertexBufferView());
    mCommandList->IASetIndexBuffer(&mBoxGeo-
        >IndexBufferView());
    mCommandList-
        >IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
    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();
}
void BoxApp::OnMouseDown(WPARAM btnState, int x, int y)
{
    mLastMousePos.x = x;
    mLastMousePos.y = y;
    SetCapture(mhMainWnd);
}
void BoxApp::OnMouseUp(WPARAM btnState, int x, int y)
{
    ReleaseCapture();
}
void BoxApp::OnMouseMove(WPARAM btnState, int x, int y)
{
    if((btnState & MK_LBUTTON) != 0)
    {
// Make each pixel correspond to a quarter of a degree.
    float dx = 
        XMConvertToRadians(0.25f*static_cast<float>(x -
        mLastMousePos.x));
    float dy =
        XMConvertToRadians(0.25f*static_cast<float>(y -
        mLastMousePos.y));
// Update angles based on input to orbit camera around box.
        mTheta += dx;
        mPhi += dy;
// Restrict the angle mPhi.
        mPhi = MathHelper::Clamp(mPhi, 0.1f,
        MathHelper::Pi - 0.1f);
    }
    else if((btnState & MK_RBUTTON) != 0)
    {
// Make each pixel correspond to 0.005 unit in the scene.
        float dx = 0.005f*static_cast<float>(x -
            mLastMousePos.x);
        float dy = 0.005f*static_cast<float>(y -
            mLastMousePos.y);
 // Update the camera radius based on input.
        mRadius += dx - dy;
// Restrict the radius.
        mRadius = MathHelper::Clamp(mRadius, 3.0f, 15.0f);
    }
    mLastMousePos.x = x;
    mLastMousePos.y = y;
}
void BoxApp::BuildDescriptorHeaps()
{
    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)));
}
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.
// Here our i = 0.
    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::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)));
}
void BoxApp::BuildShadersAndInputLayout()
{
    HRESULT hr = S_OK;
    mvsByteCode =
        d3dUtil::CompileShader(L”Shaders\color.hlsl”, nullptr,
        “VS”, “vs_5_0”);
    mpsByteCode =
        d3dUtil::CompileShader(L”Shaders\color.hlsl”, nullptr,
        “PS”, “ps_5_0”);
    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 }
    };
}
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;
}
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)));
}

发表评论

电子邮件地址不会被公开。 必填项已用*标注