iT邦幫忙

2026 iThome 鐵人賽

DAY 6
0

再繼續之前,前面一篇有出現很多簡寫,在這裡稍微帶過部分常見的內容。

  • CBV (Constant Buffer View): 讓 Shader 讀取 Constant Buffer 中固定參數資料的 View
  • SRV (Shader Resource View): 讓 Shader 以唯讀方式存取 Texture 或 Buffer 資源的 View
  • UAV (Unordered Access View): 讓 Shader 可以對 Texture 或 Buffer 進行任意順序讀寫的 View
  • RTV (Render Target View): 讓 GPU 將渲染結果寫入 Texture 等 Render Target 資源的 View

Root Signature

Root Signature 是用來定義 Shader 能存取哪些資源、程式該如何綁定這些資源的定義格式

typedef struct D3D12_ROOT_SIGNATURE_DESC
{
    UINT NumParameters; // 設定 Root Parameter 的數量

    const D3D12_ROOT_PARAMETER* pParameters; // 指向保存所有 Root Parameter 的陣列

    UINT NumStaticSamplers; // 設定 Static Sampler 的數量

    const D3D12_STATIC_SAMPLER_DESC* pStaticSamplers; // 指向保存所有 Static Sampler 設定的陣列

    D3D12_ROOT_SIGNATURE_FLAGS Flags; // 設定 Root Signature 的額外功能與限制

} D3D12_ROOT_SIGNATURE_DESC;

範例

//root_signature.h

#pragma once

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

class Shader;

// 封裝 Direct3D 12 Root Signature,用來定義 Shader 可存取的 GPU 資源與綁定方式
class RootSignature
{
  public:
    // 使用單一 Static Sampler 設定與 CBV、SRV、UAV 數量初始化 Root Signature
    bool init(D3D12_FILTER samplerFilter, D3D12_TEXTURE_ADDRESS_MODE textureAddressModeU,
              D3D12_TEXTURE_ADDRESS_MODE textureAddressModeV, D3D12_TEXTURE_ADDRESS_MODE textureAddressModeW,
              UINT maxCbvDescriptor = 8, UINT maxSrvDescriptor = 32, UINT maxUavDescriptor = 8);

    // 使用外部 Static Sampler 陣列與 Descriptor Range 設定初始化 Root Signature
    bool init(D3D12_STATIC_SAMPLER_DESC* samplerDescArray, int numSampler, UINT maxCbvDescriptor = 8,
              UINT maxSrvDescriptor = 32, UINT maxUavDescriptor = 8,
              UINT offsetInDescriptorsFromTableStartCB = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND,
              UINT offsetInDescriptorsFromTableStartSRV = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND,
              UINT offsetInDescriptorsFromTableStartUAV = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND);

    // 根據 Shader 物件內提供的資訊初始化 Root Signature
    bool init(Shader& shader);

    // 取得底層的 ID3D12RootSignature 原始指標
    [[nodiscard]]
    ID3D12RootSignature* get() const
    {
        return m_rootSignature.Get();
    }

  private:
    // 保存 Direct3D 12 的 Root Signature COM 物件
    Microsoft::WRL::ComPtr<ID3D12RootSignature> m_rootSignature = nullptr;
};

//root_signature.cpp

#include "root_signature.h"

#include <array>                 
#include <d3dcompiler.h>         
#include <directx/d3dx12_core.h>
#include <stdexcept>
#include <string>
#include <vector>

#include "graphics_engine.h"
#include "shader.h"

namespace
{
    // 場景固定使用四個 Root CBV,分別對應 b0~b3
    constexpr UINT sceneConstantBufferCount = 4;

    // 將 Root Signature 序列化失敗時的錯誤資訊整理成 std::string
    std::string makeSerializationError(ID3DBlob * errorBlob)
    {
        std::string message = "RootSignature: Failed to serialize the root signature.";

        // 如果有錯誤 Blob,就把 Direct3D 回傳的詳細錯誤文字附加到訊息中
        if (errorBlob != nullptr)
        {
            message += "\n";

            // 將 errorBlob 中的文字資料加入 message
            message.append(static_cast<const char*>(errorBlob->GetBufferPointer()), errorBlob->GetBufferSize());
        }

        return message;
    }
}

