iT邦幫忙

2026 iThome 鐵人賽

DAY 12
0

前面有個舉例顏色格子的例子,這邊就用這例子來介紹 StructuredBuffer

StructuredBuffer 是什麼

基本上來說,一般傳遞資料都是一筆一筆的,不過這樣傳的問題就會像是前面的例子那樣,如果有一百萬筆資料要傳你會讓 CPU 傳一百萬次才完成。如果這裡面的資料內容其實是相同的那就可以全放在一起後一口氣傳進 GPU 裡。

而 C++ 裡的 struct 大小要與 HLSL 裡的對應

// 這邊 C++ 是 32 bytes
struct ColorBlockData
{
    Vector2 center;
    Vector2 halfSize;
    Vector4 color;
};

// 這邊 HLSL 也要是 32 bytes
struct ColorBlockData
{
    float2 center;
    float2 halfSize;
    float4 color;
};

各家引擎傳遞的方法都不太一樣,這邊就使用相對簡陋的方式去設定 StructuredBuffer 的傳遞

render_state.h

#pragma once

#include <array>
#include <cstdint>
#include <directx/d3d12.h>
#include <wrl/client.h>

class ConstantBuffer;
class IShaderResource;
class IUnorderAccessResource;
class RenderContext;

enum class ShaderStage
{
    Graphics,
    Compute
};

class RenderState
{
  public:
    static constexpr UINT FrameCount = 2;
    static constexpr UINT ConstantBufferCount = 4;
    static constexpr UINT ShaderResourceCount = 32;
    static constexpr UINT UnorderedAccessCount = 8;
    static constexpr UINT ResourceTableRootParameter = 4;

    void init(ID3D12Device* device);

    void setStructuredBuffer(UINT slot, IShaderResource& buffer);
    void setRWStructuredBuffer(
        UINT shaderResourceSlot,
        UINT unorderedAccessSlot,
        IUnorderAccessResource& buffer);
    void clearStructuredBuffer(UINT slot);
    void clearRWStructuredBuffer(UINT shaderResourceSlot, UINT unorderedAccessSlot);
    void clearStructuredBuffers();

    void setConstantBuffer(
        RenderContext& renderContext,
        ShaderStage stage,
        UINT slot,
        ConstantBuffer& buffer) const;
    void applyGraphics(RenderContext& renderContext, UINT frameIndex) const;
    void applyCompute(RenderContext& renderContext, UINT frameIndex) const;

  private:
    static constexpr UINT ShaderResourceOffset = ConstantBufferCount;
    static constexpr UINT UnorderedAccessOffset = ShaderResourceOffset + ShaderResourceCount;
    static constexpr UINT DescriptorCount = UnorderedAccessOffset + UnorderedAccessCount;

    D3D12_CPU_DESCRIPTOR_HANDLE getCpuHandle(UINT frameIndex, UINT descriptorIndex) const;
    D3D12_GPU_DESCRIPTOR_HANDLE getGpuTableStart(UINT frameIndex) const;
    void writeNullConstantBuffer(UINT frameIndex, UINT slot);
    void writeNullShaderResource(UINT frameIndex, UINT slot);
    void writeNullUnorderedAccess(UINT frameIndex, UINT slot);
    void requireInitialized() const;

    ID3D12Device* m_device = nullptr;
    std::array<Microsoft::WRL::ComPtr<ID3D12DescriptorHeap>, FrameCount> m_descriptorHeaps;
    UINT m_descriptorSize = 0;
    bool m_initialized = false;
};
render_state.cpp

#include "render_state.h"

#include <stdexcept>

#include "constant_buffer.h"
#include "render_context.h"
#include "shader_resource.h"
#include "unorder_access_resource.h"

