> ## Documentation Index
> Fetch the complete documentation index at: https://devdocs.xbox.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 深入了解序列化與衍生管線狀態物件

> 深入了解序列化與衍生 Direct3D 12 管線狀態物件 (PSO) 的指南，包括 XBOX 上的執行階段編譯和 DXIL 位元組程式碼。

## 管線狀態物件編譯

D3D12 提供管線狀態物件 (PSO) 介面，用於以著色器和轉譯狀態設定 GPU。建立 PSO 的標準跨平台工作流程，是在建置期間的處理程序中使用 DXC 預先編譯著色器，然後在執行階段以著色器位元組程式碼和狀態填入圖形或計算管線狀態描述元 (例如 **D3D12\_GRAPHICS\_PIPELINE\_STATE\_DESC**)。驅動程式驗證可確保每個後續管線階段的輸入/輸出和狀態一致。

在 PC 上，由於建置時通常無法得知終端使用者的 GPU，因此必須在執行階段編譯 PSO。因此，離線 DXC 處理程序必須編譯成中繼位元組程式碼格式，也就是以 LLVM 為基礎的中繼表示法 DXIL (DirectX 中繼語言)。在執行階段使用其中一個可用的 API (如下) 建立 PSO 時，會促使 GPU 驅動程式將 DXIL 位元組程式碼編譯為 GPU 指令來完成著色器程式碼，之後即可在 GPU 上執行。

*在執行階段：*

```text theme={null}
ID3D12RootSignature* rootSig = nullptr;
D3D12CreateRootSignature(0, shaderBlob->GetBufferPointer(), shaderBlob->GetBufferSize(), IID_GRAPHICS_PPV_ARGS(&rootSig));

ComPtr<ID3DBlob> vsBlob = LoadCsoFromFile(vsFilename);
ComPtr<ID3DBlob> psBlob = LoadCsoFromFile(psFilename);

D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {};
psoDesc.pRootSignature = rootSig;
psoDesc.VS             = { vsBlob->GetBufferPointer(), vsBlob->GetBufferSize() };
psoDesc.PS             = { psBlob->GetBufferPointer(), psBlob->GetBufferSize() };
psoDesc.BlendState     = { ... };
...

ID3D12PipelineState* pso = nullptr;
d3dDevice->CreateGraphicsPipelineState(&psoDesc, IID_GRAPHICS_PPV_ARGS(&pso));

// Render a rabbit, or teapot, or hopefully something cooler
```

相關 API：