// 使用簡單的 Sampler 設定建立 Static Sampler,再交給完整版本的 init()
bool RootSignature::init(D3D12_FILTER samplerFilter, D3D12_TEXTURE_ADDRESS_MODE textureAddressModeU,
                         D3D12_TEXTURE_ADDRESS_MODE textureAddressModeV, D3D12_TEXTURE_ADDRESS_MODE textureAddressModeW,
                         UINT maxCbvDescriptor, UINT maxSrvDescriptor, UINT maxUavDescriptor)
{
    // 建立並將所有欄位初始化為 0 的 Static Sampler 描述
    D3D12_STATIC_SAMPLER_DESC samplerDesc{};

    // 設定 Texture 的取樣過濾方式
    samplerDesc.Filter = samplerFilter;

    // 設定 Texture 在 U、V、W 座標超出 0~1 時的處理方式
    samplerDesc.AddressU = textureAddressModeU;
    samplerDesc.AddressV = textureAddressModeV;
    samplerDesc.AddressW = textureAddressModeW;

    // 設定 Anisotropic Filtering 的最大倍率
    samplerDesc.MaxAnisotropy = 1;

    // 設定 Comparison Sampler 的比較方式為永遠通過
    samplerDesc.ComparisonFunc = D3D12_COMPARISON_FUNC_ALWAYS;

    // 設定 BORDER Address Mode 使用的邊界顏色為不透明黑色
    samplerDesc.BorderColor = D3D12_STATIC_BORDER_COLOR_OPAQUE_BLACK;

    // 允許使用到最大的 Mipmap Level
    samplerDesc.MaxLOD = D3D12_FLOAT32_MAX;

    // 限制此 Static Sampler 只能由 Pixel Shader 存取
    samplerDesc.ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;

    // 將建立好的單一 Static Sampler 傳給完整版本的 init()
    return init(&samplerDesc, 1, maxCbvDescriptor, maxSrvDescriptor, maxUavDescriptor);
}