// 初始化 RenderState 使用的 Descriptor Heap 與相關狀態
void RenderState::init(ID3D12Device* device)
{
    // 避免重複初始化
    if (m_initialized)
        // 已初始化時拋出邏輯錯誤
        throw std::logic_error("RenderState: Render state is already initialized.");

    // 確認傳入的 D3D12 Device 有效
    if (device == nullptr)
        // Device 無效時拋出參數錯誤
        throw std::invalid_argument("RenderState: A graphics device is required.");

    // 保存 D3D12 Device 供後續建立 Descriptor 使用
    m_device = device;

    // 取得 CBV、SRV、UAV Descriptor 在 Heap 中的間隔大小
    m_descriptorSize = device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);

    // 建立並零初始化 Descriptor Heap 設定
    D3D12_DESCRIPTOR_HEAP_DESC description{};

    // 指定 Heap 用來存放 CBV、SRV 與 UAV
    description.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;

    // 設定 Heap 內總 Descriptor 數量
    description.NumDescriptors = DescriptorCount;

    // 設定 Heap 可由 Shader 存取
    description.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;

    // 為每個 Frame 建立獨立的 Descriptor Heap
    for (UINT frameIndex = 0; frameIndex < FrameCount; ++frameIndex)
    {
        // 建立目前 Frame 使用的 Shader-visible Descriptor Heap
        if (FAILED(device->CreateDescriptorHeap(&description,
                                                IID_PPV_ARGS(m_descriptorHeaps[frameIndex].ReleaseAndGetAddressOf()))))
            // Descriptor Heap 建立失敗時拋出執行期錯誤
            throw std::runtime_error("RenderState: Failed to create a shader-visible descriptor heap.");
    }

    // 標記 RenderState 已完成初始化
    m_initialized = true;

    // 將所有資源 Descriptor 初始化成 Null Descriptor
    clearStructuredBuffers();
}

// 將 Structured Buffer 設定到指定 SRV slot
void RenderState::setStructuredBuffer(UINT slot, IShaderResource& buffer)
{
    // 確認 RenderState 已完成初始化
    requireInitialized();

    // 確認 SRV slot 沒有超出允許範圍
    if (slot >= ShaderResourceCount)
        // slot 超出範圍時拋出錯誤
        throw std::out_of_range("RenderState: Shader-resource slot is out of range.");

    // 為每個 Frame 註冊對應的 SRV
    for (UINT frameIndex = 0; frameIndex < FrameCount; ++frameIndex)
        // 將 Buffer 的 SRV 寫入目前 Frame 對應的 Descriptor
        buffer.registerShaderResourceView(
            // 取得指定 SRV slot 的 CPU Descriptor Handle
            getCpuHandle(frameIndex, ShaderResourceOffset + slot),
            // 將 Frame index 傳給資源以取得對應 Frame 的內容
            static_cast<int>(frameIndex));
}

// 同時將 Structured Buffer 設定成 SRV 與 UAV
void RenderState::setRWStructuredBuffer(UINT shaderResourceSlot, UINT unorderedAccessSlot,
                                        IUnorderAccessResource& buffer)
{
    // 將 Buffer 註冊到指定的 SRV slot
    setStructuredBuffer(shaderResourceSlot, buffer);

    // 確認 UAV slot 沒有超出允許範圍
    if (unorderedAccessSlot >= UnorderedAccessCount)
        // slot 超出範圍時拋出錯誤
        throw std::out_of_range("RenderState: Unordered-access slot is out of range.");

    // 為每個 Frame 註冊對應的 UAV
    for (UINT frameIndex = 0; frameIndex < FrameCount; ++frameIndex)
        // 將 Buffer 的 UAV 寫入目前 Frame 對應的 Descriptor
        buffer.RegisterUnorderAccessView(
            // 取得指定 UAV slot 的 CPU Descriptor Handle
            getCpuHandle(frameIndex, UnorderedAccessOffset + unorderedAccessSlot),
            // 將 Frame index 傳給資源以取得對應 Frame 的內容
            static_cast<int>(frameIndex));
}

// 清除指定 SRV slot
void RenderState::clearStructuredBuffer(UINT slot)
{
    // 確認 RenderState 已完成初始化
    requireInitialized();

    // 確認 SRV slot 沒有超出允許範圍
    if (slot >= ShaderResourceCount)
        // slot 超出範圍時拋出錯誤
        throw std::out_of_range("RenderState: Shader-resource slot is out of range.");

    // 清除每個 Frame 中指定的 SRV slot
    for (UINT frameIndex = 0; frameIndex < FrameCount; ++frameIndex)
        // 寫入 Null SRV
        writeNullShaderResource(frameIndex, slot);
}

