> ## 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.

# DirectStorage 概觀

> 適用於 XBOX Series X|S 的 DirectStorage API 可提供高輸送量的 NVMe 儲存 I/O，並在處理大量小型要求時維持低 CPU 負擔。

## 簡介

本文提供 DirectStorage API 的概觀，僅適用於
XBOX Series X|S 主機。如需桌面版 DirectStorage 的詳細資料，請參閱
[桌面版 DirectStorage](https://aka.ms/directstorage)。

透過 PCIe 匯流排連接的最新 NVMe 儲存裝置可以達到非常
高的輸送量和 IOPS (每秒 I/O 要求數)。Win32 API 的負擔意味著，即使可用的儲存頻寬能夠
被充分利用，但要善用它可能會導致高到無法接受的 CPU
使用率。當工作負載包含大量
小型要求時尤其如此。

DirectStorage API 的設計目的是透過與基礎 NVMe 硬體緊密互動，移除大部分的作業系統
負擔。這可以
以較低的 CPU 使用量達到較高的頻寬。目標是在最多使用
單一 CPU 核心 10% 的情況下，每秒處理多達 50,000 個要求。

### 現有問題

隨著每個主機世代對更高解析度資產的需求增加，遊戲內容
也變得越來越大。現有的 XBOX One 硬體
和軟體有數項限制，妨礙開發人員為次世代內容
將資料從硬碟取得至記憶體的能力。

* CPU 使用量過高
  * 現有的 Win32 API 可能需要耗用一整個 CPU 核心作為負擔。
  * 這取決於遊戲的要求數量。

* 磁碟的最大頻寬不足
  * [將 XBOX Series X|S 上的檔案效能最大化 (NDA 主題)](https://learn.microsoft.com/gaming/gdk/docs/gdk-dev/console-dev/overviews/file-system/Maximizing_File_Performance_Win32_Scarlett) 和[將 XBOX One 上的檔案效能最大化 (NDA 主題)](https://learn.microsoft.com/gaming/gdk/docs/gdk-dev/console-dev/overviews/file-system/Maximizing_File_Performance_Win32_Xbox_One) 白皮書詳細說明了此問題。

* 無法排定磁碟要求的優先順序
  * 若無法排定遊戲要求的優先順序，可能很難建立
    回應迅速的串流系統。

* 無法取消磁碟要求
  * 若無法取消要求，可能很難建立
    推測性讀取系統。

* 沒有硬體加速解壓縮
  * 若沒有硬體加速解壓縮，在軟體中執行解壓縮可能需要耗用大量 CPU
    資源。

DirectStorage API 集直接解決了上述每一個問題。整體
效果是大幅提升 XBOX 檔案系統的效能。

### CPU 使用量

DirectStorage 的主要設計目標是讓遊戲能夠維持 50K IOPS，
且僅使用單一 CPU 核心的 5% 到 10%。這讓
遊戲能夠從 NVMe 儲存子系統達到最大頻寬，同時
讓 CPU 可用於遊戲的其他需求。

DirectStorage 也新增了硬體解壓縮的支援。每個讀取要求
都可以直接從 NVMe 磁碟機路由至內建的硬體
解壓縮區塊。這讓遊戲不需要在解壓縮上
耗用 CPU 資源。

### 佇列管線模型

DirectStorage 使用批次方法，將多個要求新增至佇列。
在稍後的時間點，佇列會排清至下一個管線階段。這
可以立即降低管線階段之間轉換的整體 CPU 成本。
在現有的 Win32 API 集下，每個要求都會有一次轉換。
DirectStorage 佇列使用無鎖定演算法將競爭降至最低。
遊戲可以控制每個佇列何時排清。

在許多使用 Win32 API 的情況下，來自磁碟的資料可能需要複製
到另一個緩衝區。在某些情況下，資料可能需要複製不只一次。
DirectStorage 透過將遊戲提供的目的地
緩衝區直接對應至每個管線層來消除此問題。硬體會直接寫入
遊戲所提供的緩衝區。

這些變更有助於大幅降低 CPU 負擔。

### 解壓縮

硬體解壓縮資料的能力已獲得提升。它現在可以
處理更多樣的格式，且速度高於 NVMe 子系統
所能提供資料的速度。此外，DirectStorage 支援就地解壓縮，因此
不需要為壓縮和解壓縮的資料管理個別的
緩衝區。

硬體支援 `BCPACK`、`DEFLATE`，並提供對最終內容進行 swizzle 的
能力。這些格式並非互斥。可以將
三者全部套用至資料。這讓遊戲能夠選擇哪種
方法可提供最佳的壓縮比和效能。不同的資產
可以使用不同的壓縮和 swizzle 設定。

### 佇列深度

先前的建議是在旋轉式磁碟機上，同一時間只維持 12-16 個進行中的非同步
要求。再增加也不會對效能有任何好處，而
減少則會大幅損害效能。這導致遊戲
需要執行額外的工作來平衡其未完成的讀取要求，以維持在
建議的目標範圍內。

由於 DirectStorage 的目標是讓遊戲達到 50,000 IOPS，我們的
建議已有所改變。遊戲不再需要嘗試在
未完成的工作與佇列深度之間取得平衡。遊戲應提交其所有未完成的
要求。保留部分要求沒有任何好處。在許多情況下，
保留要求可能會損害效能，因為硬體會停滯，等待
新的要求。

在某些情況下 (例如處理磁碟片段)，作業系統仍需要將較大的讀取要求分割成
數個較小的要求。
不過，DirectStorage 架構已考量到這一點。50,000 IOPS 的
設計目標是以遊戲的 IO 作業數量為基礎，而不是以最終
送往硬體的要求為基礎。

### 通知

在 Win32 架構中，有相當多的負擔花費在
讀取完成的通知上。遊戲可以輪詢 *OVERLAPPED* 結構、
等待相關聯的 Event 控制代碼，或執行同步
封鎖讀取。整體而言，這會增加每個讀取
要求的資源需求。

DirectStorage 保留了兩種非同步通知概念，同時也
新增了第三種方法。DirectStorage 不支援同步封鎖讀取，
遊戲可以實作自己的系統。不過，
不建議這麼做。

第一種非同步方法是透過狀態區塊來實作，該區塊會在
相關聯的要求完成時設定。遊戲可以視需要輪詢該區塊，
以判斷讀取何時完成。這類似於 Win32 中
輪詢 *OVERLAPPED* 結構以確認完成的方法。

第二種非同步方法是使用 Windows `Event` 物件來發出完成訊號。
這類似於搭配對應的 `Event` 物件使用 *OVERLAPPED* 結構。
遊戲可以使用 [WaitForSingleObject](https://learn.microsoft.com/windows/win32/api/synchapi/nf-synchapi-waitforsingleobject)
方法讓呼叫執行緒暫停，直到讀取作業完成為止。

第三種非同步方法是使用 [ID3D12Fence](https://learn.microsoft.com/windows/win32/api/d3d12/nn-d3d12-id3d12fence) 來實作。遊戲
可以暫停以等待柵欄，也可以視需要輪詢柵欄。
另一個好處是 GPU 可以使用柵欄直接取得
已完成要求的通知。

DirectStorage 通知系統並未繫結至單一讀取要求。它是
放在佇列中的一個項目，會在所有先前的讀取
要求都完成時發出訊號。這讓遊戲可以控制通知
所需的細微度。通知一律依佇列順序發出訊號。
佇列可視為 FIFO (先進先出) 佇列。遊戲只
需要查詢最後一個相關的通知。所有先前排入佇列的要求
都保證已完成。

### 記憶體對記憶體解壓縮

DirectStorage 提供一種佇列類型，以記憶體而非磁碟檔案作為
解壓縮來源來叫用解壓縮硬體。如此一來，若壓縮的資產並非來自
檔案，或先前已從檔案取得並保留在記憶體中作為快取，仍可
使用解壓縮硬體。
記憶體來源佇列只接受記憶體來源要求，而檔案來源
佇列只接受檔案來源要求。

如果記憶體來源要求中未指定任何解壓縮選項，
解壓縮硬體也可以作為 DMA 複製引擎。

雖然 DirectStorage 保證完成通知會依序發出，但
DirectStorage 不保證要求何時開始處理。
因此，待處理的要求之間不得有任何資料相依性。也
就是說，要求 A 的目的地不能作為要求 B 的來源，除非要求
B 是在要求 A 完成後才排入佇列。

記憶體來源佇列必須以即時優先順序建立。此外，
記憶體來源的即時要求一律會在需要解壓縮的磁碟來源要求之前
由解壓縮硬體處理。
如果磁碟來源佇列沒有任何解壓縮要求，這兩種
佇列類型會完全平行處理，彼此互不影響。

### 優先順序

DirectStorage 允許為每個佇列指派優先順序層級。佇列中的每個項目
都會繼承佇列的優先順序。提供四種不同的優先順序層級：
即時、高、一般和低。要求會以加權循環配置
方式處理。例如，先處理 X 個高優先順序的要求，再處理
一個一般優先順序的要求。先處理 Y 個一般優先順序的要求，再
處理一個低優先順序的要求。

優先順序加權是根據每個要求的大小計算。各優先順序之間的
預設加權大約為 10 倍。這表示每處理 1 KB 的低優先順序
要求，就會處理 10 KB 的中優先順序要求和 100 KB 的高優先順序要求。

現有的 Win32 讀取要求會透過相同的優先順序系統路由。所有
Win32 要求都視為一般優先順序。

記憶體來源佇列必須以即時優先順序建立。

### 取消

每個 DirectStorage 讀取要求都有一個由遊戲提供的 64 位元遮罩與其
相關聯。這是為了支援取消待處理的讀取要求。遊戲可以
取消符合遮罩中特定旗標集的要求。

即使支援取消，讀取要求仍可能會
由硬體處理。遊戲的取消要求是盡力而為的
嘗試。如果要求已由硬體主動處理中，則
無法取消。

由於取消要求是盡力而為，遊戲必須等到
收到讀取要求已完成處理的通知為止。遊戲
在收到佇列中較後面的通知之前，不能釋放任何所需的資源。
不過，在此期間，符合先前取消要求所用旗標的新要求
可以排入佇列，且不會遭到取消。

已取消的要求完成時，會被視為**成功**，即使它
已遭取消且未產生完整結果也一樣。換句話說，如果
嘗試對某個要求進行取消，遊戲在完成時就不能再使用
該可能已遭取消之要求的結果。

### 保證

XBOX One 和 XBOX One S 主機的最低保證為 40 MB/s。
XBOX One X 主機將最低保證提高到 60 MB/s。這些數字
遠低於實際的硬體限制 (約在 130 MB/s 範圍內)。這
完全是作業系統造成的負擔所致。

DirectStorage 移除了作業系統造成的大部分負擔。這
讓最低保證得以更接近硬體限制。新的最低
效能保證是原始資料在 250 毫秒的時間範圍內達到 2.0 GB/s。對內容使用
解壓縮會進一步提高最終頻寬。

未來的 XBOX 主機將支援新增使用者可自行安裝的動態
磁碟機，這些磁碟機也是以 NVMe 為基礎。為內部磁碟機提供的
相同最低效能保證，也同樣適用於使用者可安裝的磁碟機。

## API 概觀

DirectStorage 介面遵循與 Direct3D 介面相同的模式。
遊戲一開始會取得單一處理站。處理站用來建立
要求佇列和開啟檔案，這些物件都會直接對應
至硬體。接著，個別要求會排入佇列中，以
提交至硬體。

### IDStorageFactoryX

`IDStorageFactoryX` 是用來建立佇列、開啟檔案以及
提交待處理要求的主要介面。

`IDStorageFactoryX `物件具有下列方法。

* [OpenFile](/zh-TW/reference/system/dstorage/interfaces/IDStorageFactoryX/methods/idstoragefactoryx_openfile)
  * 建立代表一個檔案的 `IDStorageFileX `物件。

* [CreateQueue](/zh-TW/reference/system/dstorage/interfaces/IDStorageFactoryX/methods/idstoragefactoryx_createqueue)
  * 建立 `IDStorageQueueX` 物件。用來建立讀取要求。

* [CreateStatusArray](/zh-TW/reference/system/dstorage/interfaces/IDStorageFactoryX/methods/idstoragefactoryx_createstatusarray)
  * 建立 `IDStorageStatusArray` 物件，用來管理完成狀態
    旗標。

* [SetCPUAffinity](/zh-TW/reference/system/dstorage/interfaces/IDStorageFactoryX/methods/idstoragefactoryx_setcpuaffinity)
  * 將 DirectStorage 在呼叫執行緒以外的工作，限制在遊戲定義的一組
    CPU 核心上。
  * **注意** DirectStorage 會嘗試在呼叫執行緒中
    完成大部分的工作。只有在工作無法於呼叫執行緒中完成時，才會在呼叫執行緒以外
    進行工作。範例如下。
    * 在 `IDStorageQueueX::Submit` 期間基礎資源管線已滿，
      且並非佇列中的所有要求都能向前推進。其餘
      要求會在稍後資源釋出時處理，這是
      在 DirectStorage 背景工作執行緒中完成。
    * 將要求完成處理至 `ID3DFence` 或 `IDStorageStatusArray`。

* [SetDebugFlags](/zh-TW/reference/system/dstorage/interfaces/IDStorageFactoryX/methods/idstoragefactoryx_setdebugflags)
  * 控制 DirectStorage 是否要在要求排入佇列時進行額外的
    驗證，以協助偵錯。

* [SetStagingBufferSize](/zh-TW/reference/system/dstorage/interfaces/IDStorageFactoryX/methods/idstoragefactoryx_setstagingbuffersize)
  * 設定暫存緩衝區的大小，此緩衝區用來暫時儲存從儲存裝置載入、
    尚未解密/解壓縮的內容。如果只使用
    記憶體來源佇列，暫存緩衝區的大小可以是 0。

### IDStorageFactoryX1

`IDStorageFactoryX1` 介面以
[GetStats](/zh-TW/reference/system/dstorage/interfaces/IDStorageFactoryX1/methods/idstoragefactoryx1_getstats) 方法擴充 `IDStorageFactoryX` 介面。

* [GetStats](/zh-TW/reference/system/dstorage/interfaces/IDStorageFactoryX1/methods/idstoragefactoryx1_getstats)
  * 取得 DirectStorage 統計資料。此函式可用來將
    DirectStorage 與現有的診斷和遙測管線整合。
    它只進行最少量的處理，因此可以經常呼叫。統計資料
    不包含 Win32 檔案 IO 作業。

### IDStorageFactoryX2

`IDStorageFactoryX2` 介面以
[CreateQueue1](/zh-TW/reference/system/dstorage/interfaces/IDStorageFactoryX2/methods/idstoragefactoryx_createqueue1) 方法擴充 \`IDStorageFactoryX1 介面。

* [CreateQueue1](/zh-TW/reference/system/dstorage/interfaces/IDStorageFactoryX2/methods/idstoragefactoryx_createqueue1)
  * 建立 `IDStorageQueueX2` 物件。使用新的結構以允許
    在建立佇列時使用額外選項，包括覆寫
    佇列自動提交功能的能力。

### IDStorageFileX

所有檔案一開始都需要由 DirectStorage 透過
`IDStorageFactoryX` 物件開啟。這相當於在 Win32
API 介面中使用 `CreateFile`。

檔案會以 `FILE_SHARED_READ` 權限開啟。如有需要，只要遵守適當的
權限，遊戲可以同時使用 Win32 API 開啟該檔案。在開發期間，鬆散和封裝的部署都受到支援。

檔案會在明確呼叫檔案物件上的 [Close](/zh-TW/reference/system/dstorage/interfaces/IDStorageFileX/methods/idstoragefilex_close)
函式時關閉，或在相符的
`IDStorageFileX` 物件的最後一個參考被釋放時關閉。不過，所有未完成的 I/O 作業都必須
完成，檔案才能關閉。這表示兩種關閉
檔案的方法都會封鎖，直到該檔案上所有未完成的 I/O 作業都完成為止。

遊戲可以呼叫 [GetHandle](/zh-TW/reference/system/dstorage/interfaces/IDStorageFileX/methods/idstoragefilex_gethandle) 函式，
取得 `IDStorageFileX` 物件所代表之檔案的 win32 控制代碼。
此控制代碼會以 `GENERIC_READ` 權限和 `FILE_SHARE_READ` 共用模式開啟。
它可用於查詢檔案大小等用途。不再需要時，應使用
`CloseHandle()` 關閉該控制代碼。

### IDStorageQueueX

讀取要求會透過 `IDStorageQueueX` 物件提交至 NVMe。
不過，要求要等到遊戲在佇列上呼叫
[Submit](/zh-TW/reference/system/dstorage/interfaces/IDStorageQueueX/methods/idstoragequeuex_submit)，或者其中一個 Enqueue 方法
自上次提交以來填滿了佇列容量的一半以上並觸發
自動提交時，才會提交至裝置。提交會以單一轉換的方式處理，送往
管線中的下一個階段。這讓遊戲可以控制在遊戲與核心之間轉換的
CPU 成本於何時發生。

`IDStorageQueueX` 物件有四個屬性。

* [SourceType](/zh-TW/reference/system/dstorage/enums/dstorage_request_source_type)
  * 指定佇列可以接收檔案來源要求或
    記憶體來源要求。

* [Priority](/zh-TW/reference/system/dstorage/enums/dstorage_priority)
  * 提交至佇列之所有要求的優先順序：即時、高、一般或低。
  * 記憶體來源佇列必須以即時優先順序建立。
  * 要求會根據優先順序，以加權循環配置順序處理。
  * Win32 要求會以一般優先順序層級處理。

* **Capacity**
  * 佇列可容納的未完成要求數目上限。
  * 在佇列已達容量時嘗試將要求排入佇列，會封鎖
    直到硬體完成項目為止。
  * 佇列所需的記憶體量約為佇列容量
    乘以 `DSTORAGE_REQUEST` 的大小。

* **Name**
  * 這純粹是為了協助偵錯。任何
    DirectStorage 程式碼都不會使用此名稱，但它會顯示在開發人員工具中，例如
    [PIX (NDA 主題)](/zh-TW/tools/tools-console/pix/pix-directstorage)。

硬體會以非同步方式處理要求，以達到最高的
輸送量。不過，與 Win32 不同的是，遊戲會依 FIFO
順序收到完成通知。收到完成通知時，保證同一佇列中
所有先前的要求也都已完成。

### IDStorageQueueX1

`IDStorageQueueX1` 介面以
[EnqueueSetEvent](/zh-TW/reference/system/dstorage/interfaces/IDStorageQueueX1/methods/idstoragequeuex_enqueuesetevent) 方法擴充 `IDStorageQueueX` 介面。

### IDStorageQueueX2

`IDStorageQueueX2` 介面以 [CreateQueue1](/zh-TW/reference/system/dstorage/interfaces/IDStorageFactoryX2/methods/idstoragefactoryx_createqueue1) 方法擴充 \`IDStorageQueueX1 介面。

以及一個額外的屬性。

* [Options](/zh-TW/reference/system/dstorage/structs/dstorage_queue_options)
  * 用來控制佇列行為的旗標，包括停用自動提交。

### EnqueueRequest

此介面在功能上與 Win32 `ReadFile` 介面相同。
個別的讀取要求會建立並提交至佇列。主要
差異在於 DirectStorage 允許在提交前將許多要求排入佇列、
支援硬體解壓縮，並支援取消。

要求有數個主要屬性。

**要求的來源**
根據 `Options.SourceType` 和
`Options.SourceIsPhysicalPages` 的組合，DirectStorage 會使用下列三組
屬性之一來指定來源資料所在的位置。

* **File** 和 **FileOffset**
  * 當 `Options.SourceType` 為 `DSTORAGE_REQUEST_SOURCE_FILE` 時使用此組
  * **File** 先前已使用 `IDStorageFactoryX::OpenFile` 開啟。
  * 如果使用解壓縮，**FileOffset** 需要對齊 16 位元組；
    如果未使用解壓縮，則沒有對齊需求。
    * 這與 Win32 有重大差異，Win32 的非同步讀取需要在檔案內
      對齊 4 KiB。

* **Source**
  * 當 `Options.SourceType` 為 `DSTORAGE_REQUEST_SOURCE_MEMORY`
    且 `Options.SourceIsPhysicalPages` 為 `FALSE` 時使用此組。
  * 存放要解壓縮之資料的記憶體緩衝區。

* **SourcePageArray** 和 **SourcePageOffset**
  * 當 `Options.SourceType` 為 `DSTORAGE_REQUEST_SOURCE_MEMORY`
    且 `Options.SourceIsPhysicalPages` 為 `TRUE` 時使用此組。
  * 與 `Source` 類似，但以 64 KB 實體分頁陣列的形式
    提供來源記憶體緩衝區，以及第一個分頁中的位元組位移。
  * 64 KB 實體分頁可透過 `XMemAllocatePhysicalPages` 配置。

**SourceSize**

* 要從記憶體緩衝區或檔案讀取之來源資料的大小
  (以位元組為單位)。

**IntermediateSize**

* 當此要求中同時啟用 `zlib` 和 `BCPACK`解壓縮時，
  `IntermediateSize` 用來指定來源資料經 zlib 解壓縮後
  (以及 BCPACK 解壓縮前) 的中繼大小。
* 否則，應將其設定為 0。

**要求的目的地**
根據 `Options.DestinationIsPhysicalPages`，DirectStorage 會使用下列
兩組屬性之一來指定目的地所在的位置。

* **Destination**
  * 當 `Options.DestinationIsPhysicalPages` 為 `FALSE` 時使用此組。
  * 最終載入資料的目的地緩衝區。
  * 解壓縮會使用共用的內部緩衝區進行，可視為
    就地解壓縮。

* **DestinationPageArray** 和 **DestinationPageOffset**
  * 當 `Options.DestinationIsPhysicalPages` 為 `TRUE` 時使用此組。
  * 與 `Destination` 類似，但以 64 KB 實體分頁陣列的
    形式提供目的地記憶體緩衝區，以及第一個
    分頁中的位元組位移。
  * 64 KB 實體分頁可透過 `XMemAllocatePhysicalPages` 配置。

**DestinationSize**

* 最終載入內容的預期大小 (以位元組為單位)。目的地
  必須包含足夠的空間以容納此作業。
* 未使用解壓縮時，大小必須等於 **SourceSize**；使用解壓縮時，
  則必須大於 **SourceSize**。

**CancellationTag**

* 由遊戲定義的任意 64 位元標記。
* 此標記用作取消要求的遮罩。

**Name**

* 用來協助偵錯的選用字串。Name 可以出現在開發人員
  工具中，例如 [PIX (NDA 主題)](/zh-TW/tools/tools-console/pix/pix-directstorage)，或出現在從
  `IDStorageQueueX::RetrieveErrorRecord` 取得的錯誤記錄中。`Name` 字串必須
  在要求的整個存留期間都可存取。

**Options**

* **ZlibDecompress**
  * 表示資料需要使用 RFC 1950
    解壓縮標準進行解壓縮。
* **BcpackMode**
  * 表示應使用哪種 `BCPACK` 模式來解壓縮資料。
  * None 是有效的選項，表示資料未經 `BCPACK` 壓縮。
* **SwizzleMode**
  * 表示最終資料在記憶體中應如何進行 swizzle。
* **DestinationIsPhysicalPages**
  * 表示目的地緩衝區是使用
    **DestinationPageArray** 和 **DestinationPageOffset** 指定，而不是
    **Destination**。
* **SourceType**
  * 要求可以是記憶體來源 (因此具有 `Source`/
    `SourcePageArray` 和 `SourcePageOffset` 屬性)，或檔案來源 (因此
    具有 `File`/`FileOffset` 屬性)。
* **SourceIsPhysicalPages**
  * 表示來源緩衝區是使用 **SourcePageArray**
    和 **SourcePageOffset** 指定，而不是 **Source**。

### EnqueueStatus/EnqueueSignal/EnqueueSetEvent

要求可以排入佇列，並視為一系列相關的要求。做法是
將通知排入佇列，以便在處理到達佇列中的特定位置時收到通知。
只有在所有先前的讀取要求都完成時，才會處理通知。這保證
所有先前要求的資料都能立即使用。

遊戲有兩種輪詢方法和一種等待方法可用於通知。
遊戲可以插入 `ID3D12Fence` 物件、`IDStorageStatusArrayX` 物件或
設定事件作業。`ID3D12Fence` 的行為與 `ID3D12Fence`
物件的預期行為相同。遊戲執行緒可以等待 `Event`、CPU 可以輪詢柵欄，而
GPU 也可以輪詢柵欄。`IDStorageStatusArrayX` 物件允許 CPU 輪詢
完成狀態，並存取可能的讀取失敗。`EnqueueSetEvent`
方法讓遊戲執行緒可以等待指定的事件，而不需輪詢。
這與 `ID3D12Fence::SetEventOnCompletion` 不同，因為 XBOX 對
`ID3D12Fence::SetEventOnCompletion` 的實作會在柵欄上空轉直到收到訊號為止，
因而在收到訊號前持續耗用 CPU 硬體執行緒；而 `EnqueueSetEvent`
則讓遊戲執行緒可以使用 `WaitForSingleObject`/`WaitForMultipleObjects`，
在事件收到訊號前將 CPU 讓給其他執行緒。

如先前所述，所有要求都會依序完成，即使基礎
硬體為了效能而決定重新排序也一樣。通知要等到
佇列上所有先前排入的要求都完成後，才會發出訊號。

### 解壓縮

解壓縮由專用硬體處理。這消除了傳統解壓縮演算法的 CPU 負擔。
DirectStorage 會在初始化期間配置固定的記憶體區塊，
作為解壓縮的工作緩衝區。
這允許就地解壓縮，且不需要同時在記憶體中保存
壓縮和解壓縮的資料。

解壓縮硬體支援三種操作模式。這些模式並非
互斥，因此可以指定任何模式組合。解壓縮
模式會依下列順序套用：`DEFLATE`、`BCPACK` 和 `Swizzle`。

* `ZLibDecompress`
  * 這是 [IETF RFC 1950](https://www.ietf.org/rfc/rfc1950.txt) 壓縮標準。

* `BCPack`
  * `BCPack` 是專為 BCn 資料設計的自訂熵編碼器。
    一般而言，這表示色彩端點會與
    調色盤索引 (也就是權重) 分開，並使用 rANS 演算法進行壓縮。

* `Swizzle`
  * `Swizzle` 和隨機排列模式可以在內容管線中提供額外的最佳化。

壓縮高熵資料時，壓縮實際上可能會
使大小變大。相反地，對應的解壓縮會使大小
縮小。DirectStorage 不允許縮小的解壓縮，遊戲必須自行
偵測無法壓縮的高熵資料，並避免
對這類資產進行壓縮。如需進一步的詳細資料，請參閱[使用 DirectStorage 和 XBTC
最佳化壓縮內容 (NDA 主題)](/zh-TW/build/console-features/storage/directstorage/directstorage-compression) 指南。

### 暫存緩衝區

DirectStorage 在內部使用緩衝區來暫存從原始
NVMe 儲存空間讀取的所有內容，然後再執行解密和
解壓縮等作業。此暫存緩衝區讓 NVMe 磁碟機與解密/解壓縮晶片能夠以管線方式平行運作。其預設為 32 MiB，
並會在擷取第一個 DirectStorage 處理站指標時配置。

如果遊戲只將 DirectStorage 用於記憶體對記憶體的解壓縮
作業，就不需要暫存緩衝區，可以呼叫
[SetStagingBufferSize](/zh-TW/reference/system/dstorage/interfaces/IDStorageFactoryX/methods/idstoragefactoryx_setstagingbuffersize)
將暫存緩衝區大小設定為 0。

目前版本的 DirectStorage 支援 0、16、20、24、28 和 32 MiB 的暫存緩衝區大小。小於預設值的大小可以為遊戲節省記憶體，但可能會影響整體讀取效能。

**注意：**[SetStagingBufferSize](/zh-TW/reference/system/dstorage/interfaces/IDStorageFactoryX/methods/idstoragefactoryx_setstagingbuffersize)
只能在不存在任何 `IDStorageQueueX` 物件或 `IDStorageFileX` 物件時呼叫，否則會擲回錯誤。

### CancelRequestsWithTag

DirectStorage 支援取消要求。每個要求都有一個與其相關聯、
由遊戲定義的 64 位元標記。其用途是作為位元遮罩，決定
要取消哪些要求。遊戲會提供用於取消的遮罩和
值。佇列會嘗試取消所有符合
準則的要求：`tag & mask == value`。

取消是盡力而為的作業。根據要求在管線中的位置，
可能無法取消。例如，要求可能
正由硬體進行解壓縮，而這是無法取消的。API
會立即傳回，不會封鎖等待所有已取消的要求
處理完畢。遊戲必須等到佇列中較後面的通知發出訊號後，
才能釋放與已取消要求相關聯的資源。

必須小心避免在呼叫
[CancelRequestsWithTag](/zh-TW/reference/system/dstorage/interfaces/IDStorageQueueX/methods/idstoragequeuex_cancelrequestswithtag) 的同時將要求新增至佇列。
在此情況下，行為是未定義的。不過，在
[CancelRequestsWithTag](/zh-TW/reference/system/dstorage/interfaces/IDStorageQueueX/methods/idstoragequeuex_cancelrequestswithtag)
的呼叫傳回後才新增至佇列的要求，即使符合準則也不會遭到取消。只有先前
排入佇列的要求會遭到取消。

### GetErrorEvent/RetrieveErrorRecord

如果讀取發生錯誤，該讀取會標示為已完成。佇列中
後續的通知不會因此無法發出訊號。錯誤的通知
是透過與佇列相關聯的 `Event` 物件處理，該物件可以
使用 [GetErrorEvent](/zh-TW/reference/system/dstorage/interfaces/IDStorageQueueX/methods/idstoragequeuex_geterrorevent) 擷取。遊戲可以
對 [GetErrorEvent](/zh-TW/reference/system/dstorage/interfaces/IDStorageQueueX/methods/idstoragequeuex_geterrorevent) 傳回的 `Event`
使用 **WaitForSingleObject**。如果事件收到訊號，
遊戲可以呼叫 [RetrieveErrorRecord](/zh-TW/reference/system/dstorage/interfaces/IDStorageQueueX/methods/idstoragequeuex_retrieveerrorrecord)
函式，取得自上次呼叫
[RetrieveErrorRecord](/zh-TW/reference/system/dstorage/interfaces/IDStorageQueueX/methods/idstoragequeuex_retrieveerrorrecord) 函式以來的
第一個錯誤。

[RetrieveErrorRecord](/zh-TW/reference/system/dstorage/interfaces/IDStorageQueueX/methods/idstoragequeuex_retrieveerrorrecord) 傳回的
錯誤記錄只包含自上次
[RetrieveErrorRecord](/zh-TW/reference/system/dstorage/interfaces/IDStorageQueueX/methods/idstoragequeuex_retrieveerrorrecord) 以來，佇列中第一個失敗要求的
資料。如果錯誤 `Event` 未收到訊號，或資料已
被擷取，則錯誤記錄中的資料是未定義的。

### Query

取得佇列的相關資訊。其中包括用來建立佇列的 [DSTORAGE\_QUEUE\_DESC](/zh-TW/reference/system/dstorage/structs/dstorage_queue_desc) 或 [DSTORAGE\_QUEUE\_DESC1](/zh-TW/reference/system/dstorage/structs/dstorage_queue_desc1)
結構，以及空白位置的數目
和觸發自動提交所需排入佇列的項目數目。

## 最佳做法

針對 Win32 提供的最佳做法建議同樣適用於 DirectStorage。
不過，達到最佳效能的閾值已大幅改變。

### 讀取大小

針對旋轉式磁碟的原始建議是以至少 128 KiB 的
區塊進行讀取。效能會隨著區塊大小增加而持續提升。512
KiB 大小的區塊可達到最佳效能。

NVMe 沒有移動零件，因此閾值低得多。讀取
效能從 32 KiB 的讀取開始大幅躍升，並在
64 KiB 時開始達到頂峰。讀取大於此大小並不會提升效能。這表示
為了達到最佳效能，在套件中將資料合併成較大區塊
所需的心力較少。

超過 512 KiB 時，如果使用解壓縮，建議使用較小但數量較多的平行要求，
而不是單一巨大要求。單一巨大要求會迫使
解壓縮序列化，而多個並行要求則允許
多個解壓縮硬體單元平行運作，達到完整的
輸送量。

在 Microsoft Game Development Kit (GDK) 的 October 2022 版本中，單一要求的大小上限
已從以目的地計算的 32 MiB，提高為以合併 (來源加上
目的地) 記憶體使用量計算的 1 GiB。這是為了方便從其他允許
大型讀取大小的儲存 API 移植而提供。但先前為了達到
最大輸送量的大小建議仍維持不變，因為大型要求無法讓
多個解壓縮硬體單元平行運作。

### 順序

先前使用旋轉式磁碟時，會花費心力排序磁碟上
讀取的位置。理想情況是從磁碟上的連續位置讀取。
這可讓磁碟讀寫頭的移動量降到最低，進而
排除搜尋時間這個因素。這麼做可以將效能提升一個數量
級。即使是依位置排序後再提交隨機讀取也有好處，
在某些情況下可快 2 倍。

在 NVMe 磁碟機上，將讀取要求排序得盡可能連續仍然
有用。NVMe 磁碟機會以 64 KiB 對齊的區塊進行讀取。因此，
讀取 64 KiB 區塊中未使用的部分可能會浪費頻寬。如果
讀取要求只有 4 KiB，就會浪費 60 KiB 的頻寬。
NVMe 會盡可能重複使用那額外的 60 KiB 來滿足其他待處理的要求。
例如，如果您有兩個連續讀取，一個為 32 KiB，接著一個為 8 KiB，
仍然只會從磁碟機進行一次 64 KiB 的讀取。

### 佇列管理

針對旋轉式磁碟的建議是將佇列大小維持在 12 到
16 之間。較大的佇列深度沒有任何好處，而使用較小的佇列深度
則會造成顯著的效能降低。

NVMe 規格指出，NVMe 磁碟機應支援多個佇列，每個佇列的
深度最多可達 65,536 個項目。DirectStorage 支援此需求，
因此允許遊戲一次提交數千個要求。

先前使用旋轉式磁碟時，遊戲會緩衝待處理的要求，以將
佇列深度維持在 12 到 16 的範圍內。針對 DirectStorage 的建議是
不要緩衝要求，而是在要求建立後立即將其排入佇列。整個
系統是一條管線，遊戲的緩衝會在管線中產生空泡，
這可能會嚴重損害效能。

另一個建議是建立容量至少為每個畫面格所建立之最大要求數目
四倍的佇列。這應能提供足夠的
容量，讓新要求可以在不停滯、不需等待現有
要求完成的情況下新增。

### 通知管理

一般而言，新增至佇列的通知要求越少越好。
建議在遊戲需求與將排入佇列的
通知要求維持在最低限度之間取得平衡。在每個要求之後都將通知排入佇列，
只會因為處理這些通知所增加的負擔而
損害整體效能。

其中一個範例可能是依內容分組。例如，SFS 紋理、地形
需求 (例如網格和紋理)，以及角色需求 (例如網格、
紋理和動畫)。這可讓單一通知繫結至
建立物件所需之所有資產的可用性。

要使用 `ID3D12Fence` 還是狀態陣列，取決於
遊戲的需求。資料是否需要立即由 GPU 使用？是否
可以接受檢查執行緒暫停直到讀取完成？由於資料只能在畫面格中的特定時間點處理，
定期輪詢是否就已足夠？

### 考量事項

由於進行中的要求數目可能大幅增加，必須
小心避免在遊戲的其他部分造成瓶頸。遊戲
管理要求的成本可能很快就會抵銷 DirectStorage
所帶來的節省。建議檢查每個要求的所有支援
程式碼，並判斷哪些部分可以降到最低。

每個要求是否都需要配置新的記憶體區塊？

* 記憶體系統在尋找新區塊和更新內部清單時會產生負擔。
* 請考慮盡可能重複使用記憶體區塊。

更新管理員是否需要鎖定？

* 隨著執行的更新越多，會產生越多競爭。
* 請考慮盡可能採用無鎖定方式。

是否使用推測性載入？

* 支援取消，這可能會導致建立更多要求。
* 不過，推測需要有可用的記憶體。
* 請考慮對推測閾值設定硬性限制。

## 另請參閱

[DirectStorage](/zh-TW/build/console-features/storage/directstorage-toc)
[DirectStorage 使用方式與內部細節 (NDA 主題)](/zh-TW/build/console-features/storage/directstorage/directstorage-white-paper)
[使用 DirectStorage 和 XBTC 最佳化壓縮內容 (NDA 主題)](/zh-TW/build/console-features/storage/directstorage/directstorage-compression)
[分析 DirectStorage 效能 (NDA 主題)](/zh-TW/tools/tools-console/pix/pix-directstorage)


## Related topics

- [DirectStorage](/build/console-features/storage/directstorage-toc.md)
- [DirectStorage Overview](/build/console-features/storage/directstorage/directstorage-overview.md)
- [DirectStorage 개요](/ko/build/console-features/storage/directstorage/directstorage-overview.md)
- [DirectStorage 概述](/zh-CN/build/console-features/storage/directstorage/directstorage-overview.md)
- [DirectStorage の概要](/ja-jp/build/console-features/storage/directstorage/directstorage-overview.md)