// 建立包含四個 Root CBV 與一個可選 Descriptor Table 的完整 Root Signature
bool RootSignature::init(D3D12_STATIC_SAMPLER_DESC* samplerDescArray, int numSampler, UINT maxCbvDescriptor,
                         UINT maxSrvDescriptor, UINT maxUavDescriptor, UINT offsetInDescriptorsFromTableStartCB,
                         UINT offsetInDescriptorsFromTableStartSRV, UINT offsetInDescriptorsFromTableStartUAV)
{
    // 確認 GraphicsEngine 與 Direct3D 12 Device 已完成初始化
    if (g_graphicsEngine == nullptr || g_graphicsEngine->getD3DDevice() == nullptr)
    {
        throw std::runtime_error("RootSignature: Graphics device is not initialized.");
    }

    // Static Sampler 數量不可為負數
    if (numSampler < 0)
    {
        throw std::invalid_argument("RootSignature: Sampler count must not be negative.");
    }

    // 當 Static Sampler 數量大於 0 時必須提供有效的 Sampler 陣列
    if (numSampler > 0 && samplerDescArray == nullptr)
    {
        throw std::invalid_argument("RootSignature: A sampler array is required when sampler count is non-zero.");
    }

    // 因為 b0~b3 固定作為 Root CBV,所以 CBV 最大數量不可少於 4
    if (maxCbvDescriptor < sceneConstantBufferCount)
    {
        throw std::invalid_argument("RootSignature: At least four constant-buffer slots are required.");
    }

    // 建立四個 Root CBV 加上一個 Descriptor Table 用的 Root Parameter 陣列
    std::array<CD3DX12_ROOT_PARAMETER, sceneConstantBufferCount + 1> rootParameters{};

    // 建立對應 b0~b3 的四個 Root CBV
    for (UINT registerIndex = 0; registerIndex < sceneConstantBufferCount; ++registerIndex)
    {
        rootParameters[registerIndex].InitAsConstantBufferView(
            // 使用目前索引作為 CBV 的 Shader Register
            registerIndex,

            // 使用 register space0
            0,

            // 允許所有 Shader Stage 存取此 Root CBV
            D3D12_SHADER_VISIBILITY_ALL);
    }

    // 保存 Descriptor Table 中需要使用的 CBV、SRV 與 UAV Descriptor Range
    std::vector<CD3DX12_DESCRIPTOR_RANGE> descriptorRanges;

    // 預先保留三個元素空間,避免新增 Range 時重複配置記憶體
    descriptorRanges.reserve(3);

    // 計算扣除 b0~b3 後還需要放進 Descriptor Table 的 CBV 數量
    const UINT additionalCbvDescriptorCount = maxCbvDescriptor - sceneConstantBufferCount;

    // 如果還有 b4 之後的 CBV,就建立對應的 Descriptor Range
    if (additionalCbvDescriptorCount > 0)
    {
        // 在 Descriptor Range 陣列尾端建立一個新的 Range
        descriptorRanges.emplace_back();

        // 將新的 Descriptor Range 設定成 CBV Range
        descriptorRanges.back().Init(
            // 指定此 Descriptor Range 的類型為 CBV
            D3D12_DESCRIPTOR_RANGE_TYPE_CBV,

            // 設定此 Range 包含的 CBV Descriptor 數量
            additionalCbvDescriptorCount,

            // 因為 b0~b3 已被使用,所以此 Range 從 b4 開始
            sceneConstantBufferCount,

            // 使用 register space0
            0,

            // 設定此 Range 在 Descriptor Table 中的起始偏移
            offsetInDescriptorsFromTableStartCB);
    }

    // 如果需要 SRV,就建立一個 SRV Descriptor Range
    if (maxSrvDescriptor > 0)
    {
        // 在 Descriptor Range 陣列尾端建立一個新的 Range
        descriptorRanges.emplace_back();

        // 將新的 Descriptor Range 設定成 SRV Range
        descriptorRanges.back().Init(
            // 指定此 Descriptor Range 的類型為 SRV
            D3D12_DESCRIPTOR_RANGE_TYPE_SRV,

            // 設定可以使用的 SRV Descriptor 數量
            maxSrvDescriptor,

            // SRV 從 t0 開始
            0,

            // 使用 register space0
            0,

            // 設定此 Range 在 Descriptor Table 中的起始偏移
            offsetInDescriptorsFromTableStartSRV);
    }

    // 如果需要 UAV,就建立一個 UAV Descriptor Range
    if (maxUavDescriptor > 0)
    {
        // 在 Descriptor Range 陣列尾端建立一個新的 Range
        descriptorRanges.emplace_back();

        // 將新的 Descriptor Range 設定成 UAV Range
        descriptorRanges.back().Init(
            // 指定此 Descriptor Range 的類型為 UAV
            D3D12_DESCRIPTOR_RANGE_TYPE_UAV,

            // 設定可以使用的 UAV Descriptor 數量
            maxUavDescriptor,

            // UAV 從 u0 開始
            0,

            // 使用 register space0
            0,

            // 設定此 Range 在 Descriptor Table 中的起始偏移
            offsetInDescriptorsFromTableStartUAV);
    }

    // Root Signature 一開始固定有四個 Root CBV
    UINT rootParameterCount = sceneConstantBufferCount;

    // 如果存在任何 Descriptor Range,就將它們組成同一個 Descriptor Table
    if (!descriptorRanges.empty())
    {
        rootParameters[sceneConstantBufferCount].InitAsDescriptorTable(
            // 設定 Descriptor Table 中包含多少個 Descriptor Range
            static_cast<UINT>(descriptorRanges.size()),

            // 傳入 Descriptor Range 陣列的起始位置
            descriptorRanges.data(),

            // 允許所有 Shader Stage 存取此 Descriptor Table
            D3D12_SHADER_VISIBILITY_ALL);

        // 因為新增了一個 Descriptor Table,所以 Root Parameter 數量加一
        ++rootParameterCount;
    }

    // 建立 Root Signature 的完整描述結構
    D3D12_ROOT_SIGNATURE_DESC rootSignatureDesc{};

    // 設定 Root Signature 中實際使用的 Root Parameter 數量
    rootSignatureDesc.NumParameters = rootParameterCount;

    // 指向 Root Parameter 陣列的起始位置
    rootSignatureDesc.pParameters = rootParameters.data();

    // 設定 Root Signature 中的 Static Sampler 數量
    rootSignatureDesc.NumStaticSamplers = static_cast<UINT>(numSampler);

    // 指向 Static Sampler 描述陣列
    rootSignatureDesc.pStaticSamplers = samplerDescArray;

    // 允許 Input Assembler 使用 Input Layout
    rootSignatureDesc.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT;

    // 保存序列化完成後的 Root Signature 二進位資料
    Microsoft::WRL::ComPtr<ID3DBlob> serializedRootSignature;

    // 保存 Root Signature 序列化失敗時的詳細錯誤資訊
    Microsoft::WRL::ComPtr<ID3DBlob> errorBlob;

    // 將 D3D12_ROOT_SIGNATURE_DESC 序列化成 CreateRootSignature() 可使用的 Binary Blob
    const HRESULT serializeResult = D3D12SerializeRootSignature(
        // 傳入要序列化的 Root Signature 描述
        &rootSignatureDesc,

        // 指定使用 Root Signature 1.0
        D3D_ROOT_SIGNATURE_VERSION_1,

        // 接收序列化成功後的 Root Signature Binary Blob
        serializedRootSignature.GetAddressOf(),

        // 接收序列化失敗時的錯誤資訊
        errorBlob.GetAddressOf());

    // 如果 Root Signature 序列化失敗就丟出包含詳細資訊的例外
    if (FAILED(serializeResult))
    {
        throw std::runtime_error(makeSerializationError(errorBlob.Get()));
    }

    // 使用序列化後的 Binary Blob 建立真正的 ID3D12RootSignature 物件
    const HRESULT createResult = g_graphicsEngine->getD3DDevice()->CreateRootSignature(
        // 使用一般單 GPU 的 NodeMask 0
        0,

        // 傳入序列化後 Root Signature Binary 的起始位置
        serializedRootSignature->GetBufferPointer(),

        // 傳入序列化後 Root Signature Binary 的資料大小
        serializedRootSignature->GetBufferSize(),

        // 釋放舊物件並取得可接收新 COM 指標的位址
        IID_PPV_ARGS(m_rootSignature.ReleaseAndGetAddressOf()));

    // 如果建立 ID3D12RootSignature 失敗就丟出例外
    if (FAILED(createResult))
    {
        throw std::runtime_error("RootSignature: Failed to create the root signature.");
    }

    return true;
}