// 清除指定 Buffer 對應的 SRV 與 UAV slot
void RenderState::clearRWStructuredBuffer(UINT shaderResourceSlot, UINT unorderedAccessSlot)
{
    // 清除指定的 SRV slot
    clearStructuredBuffer(shaderResourceSlot);

    // 確認 UAV slot 沒有超出允許範圍
    if (unorderedAccessSlot >= UnorderedAccessCount)
        // slot 超出範圍時拋出錯誤
        throw std::out_of_range("RenderState: Unordered-access slot is out of range.");

    // 清除每個 Frame 中指定的 UAV slot
    for (UINT frameIndex = 0; frameIndex < FrameCount; ++frameIndex)
        // 寫入 Null UAV
        writeNullUnorderedAccess(frameIndex, unorderedAccessSlot);
}

// 清除所有 Frame 中的 CBV、SRV 與 UAV Descriptor
void RenderState::clearStructuredBuffers()
{
    // 確認 RenderState 已完成初始化
    requireInitialized();

    // 逐一處理所有 Frame
    for (UINT frameIndex = 0; frameIndex < FrameCount; ++frameIndex)
    {
        // 清除目前 Frame 的所有 Constant Buffer slot
        for (UINT slot = 0; slot < ConstantBufferCount; ++slot)
            // 寫入 Null CBV
            writeNullConstantBuffer(frameIndex, slot);

        // 清除目前 Frame 的所有 Shader Resource slot
        for (UINT slot = 0; slot < ShaderResourceCount; ++slot)
            // 寫入 Null SRV
            writeNullShaderResource(frameIndex, slot);

        // 清除目前 Frame 的所有 Unordered Access slot
        for (UINT slot = 0; slot < UnorderedAccessCount; ++slot)
            // 寫入 Null UAV
            writeNullUnorderedAccess(frameIndex, slot);
    }
}

// 將 Constant Buffer 綁定到指定 Shader Stage 的 Root Parameter
void RenderState::setConstantBuffer(RenderContext& renderContext, ShaderStage stage, UINT slot,
                                    ConstantBuffer& buffer) const
{
    // 確認 RenderState 已完成初始化
    requireInitialized();

    // 確認 Constant Buffer slot 沒有超出允許範圍
    if (slot >= ConstantBufferCount)
        // slot 超出範圍時拋出錯誤
        throw std::out_of_range("RenderState: Constant-buffer slot is out of range.");

    // 取得 Constant Buffer 的 GPU Virtual Address
    const D3D12_GPU_VIRTUAL_ADDRESS address = buffer.getGPUVirtualAddress();

    // 判斷 Constant Buffer 要綁定到 Graphics 或 Compute Pipeline
    if (stage == ShaderStage::Graphics)
        // 設定 Graphics Root CBV
        renderContext.setGraphicsRootConstantBufferView(slot, address);
    else
        // 設定 Compute Root CBV
        renderContext.setComputeRootConstantBufferView(slot, address);
}

// 套用 Graphics Pipeline 所需的 Descriptor Heap 與 Descriptor Table
void RenderState::applyGraphics(RenderContext& renderContext, UINT frameIndex) const
{
    // 確認 RenderState 已完成初始化
    requireInitialized();

    // 確認 Frame index 沒有超出範圍
    if (frameIndex >= FrameCount)
        // Frame index 超出範圍時拋出錯誤
        throw std::out_of_range("RenderState: Frame index is out of range.");

    // 將目前 Frame 的 Descriptor Heap 綁定到 Command List
    renderContext.setDescriptorHeap(m_descriptorHeaps[frameIndex].Get());

    // 將目前 Frame 的 Descriptor Table 綁定到 Graphics Root Signature
    renderContext.setGraphicsRootDescriptorTable(ResourceTableRootParameter, getGpuTableStart(frameIndex));
}