* [**ID3D12Device::CreatePipelineState**](https://learn.microsoft.com/windows/win32/api/d3d12/nf-d3d12-id3d12device2-createpipelinestate)
* [**ID3D12Device::CreateGraphicsPipelineState**](https://learn.microsoft.com/windows/win32/api/d3d12/nf-d3d12-id3d12device-creategraphicspipelinestate)
* [**ID3D12Device::CreateGraphicsPipelineStateX**](/zh-TW/reference/graphics/d3d12_x/interfaces/id3d12device/methods/id3d12device_creategraphicspipelinestatex)
* [**ID3D12Device::CreateComputePipelineState**](https://learn.microsoft.com/windows/win32/api/d3d12/nf-d3d12-id3d12device-createcomputepipelinestate)
* [**ID3D12Device::CreateComputePipelineStateX**](/zh-TW/reference/graphics/d3d12_x/interfaces/id3d12device/methods/id3d12device_createcomputepipelinestatex)

## 序列化管線狀態物件

XBOX 平台的硬體設定是固定的，因此不需要隨附著色器程式碼的中繼表示法。這讓 PSO 編譯程序可以移至建置期間進行，免除遊戲啟動時建立 PSO 的額外負荷。XBOX API 延伸已新增將 PSO 序列化為二進位 Blob 格式的功能，可直接儲存至磁碟。在執行階段，可以從磁碟載入這些 Blob，並還原序列化為可運作的管線狀態。PSO 仍然可以在執行階段使用標準 D3D12 功能建立，但序列化提供了最佳化啟動速度的途徑。

由於序列化 PSO 會隨套件組建一起發佈，因此其設計核心之一是將記憶體使用量降到最低。**SerializeGraphicsPipelineStateX** 輸出的 Blob 應在所有預先編譯的 PSO 之間刪除重複項目，以降低記憶體需求。序列化時會捨棄與執行無關的其他中繼資料，因此無法重新序列化先前已還原序列化的 PSO，嘗試這麼做會失敗並出現驗證訊息。序列化 API (如下) 在零售組建中無法使用。

請注意，用來預先編譯 PSO 的處理程序，必須使用從以平台為目標之程式庫建立的 ID3D12Device。D3D12CreateDevice 函式可在 GDK 安裝的 *toolkit* 資料夾中找到，XBOX One 位於 d3d12\_x.h 內，XBOX Series X|S 則位於 d3d12\_xs.h 內，請據此連結對應的 d3d12\_x.lib 或 d3d12\_xs.lib。這些程式庫預期可從工作目錄或透過 **PATH** 系統變數存取 GDK DLL。這些 DLL 位於對應 GDK 版本下的 *bin* 資料夾中，例如 August 2020 QFE 4 的 **C:\Program Files (x86)\Microsoft GDK\200804\GXDK\bin**。

*在建置期間：*

```text theme={null}
ComPtr<ID3D12Device> d3dDevice;
D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_11_0, IID_GRAPHICS_PPV_ARGS(d3dDevice.ReleaseAndGetAddressOf()));
...

D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = { ... };

ID3D12PipelineState* pso = nullptr;
d3dDevice->CreateGraphicsPipelineState(&psoDesc, IID_GRAPHICS_PPV_ARGS(&pso));

D3D12XBOX_SERIALIZE_GRAPHICS_PIPELINE_STATE serializedState;
d3dDevice->SerializeGraphicsPipelineStateX(pso, D3D12XBOX_SERIALIZE_FLAGS_NONE, &serializedState);

WriteSerializedStateToFile(filename, serializedState);
```

*在執行階段：*

```text theme={null}
ID3D12RootSignature* rootSig = nullptr;
D3D12CreateRootSignature(0, shaderBlob->GetBufferPointer(), shaderBlob->GetBufferSize(), IID_GRAPHICS_PPV_ARGS(&rootSig));

D3D12XBOX_SERIALIZE_GRAPHICS_PIPELINE_STATE state = ReadSerializedStateFromFile(filename);

// Assume this data is serialized very simply.
D3D12XBOX_DESERIALIZE_GRAPHICS_PIPELINE_STATE deserialized {};
deserialized.pRootSignature = rootSig;

deserialized.PacketSize     = state.pPacket->GetBufferSize();
deserialized.pPacket        = state.pPacket->GetBufferPointer();
deserialized.MetadataSize   = state.pMetadata->GetBufferSize();
deserialized.pMetadata      = state.pMetadata->GetBufferPointer();

if (state.VS != nullptr)
{
    deserialized.VS.GpuInstructionsSize = state.VS.pGpuInstructions->GetBufferSize();
    deserialized.VS.pGpuInstructions    = state.VS.pGpuInstructions->GetBufferPointer();
    deserialized.VS.MetadataSize        = state.VS.pMetadata->GetBufferSize();
    deserialized.VS.pMetadata           = state.VS.pMetadata->GetBufferPointer();
}
...

ID3D12PipelineState* pso = nullptr;
d3dDevice->DeserializeGraphicsPipelineStateX(&deserializeState, IID_GRAPHICS_PPV_ARGS(&pso));

// Render as normal
```

以下簡要說明每項序列化資料的內容：

**D3D12XBOX\_SERIALIZE\_GRAPHICS\_PIPELINE\_STATE::pPacket**

* 命令處理器執行資訊
* 混色/點陣化/深度樣板狀態的 GPU 狀態
* RTV/DSV 設定的 GPU 狀態
* 頂點/索引處理、鑲嵌和串流輸出的 GPU 狀態
* 每個硬體階段的 GPU 狀態

**D3D12XBOX\_SERIALIZE\_GRAPHICS\_PIPELINE\_STATE::pMetadata**

* 序列化 PSO 的 CRC32 雜湊
* 序列化結構版本號碼
* 驗證資料
* PSO 建立引數的精簡格式
* 輸入配置和串流輸出設定

**D3D12XBOX\_SERIALIZE\_SHADER::pGpuInstructions**

* 預先編譯的硬體著色器 GPU 指令

**D3D12XBOX\_SERIALIZE\_SHADER::pMetaData**

* 序列化結構版本號碼
* 著色器編譯器中繼資料
* PIX 著色器資訊

相關 API：

* [**ID3D12Device::SerializeGraphicsPipelineStateX**](/zh-TW/reference/graphics/d3d12_x/interfaces/id3d12device/methods/id3d12device_serializegraphicspipelinestatex)
* [**ID3D12Device::DeserializeGraphicsPipelineStateX**](/zh-TW/reference/graphics/d3d12_x/interfaces/id3d12device/methods/id3d12device_deserializegraphicspipelinestatex)
* [**ID3D12Device::SerializeComputePipelineStateX**](/zh-TW/reference/graphics/d3d12_x/interfaces/id3d12device/methods/id3d12device_serializecomputepipelinestatex)
* [**ID3D12Device::DeserializeComputePipelineStateX**](/zh-TW/reference/graphics/d3d12_x/interfaces/id3d12device/methods/id3d12device_deserializecomputepipelinestatex)

## 衍生管線狀態物件

透過 D3D12.X API 公開的另一項功能是「衍生」管線狀態物件。此功能會建立現有 PSO 的輕量型變體，僅在硬體狀態 (深度樣板狀態、點陣化器狀態、混色狀態、取樣遮罩和著色器限制) 上有所不同。與 PSO 編譯相比，衍生 PSO 所需的執行階段額外負荷微乎其微。這讓開發人員可以建立單一 PSO 並繫結一組通用著色器，然後衍生出任意數量的變體，而只需負擔初始 PSO 建立的完整執行階段成本。

```text theme={null}
D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc {};
...

ID3D12PipelineState* pso = nullptr;
d3dDevice->CreateGraphicsPipelineState(&psoDesc, IID_GRAPHICS_PPV_ARGS(&pso));

D3D12XBOX_DERIVED_GRAPHICS_STATE_DESC derivedDescs[2] {};
derivedDescs[0].Type                           = D3D12XBOX_DERIVED_GRAPHICS_STATE_TYPE_DEPTH_STENCIL;
derivedDescs[0].DepthStencilDesc               = psoDesc.DepthStencilState;
derivedDescs[0].DepthStencilDesc.StencilEnable = false;

derivedDescs[1].Type                           = D3D12XBOX_DERIVED_GRAPHICS_STATE_TYPE_RASTERIZER;
derivedDescs[1].RasterizerDesc                 = psoDesc.RasterizerState;
derivedDescs[1].RasterizerDesc.DepthBias       = -1;

ID3D12PipelineState* derivedPso = nullptr;
d3dDevice->CreateDerivedGraphicsPipelineStateX(pso, 2, &derivedDescs, IID_GRAPHICS_PPV_ARGS(&derivedPso));

// Render as normal
```

相關 API：

* [**ID3D12Device::CreateDerivedGraphicsPipelineStateX**](/zh-TW/reference/graphics/d3d12_x/interfaces/id3d12device/methods/id3d12device_createderivedgraphicspipelinestatex)
* [**ID3D12Device::CreateDerivedComputePipelineStateX**](/zh-TW/reference/graphics/d3d12_x/interfaces/id3d12device/methods/id3d12device_createderivedcomputepipelinestatex)

## 序列化與衍生管線狀態物件

搭配使用時，序列化與衍生 PSO 可在減少套件大小方面帶來額外的好處。衍生 PSO 可讓遊戲將序列化 PSO 的數量減至遊戲所用完整集合的子集。根據衍生 PSO 覆寫無法變更的屬性來刪除重複的 PSO，即可得到一組「基底」PSO，所有其他遊戲 PSO 都可以從中衍生。

這兩項功能的典型使用案例如下：

* 識別具有根簽章、輸入配置、著色器階段、串流輸出和轉譯目標唯一組合的 PSO。
* 在離線處理程序中序列化這組已刪除重複項目的基底 PSO，並隨套件一起發佈。
* 在啟動時從檔案載入基底 PSO，並衍生遊戲所需的所有 PSO 變體。

## 衍生管線狀態物件的限制

衍生 PSO 的一項顯著限制是它們\_無法\_修改已編譯的著色器程式碼。這就是為什麼輸入配置、轉譯目標格式和串流輸出設定不是 **D3D12XBOX\_DERIVED\_GRAPHICS\_STATE\_DESC** 中的選項，因為這些都需要修改已編譯 PSO 的 GPU 指令。

驅動程式會在 PSO 編譯期間選擇適當的著色器匯出資料類型；支援的著色器匯出類型為 16 或 32 位元的 uint、sint、unorm、snorm 或 float。驅動程式會選取精確度大於或等於轉譯目標精確度、同時符合混色硬體設定的最小類型。為了實作其選擇，驅動程式會以適當的匯出指令修補已編譯的著色器程式碼，這些指令會將著色器運算所用的類型向下轉換為驅動程式所選的匯出類型。修補完成後，驅動程式就不會再修改這些最終的 GPU 指令。

著色器匯出指令的最終選擇取決於幾個關鍵因素：

* HLSL 程式碼寫入的類型和目標數量 (SV\_TargetN)
* PSO 轉譯目標的格式和數量
* Alpha-to-coverage 設定
* 混色狀態設定

在不透明轉譯 (停用混色) 下，輸出類型可以是任何支援的著色器匯出類型：16 或 32 位元的 uint、sint、unorm、snorm 或 float。值會從著色器匯出，由色彩區塊 (CB) 轉換為轉譯目標類型，最後寫入記憶體。啟用混色時，驅動程式會被迫將著色器匯出類型限制為 16 或 32 位元 float，因為這些是 CB 接受作為混色運算輸入的唯一支援類型。混色後的值接著會轉換為轉譯目標類型並寫入記憶體。

這些細節導致衍生 PSO 可套用的混色狀態覆寫存在細微的限制。覆寫的混色狀態可能會使衍生 PSO 的匯出需求，與基底 PSO 編譯期間所決定並已修補至 GPU 指令中的匯出指令選擇不一致。如果基底 PSO 與衍生 PSO 之間任何計算出的匯出不相符，就會擲回驗證錯誤。

以下是一些會導致匯出類型不符而驗證失敗的範例：

*範例 1* -

```text theme={null}
    - Base PSO writes float4 to a DXGI_FORMAT_R32G32_FLOAT format with no blending
    - Driver selects RG export of 32-bit float
    - Derived PSO enables blending, using source alpha in blend equation (destination factor == 1)
    - Result: Shader doesn't export alpha - CB expects alpha export
```

*範例 2* -

```text theme={null}
    - Base PSO writes float4 to DXGI_FORMAT_R16G16B16A16_UNORM with no blending
    - Driver selects RGBA export of 16-bit UNORM
    - Derived PSO enables blending
    - Result: Shader exports UNORM format - CB expects 16 or 32-bit floats
```

*範例 3* -

```text theme={null}
    - Base PSO writes float2 to DXGI_FORMAT_R32G32_FLOAT format with RenderTargetWriteMask == D3D12_COLOR_WRITE_ENABLE_RED
    - Driver selects R export of 32-bit float
    - Derived PSO changes RenderTargetWriteMask == D3D12_COLOR_WRITE_ENABLE_ALL
    - Result: Shader exports only red - artifacts in green
```

## 序列化 PSO 適合您嗎？

雖然序列化 PSO 的建立目的是要降低著色器遊戲內容的套件大小和執行階段成本，但建置和維護這類建置步驟必然會有不小的開發成本。開發人員通常會發現，隨附已移除 DXIL 的預先編譯著色器，即可為其用途提供合適的效能，而且現有的內容管線通常已經支援這種做法。XBOX 驅動程式後端已透過數種快取機制進行最佳化，以降低執行階段記憶體需求和 PSO 編譯時間，讓這條途徑更加可行。

根據遊戲的著色器內容，開發人員可能會發現某一種方法在套件大小、執行階段記憶體或 PSO 編譯時間方面較為理想。避免維護 PSO 序列化內容管線也是另一個考量點。針對您遊戲的內容測試這兩種選項，可讓您更清楚了解哪一種最適合您的遊戲。


## Related topics

- [NDA 主題目錄 - Microsoft Learn 線上連結](/zh-TW/nda/nda-topic-directory.md)
- [Advanced Shader Delivery](/zh-TW/build/gdk-and-engines/guides/advanced-shader-delivery.md)
- [多人游戏会话高级主题](/zh-CN/services/xbox-services/multiplayer/mpsd/concepts/live-mpsd-details.md)
- [移植概览](/zh-CN/paths/porting/overview.md)
- [移植總覽](/zh-TW/paths/porting/overview.md)