// 從已編譯 Shader Bytecode 中取出 Embedded Root Signature 並建立 Root Signature
bool RootSignature::init(Shader& shader)
{
    // 確認 GraphicsEngine 與 Direct3D 12 Device 已完成初始化
    if (g_graphicsEngine == nullptr || g_graphicsEngine->getD3DDevice() == nullptr)
    {
        throw std::runtime_error("RootSignature: Graphics device is not initialized.");
    }

    // 確認 Shader 已經成功編譯並具有可讀取的 Bytecode
    if (shader.getCompiledBlob() == nullptr)
    {
        throw std::invalid_argument("RootSignature: A compiled shader is required.");
    }

    // 保存從 Shader Bytecode 中抽取出的 Root Signature Binary
    Microsoft::WRL::ComPtr<ID3DBlob> serializedRootSignature;

    // 從 Shader Bytecode 中抽取 Embedded Root Signature
    const HRESULT extractResult = D3DGetBlobPart(
        // 傳入 Shader Binary 的起始位置
        shader.getCompiledBlob()->GetBufferPointer(),

        // 傳入 Shader Binary 的資料大小
        shader.getCompiledBlob()->GetBufferSize(),

        // 指定要抽取 Shader 中的 Root Signature 部分
        D3D_BLOB_ROOT_SIGNATURE,

        // 不使用額外的抽取 Flags
        0,

        // 接收抽取出的 Root Signature Binary Blob
        serializedRootSignature.GetAddressOf());

    // 如果 Shader 中沒有 Embedded Root Signature 或抽取失敗就丟出例外
    if (FAILED(extractResult))
    {
        throw std::runtime_error("RootSignature: The shader does not contain an embedded root signature.");
    }

    // 使用 Shader 中抽取出的 Root Signature Binary 建立 ID3D12RootSignature
    const HRESULT createResult = g_graphicsEngine->getD3DDevice()->CreateRootSignature(
        // 使用一般單 GPU 的 NodeMask 0
        0,

        // 傳入 Root Signature Binary 的起始位置
        serializedRootSignature->GetBufferPointer(),

        // 傳入 Root Signature Binary 的資料大小
        serializedRootSignature->GetBufferSize(),

        // 釋放舊物件並將新建立的 COM 指標寫入 m_rootSignature
        IID_PPV_ARGS(m_rootSignature.ReleaseAndGetAddressOf()));

    // 如果建立 Embedded Root Signature 失敗就丟出例外
    if (FAILED(createResult))
    {
        throw std::runtime_error("RootSignature: Failed to create the embedded root signature.");
    }

    return true;
}

參考資料

D3D12_ROOT_SIGNATURE_DESC (d3d12.h)


上一篇
Day 5 :Graphics Pipeline 介紹(1) - 總覽、PSO
下一篇
Day 7:Graphics Pipeline 介紹(3) - Command List / Command Queue
系列文
因為 AI 看不懂老舊程式,只好乖乖從零開始學 DirectX 12 與 HLSL13
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言