// 套用 Compute Pipeline 所需的 Descriptor Heap 與 Descriptor Table
void RenderState::applyCompute(RenderContext& renderContext, UINT frameIndex) const
{
    // 確認 RenderState 已完成初始化
    requireInitialized();

    // 確認 Frame index 沒有超出範圍
    if (frameIndex >= FrameCount)
        // Frame index 超出範圍時拋出錯誤
        throw std::out_of_range("RenderState: Frame index is out of range.");

    // 將目前 Frame 的 Descriptor Heap 綁定到 Command List
    renderContext.setDescriptorHeap(m_descriptorHeaps[frameIndex].Get());

    // 將目前 Frame 的 Descriptor Table 綁定到 Compute Root Signature
    renderContext.setComputeRootDescriptorTable(ResourceTableRootParameter, getGpuTableStart(frameIndex));
}

// 取得指定 Descriptor 的 CPU Handle
D3D12_CPU_DESCRIPTOR_HANDLE RenderState::getCpuHandle(UINT frameIndex, UINT descriptorIndex) const
{
    // 取得目前 Frame Descriptor Heap 起始位置的 CPU Handle
    D3D12_CPU_DESCRIPTOR_HANDLE handle = m_descriptorHeaps[frameIndex]->GetCPUDescriptorHandleForHeapStart();

    // 根據 Descriptor index 與 Descriptor 大小計算目標位置
    handle.ptr += static_cast<SIZE_T>(descriptorIndex) * m_descriptorSize;

    // 回傳計算完成的 CPU Descriptor Handle
    return handle;
}

// 取得指定 Frame Descriptor Heap 的 GPU 起始 Handle
D3D12_GPU_DESCRIPTOR_HANDLE RenderState::getGpuTableStart(UINT frameIndex) const
{
    // 回傳 Descriptor Heap 起始位置的 GPU Handle
    return m_descriptorHeaps[frameIndex]->GetGPUDescriptorHandleForHeapStart();
}

// 在指定 Constant Buffer slot 建立 Null CBV
void RenderState::writeNullConstantBuffer(UINT frameIndex, UINT slot)
{
    // 使用空 Resource 建立 Null Constant Buffer View
    m_device->CreateConstantBufferView(nullptr, getCpuHandle(frameIndex, slot));
}

// 在指定 Shader Resource slot 建立 Null SRV
void RenderState::writeNullShaderResource(UINT frameIndex, UINT slot)
{
    // 建立並零初始化 SRV 設定
    D3D12_SHADER_RESOURCE_VIEW_DESC description{};

    // Structured Buffer 不使用特定像素格式
    description.Format = DXGI_FORMAT_UNKNOWN;

    // 將 SRV 類型設定成 Buffer
    description.ViewDimension = D3D12_SRV_DIMENSION_BUFFER;

    // 使用 D3D12 預設的 Shader Component Mapping
    description.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;

    // Null SRV 使用一個虛擬元素描述 Buffer
    description.Buffer.NumElements = 1;

    // 將虛擬元素大小設定成 32-bit
    description.Buffer.StructureByteStride = sizeof(std::uint32_t);

    // 使用空 Resource 建立 Null Shader Resource View
    m_device->CreateShaderResourceView(nullptr, &description,
                                       // 將 SRV 寫入指定 Frame 與 slot 的 Descriptor
                                       getCpuHandle(frameIndex, ShaderResourceOffset + slot));
}

// 在指定 Unordered Access slot 建立 Null UAV
void RenderState::writeNullUnorderedAccess(UINT frameIndex, UINT slot)
{
    // 建立並零初始化 UAV 設定
    D3D12_UNORDERED_ACCESS_VIEW_DESC description{};

    // Structured Buffer 不使用特定像素格式
    description.Format = DXGI_FORMAT_UNKNOWN;

    // 將 UAV 類型設定成 Buffer
    description.ViewDimension = D3D12_UAV_DIMENSION_BUFFER;

    // Null UAV 使用一個虛擬元素描述 Buffer
    description.Buffer.NumElements = 1;

    // 將虛擬元素大小設定成 32-bit
    description.Buffer.StructureByteStride = sizeof(std::uint32_t);

    // 使用空 Resource 與空 Counter Resource 建立 Null UAV
    m_device->CreateUnorderedAccessView(nullptr, nullptr, &description,
                                        // 將 UAV 寫入指定 Frame 與 slot 的 Descriptor
                                        getCpuHandle(frameIndex, UnorderedAccessOffset + slot));
}

// 確認 RenderState 已完成初始化
void RenderState::requireInitialized() const
{
    // 尚未初始化時禁止使用 RenderState
    if (!m_initialized)
        // 拋出錯誤提醒必須先呼叫 init
        throw std::logic_error("RenderState: init must be called before use.");
}

剩下為實作的程式

//color_block.hlsl

// 定義單一色塊在 Structured Buffer 中的資料格式
struct ColorBlockData
{
    // 儲存色塊中心位置
    float2 center;

    // 儲存色塊寬高的一半
    float2 halfSize;

    // 儲存色塊的 RGBA 顏色
    float4 color;
};

// 將色塊資料綁定到 t0 Shader Resource slot
StructuredBuffer<ColorBlockData> colorBlocks : register(t0);

// 定義 Vertex Shader 的輸入資料
struct VSInput
{
    // 接收 Vertex Buffer 傳入的頂點位置
    float4 position : POSITION;

    // 接收目前 instance 的 index
    uint instanceId : SV_InstanceID;
};

// 定義 Vertex Shader 傳給 Pixel Shader 的資料
struct VSOutput
{
    // 儲存轉換後的裁切空間位置
    float4 position : SV_Position;

    // 傳遞目前色塊的顏色
    float4 color : COLOR0;
};

// 執行每個頂點的 Vertex Shader
VSOutput VSMain(VSInput input)
{
    // 根據目前 instance index 取得對應的色塊資料
    ColorBlockData block = colorBlocks[input.instanceId];

    // 建立 Vertex Shader 輸出資料
    VSOutput output;

    // 將共用四邊形頂點縮放至色塊大小,再平移至色塊中心
    output.position = float4(block.center + input.position.xy * block.halfSize, 0.0f, 1.0f);

    // 將目前色塊顏色傳給 Pixel Shader
    output.color = block.color;

    // 回傳 Vertex Shader 輸出
    return output;
}

// 執行每個 Pixel 的 Pixel Shader
float4 PSMain(VSOutput input) : SV_Target0
{
    // 將 Vertex Shader 傳入的顏色直接輸出到 Render Target
    return input.color;
}
//main.cpp

#include <array>
#include <cstdint>
#include <cstdlib>
#include <directx/d3dx12_core.h>
#include <exception>
#include <stdexcept>

#include "graphics_engine.h"
#include "math/vector.h"
#include "my_engine.h"
#include "render_state.h"
#include "skyline_debugger.h"
#include "system.h"

namespace
{
// 定義單一頂點只包含二維位置資料
struct SimpleVertex
{
    // 儲存頂點在區域座標中的二維位置
    Vector2 position;
};

// 定義傳給 Shader 的單一色塊資料
struct ColorBlockData
{
    // 儲存色塊中心位置
    Vector2 center;

    // 儲存色塊寬高的一半
    Vector2 halfSize;

    // 儲存色塊的 RGBA 顏色
    Vector4 color;
};

static_assert(sizeof(ColorBlockData) == sizeof(float) * 8,
              "ColorBlockData must match the HLSL structured-buffer layout.");

// 設定水平方向的色塊數量
constexpr std::uint32_t ColumnCount = 10;

// 設定垂直方向的色塊數量
constexpr std::uint32_t RowCount = 10;

// 計算畫面上的色塊總數
constexpr std::uint32_t BlockCount = ColumnCount * RowCount;

// 建立所有色塊的位置、大小與顏色資料
std::array<ColorBlockData, BlockCount> createColorBlocks()
{
    // 定義色塊使用的顏色表
    static const std::array<Vector4, 20> palette = {
        Vector4{1.00f, 0.05f, 0.03f, 1.0f}, Vector4{0.05f, 0.08f, 1.00f, 1.0f}, Vector4{1.00f, 0.88f, 0.03f, 1.0f},
        Vector4{0.12f, 0.92f, 0.10f, 1.0f}, Vector4{0.95f, 0.20f, 0.68f, 1.0f}, Vector4{0.02f, 0.85f, 0.88f, 1.0f},
        Vector4{1.00f, 0.43f, 0.02f, 1.0f}, Vector4{0.45f, 0.05f, 0.62f, 1.0f}, Vector4{0.98f, 0.67f, 0.82f, 1.0f},
        Vector4{0.02f, 0.45f, 0.13f, 1.0f}, Vector4{0.33f, 0.27f, 0.92f, 1.0f}, Vector4{0.22f, 0.90f, 0.67f, 1.0f},
        Vector4{1.00f, 0.30f, 0.32f, 1.0f}, Vector4{0.04f, 0.09f, 0.48f, 1.0f}, Vector4{0.98f, 0.96f, 0.39f, 1.0f},
        Vector4{0.83f, 0.02f, 0.59f, 1.0f}, Vector4{0.02f, 0.59f, 0.56f, 1.0f}, Vector4{1.00f, 0.62f, 0.42f, 1.0f},
        Vector4{0.57f, 0.05f, 0.98f, 1.0f}, Vector4{0.54f, 0.63f, 0.12f, 1.0f},
    };

    // 根據欄數計算每個色塊在 NDC 中的寬度
    constexpr float blockWidth = 2.0f / static_cast<float>(ColumnCount);

    // 根據列數計算每個色塊在 NDC 中的高度
    constexpr float blockHeight = 2.0f / static_cast<float>(RowCount);

    // 計算每個色塊從中心到邊界的距離
    const Vector2 halfSize{blockWidth * 0.5f, blockHeight * 0.5f};

    // 建立並零初始化所有色塊資料
    std::array<ColorBlockData, BlockCount> blocks{};

    // 逐列建立色塊
    for (std::uint32_t row = 0; row < RowCount; ++row)
    {
        // 逐欄建立目前列中的色塊
        for (std::uint32_t column = 0; column < ColumnCount; ++column)
        {
            // 將二維列欄座標轉換成一維陣列 index
            const std::uint32_t blockIndex = row * ColumnCount + column;

            // 依照色塊位置產生分散的顏色表 index
            const std::uint32_t colorIndex = (blockIndex * 7 + row * 3) % palette.size();

            // 計算目前色塊在 NDC 中的中心位置
            const Vector2 center{-1.0f + blockWidth * (static_cast<float>(column) + 0.5f),
                                 1.0f - blockHeight * (static_cast<float>(row) + 0.5f)};

            // 寫入目前色塊的位置、大小與顏色
            blocks[blockIndex] = {center, halfSize, palette[colorIndex]};
        }
    }

    // 回傳建立完成的色塊資料
    return blocks;
}

// 初始化 Root Signature
void initRootSignature(RootSignature& rootSignature)
{
    // 使用線性取樣與 Wrap addressing mode 建立 Root Signature
    rootSignature.init(D3D12_FILTER_MIN_MAG_MIP_LINEAR, D3D12_TEXTURE_ADDRESS_MODE_WRAP,
                       D3D12_TEXTURE_ADDRESS_MODE_WRAP, D3D12_TEXTURE_ADDRESS_MODE_WRAP);
}

// 建立繪製色塊使用的 Graphics Pipeline State 設定
D3D12_GRAPHICS_PIPELINE_STATE_DESC createColorBlockPipelineStateDescription(ID3D12RootSignature* rootSignature,
                                                                            ID3DBlob* vertexShader,
                                                                            ID3DBlob* pixelShader)
{
    if (rootSignature == nullptr)
        throw std::invalid_argument("ColorBlockPipeline: Root signature is required.");

    if (vertexShader == nullptr)
        throw std::invalid_argument("ColorBlockPipeline: Vertex shader is required.");

    if (pixelShader == nullptr)
        throw std::invalid_argument("ColorBlockPipeline: Pixel shader is required.");

    // 定義 Vertex Buffer 中 POSITION attribute 的格式
    static constexpr D3D12_INPUT_ELEMENT_DESC inputElementDescriptions[] = {
        {"POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0},
    };

    // 建立並零初始化 Graphics Pipeline State 設定
    D3D12_GRAPHICS_PIPELINE_STATE_DESC description{};

    // 設定 Vertex Shader 的 Input Layout
    description.InputLayout = {inputElementDescriptions, _countof(inputElementDescriptions)};

    // 指定 Pipeline 使用的 Root Signature
    description.pRootSignature = rootSignature;

    // 設定 Vertex Shader bytecode
    description.VS = CD3DX12_SHADER_BYTECODE(vertexShader);

    // 設定 Pixel Shader bytecode
    description.PS = CD3DX12_SHADER_BYTECODE(pixelShader);

    // 使用 D3D12 預設 Rasterizer State
    description.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);

    // 關閉背面剔除以繪製所有三角形方向
    description.RasterizerState.CullMode = D3D12_CULL_MODE_NONE;

    // 使用 D3D12 預設 Blend State
    description.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);

    // 使用 D3D12 預設 Depth Stencil State
    description.DepthStencilState = CD3DX12_DEPTH_STENCIL_DESC(D3D12_DEFAULT);

    // 關閉 Depth Test
    description.DepthStencilState.DepthEnable = FALSE;

    // 禁止寫入 Depth Buffer
    description.DepthStencilState.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ZERO;

    // 關閉 Stencil Test
    description.DepthStencilState.StencilEnable = FALSE;

    // 啟用所有 sample
    description.SampleMask = UINT_MAX;

    // 指定 Pipeline 接收 Triangle primitive
    description.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;

    // 設定使用一個 Render Target
    description.NumRenderTargets = 1;

    // 設定 Render Target 的像素格式
    description.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;

    // 設定 Depth Stencil Buffer 的像素格式
    description.DSVFormat = DXGI_FORMAT_D32_FLOAT;

    // 關閉 MSAA 並使用單一 sample
    description.SampleDesc.Count = 1;

    // 回傳完成的 Pipeline State 設定
    return description;
}

// 建立色塊繪製使用的 Pipeline State
void initPipelineState(PipelineState& pipelineState, RootSignature& rootSignature, Shader& vertexShader,
                       Shader& pixelShader)
{
    // 根據 Root Signature 與 Shader 建立 Pipeline State 描述
    const auto description = createColorBlockPipelineStateDescription(
        rootSignature.get(), vertexShader.getCompiledBlob(), pixelShader.getCompiledBlob());

    // 使用描述資料建立 Pipeline State Object
    pipelineState.init(description);
}
} // namespace

// Windows GUI 應用程式的進入點
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow)
{
    // 捕捉初始化與執行期間可能發生的例外
    try
    {
        // 初始化 Skyline Debugger 並在建立 D3D12 Device 前啟用 Debug Layer
        SkylineDebugger::Initialize("DayX_ColorBlock");

        // 建立應用程式視窗
        initWindow(hInstance, hPrevInstance, lpCmdLine, nCmdShow, TEXT("Color Block"));

        // 確認視窗已成功建立
        if (g_hWnd == nullptr)
            throw std::runtime_error("DayX_ColorBlock: Failed to create the application window.");

        // 建立 Graphics Engine
        GraphicsEngine graphicsEngine;

        // 初始化 D3D12 Device、Swap Chain 與相關圖形資源
        graphicsEngine.init(g_hWnd, FRAME_BUFFER_W, FRAME_BUFFER_H);

        // 將建立完成的 D3D12 Device 交給 Skyline Debugger 設定
        SkylineDebugger::ConfigureD3D12(graphicsEngine.getD3DDevice());

        // 建立 Root Signature
        RootSignature rootSignature;

        // 初始化繪圖使用的 Root Signature
        initRootSignature(rootSignature);

        // 建立 Vertex Shader 與 Pixel Shader
        Shader vertexShader, pixelShader;

        // 編譯並載入 Vertex Shader
        vertexShader.loadVS("assets/shaders/color_block.hlsl", "VSMain");

        // 編譯並載入 Pixel Shader
        pixelShader.loadPS("assets/shaders/color_block.hlsl", "PSMain");

        // 建立 Graphics Pipeline State
        PipelineState pipelineState;

        // 使用 Shader 與 Root Signature 初始化 Pipeline State
        initPipelineState(pipelineState, rootSignature, vertexShader, pixelShader);

        // 定義所有色塊共用的單位四邊形頂點
        const SimpleVertex vertices[] = {{{-1.0f, -1.0f}}, {{-1.0f, 1.0f}}, {{1.0f, 1.0f}}, {{1.0f, -1.0f}}};

        // 建立四邊形的 Vertex Buffer
        VertexBuffer blockVertexBuffer;

        // 配置可容納所有頂點的 Vertex Buffer
        blockVertexBuffer.init(_countof(vertices), sizeof(vertices[0]));

        // 將頂點資料複製進 Vertex Buffer
        blockVertexBuffer.copy(vertices);

        // 定義組成四邊形兩個三角形的 index
        const std::uint16_t indices[] = {0, 1, 2, 0, 2, 3};

        // 建立四邊形的 Index Buffer
        IndexBuffer blockIndexBuffer;

        // 配置可容納所有 index 的 Index Buffer
        blockIndexBuffer.init(_countof(indices), sizeof(indices[0]));

        // 將 index 資料複製進 Index Buffer
        blockIndexBuffer.copy(indices);

        // 產生所有色塊的 instance 資料
        auto blocks = createColorBlocks();

        // 建立存放所有色塊資料的 Structured Buffer
        StructuredBuffer colorBlockBuffer;

        // 將色塊資料上傳到 Structured Buffer
        colorBlockBuffer.init(sizeof(ColorBlockData), static_cast<int>(blocks.size()), blocks.data());

        // 建立管理 Descriptor 與資源綁定狀態的 RenderState
        RenderState renderState;

        // 初始化 RenderState 使用的 Descriptor Heap
        renderState.init(graphicsEngine.getD3DDevice());

        // 將色塊 Structured Buffer 綁定到 SRV slot 0
        renderState.setStructuredBuffer(0, colorBlockBuffer);

        // 取得 Graphics Engine 使用的 RenderContext
        RenderContext& renderContext = graphicsEngine.getRenderContext();

        // 將目前的 Command List 設定到 RenderContext
        renderContext.SetCommandList(graphicsEngine.getCommandList());

        // 持續處理視窗訊息直到應用程式結束
        while (dispatchWindowMessage())
        {
            // 開始目前 Frame 的繪製流程
            graphicsEngine.beginRender();

            // 綁定目前繪圖使用的 Root Signature
            renderContext.setRootSignature(rootSignature);

            // 綁定色塊繪製使用的 Pipeline State
            renderContext.setPipelineState(pipelineState);

            // 綁定目前 Back Buffer 對應的 Descriptor Heap 與資源表
            renderState.applyGraphics(renderContext, graphicsEngine.getBackBufferIndex());

            // 指定輸入的 index 以 Triangle List 方式組成三角形
            renderContext.setPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

            // 綁定四邊形的 Vertex Buffer
            renderContext.setVertexBuffer(blockVertexBuffer);

            // 綁定四邊形的 Index Buffer
            renderContext.setIndexBuffer(blockIndexBuffer);

            // 使用同一組四邊形 geometry 執行 BlockCount 次 instanced drawing
            renderContext.drawIndexedInstanced(_countof(indices), BlockCount);

            // 結束目前 Frame 並提交繪圖命令
            graphicsEngine.endRender();

            // 輸出 D3D12 Debug Layer 產生的訊息
            SkylineDebugger::LogD3D12Messages(graphicsEngine.getD3DDevice());
        }

        SkylineDebugger::Shutdown();

        return EXIT_SUCCESS;
    }
    catch (const std::exception& exception)
    {
        SkylineDebugger::ShowFatalError("DayX_ColorBlock initialization failed", exception);
        SkylineDebugger::Shutdown();

        return EXIT_FAILURE;
    }
    catch (...)
    {
        SkylineDebugger::ShowFatalError("DayX_ColorBlock initialization failed", "An unknown fatal error occurred.");
        SkylineDebugger::Shutdown();

        return EXIT_FAILURE;
    }
}

結果


上一篇
Day 11:基本 HLSL 介紹
下一篇
Day 13 : 天空背景
系列文
因為 AI 看不懂老舊程式,只好乖乖從零開始學 DirectX 12 與 HLSL13
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言