Skip to main content

簡介

本文提供 DirectStorage API 的概觀,僅適用於 XBOX Series X|S 主機。如需桌面版 DirectStorage 的詳細資料,請參閱 桌面版 DirectStorage。 透過 PCIe 匯流排連接的最新 NVMe 儲存裝置可以達到非常 高的輸送量和 IOPS (每秒 I/O 要求數)。Win32 API 的負擔意味著,即使可用的儲存頻寬能夠 被充分利用,但要善用它可能會導致高到無法接受的 CPU 使用率。當工作負載包含大量 小型要求時尤其如此。 DirectStorage API 的設計目的是透過與基礎 NVMe 硬體緊密互動,移除大部分的作業系統 負擔。這可以 以較低的 CPU 使用量達到較高的頻寬。目標是在最多使用 單一 CPU 核心 10% 的情況下,每秒處理多達 50,000 個要求。

現有問題

隨著每個主機世代對更高解析度資產的需求增加,遊戲內容 也變得越來越大。現有的 XBOX One 硬體 和軟體有數項限制,妨礙開發人員為次世代內容 將資料從硬碟取得至記憶體的能力。
  • CPU 使用量過高
    • 現有的 Win32 API 可能需要耗用一整個 CPU 核心作為負擔。
    • 這取決於遊戲的要求數量。
  • 磁碟的最大頻寬不足
  • 無法排定磁碟要求的優先順序
    • 若無法排定遊戲要求的優先順序,可能很難建立 回應迅速的串流系統。
  • 無法取消磁碟要求
    • 若無法取消要求,可能很難建立 推測性讀取系統。
  • 沒有硬體加速解壓縮
    • 若沒有硬體加速解壓縮,在軟體中執行解壓縮可能需要耗用大量 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 方法讓呼叫執行緒暫停,直到讀取作業完成為止。 第三種非同步方法是使用 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
    • 建立代表一個檔案的 IDStorageFileX 物件。
  • CreateQueue
    • 建立 IDStorageQueueX 物件。用來建立讀取要求。
  • CreateStatusArray
    • 建立 IDStorageStatusArray 物件,用來管理完成狀態 旗標。
  • SetCPUAffinity
    • 將 DirectStorage 在呼叫執行緒以外的工作,限制在遊戲定義的一組 CPU 核心上。
    • 注意 DirectStorage 會嘗試在呼叫執行緒中 完成大部分的工作。只有在工作無法於呼叫執行緒中完成時,才會在呼叫執行緒以外 進行工作。範例如下。
      • 在 IDStorageQueueX::Submit 期間基礎資源管線已滿, 且並非佇列中的所有要求都能向前推進。其餘 要求會在稍後資源釋出時處理,這是 在 DirectStorage 背景工作執行緒中完成。
      • 將要求完成處理至 ID3DFence 或 IDStorageStatusArray。
  • SetDebugFlags
    • 控制 DirectStorage 是否要在要求排入佇列時進行額外的 驗證,以協助偵錯。
  • SetStagingBufferSize
    • 設定暫存緩衝區的大小,此緩衝區用來暫時儲存從儲存裝置載入、 尚未解密/解壓縮的內容。如果只使用 記憶體來源佇列,暫存緩衝區的大小可以是 0。

IDStorageFactoryX1

IDStorageFactoryX1 介面以 GetStats 方法擴充 IDStorageFactoryX 介面。
  • GetStats
    • 取得 DirectStorage 統計資料。此函式可用來將 DirectStorage 與現有的診斷和遙測管線整合。 它只進行最少量的處理,因此可以經常呼叫。統計資料 不包含 Win32 檔案 IO 作業。

IDStorageFactoryX2

IDStorageFactoryX2 介面以 CreateQueue1 方法擴充 `IDStorageFactoryX1 介面。
  • CreateQueue1
    • 建立 IDStorageQueueX2 物件。使用新的結構以允許 在建立佇列時使用額外選項,包括覆寫 佇列自動提交功能的能力。

IDStorageFileX

所有檔案一開始都需要由 DirectStorage 透過 IDStorageFactoryX 物件開啟。這相當於在 Win32 API 介面中使用 CreateFile。 檔案會以 FILE_SHARED_READ 權限開啟。如有需要,只要遵守適當的 權限,遊戲可以同時使用 Win32 API 開啟該檔案。在開發期間,鬆散和封裝的部署都受到支援。 檔案會在明確呼叫檔案物件上的 Close 函式時關閉,或在相符的 IDStorageFileX 物件的最後一個參考被釋放時關閉。不過,所有未完成的 I/O 作業都必須 完成,檔案才能關閉。這表示兩種關閉 檔案的方法都會封鎖,直到該檔案上所有未完成的 I/O 作業都完成為止。 遊戲可以呼叫 GetHandle 函式, 取得 IDStorageFileX 物件所代表之檔案的 win32 控制代碼。 此控制代碼會以 GENERIC_READ 權限和 FILE_SHARE_READ 共用模式開啟。 它可用於查詢檔案大小等用途。不再需要時,應使用 CloseHandle() 關閉該控制代碼。

IDStorageQueueX

讀取要求會透過 IDStorageQueueX 物件提交至 NVMe。 不過,要求要等到遊戲在佇列上呼叫 Submit,或者其中一個 Enqueue 方法 自上次提交以來填滿了佇列容量的一半以上並觸發 自動提交時,才會提交至裝置。提交會以單一轉換的方式處理,送往 管線中的下一個階段。這讓遊戲可以控制在遊戲與核心之間轉換的 CPU 成本於何時發生。 IDStorageQueueX 物件有四個屬性。
  • SourceType
    • 指定佇列可以接收檔案來源要求或 記憶體來源要求。
  • Priority
    • 提交至佇列之所有要求的優先順序:即時、高、一般或低。
    • 記憶體來源佇列必須以即時優先順序建立。
    • 要求會根據優先順序,以加權循環配置順序處理。
    • Win32 要求會以一般優先順序層級處理。
  • Capacity
    • 佇列可容納的未完成要求數目上限。
    • 在佇列已達容量時嘗試將要求排入佇列,會封鎖 直到硬體完成項目為止。
    • 佇列所需的記憶體量約為佇列容量 乘以 DSTORAGE_REQUEST 的大小。
  • Name
    • 這純粹是為了協助偵錯。任何 DirectStorage 程式碼都不會使用此名稱,但它會顯示在開發人員工具中,例如 PIX (NDA 主題)。
硬體會以非同步方式處理要求,以達到最高的 輸送量。不過,與 Win32 不同的是,遊戲會依 FIFO 順序收到完成通知。收到完成通知時,保證同一佇列中 所有先前的要求也都已完成。

IDStorageQueueX1

IDStorageQueueX1 介面以 EnqueueSetEvent 方法擴充 IDStorageQueueX 介面。

IDStorageQueueX2

IDStorageQueueX2 介面以 CreateQueue1 方法擴充 `IDStorageQueueX1 介面。 以及一個額外的屬性。
  • 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 主題),或出現在從 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
  • BCPack
    • BCPack 是專為 BCn 資料設計的自訂熵編碼器。 一般而言,這表示色彩端點會與 調色盤索引 (也就是權重) 分開,並使用 rANS 演算法進行壓縮。
  • Swizzle
    • Swizzle 和隨機排列模式可以在內容管線中提供額外的最佳化。
壓縮高熵資料時,壓縮實際上可能會 使大小變大。相反地,對應的解壓縮會使大小 縮小。DirectStorage 不允許縮小的解壓縮,遊戲必須自行 偵測無法壓縮的高熵資料,並避免 對這類資產進行壓縮。如需進一步的詳細資料,請參閱使用 DirectStorage 和 XBTC 最佳化壓縮內容 (NDA 主題) 指南。

暫存緩衝區

DirectStorage 在內部使用緩衝區來暫存從原始 NVMe 儲存空間讀取的所有內容,然後再執行解密和 解壓縮等作業。此暫存緩衝區讓 NVMe 磁碟機與解密/解壓縮晶片能夠以管線方式平行運作。其預設為 32 MiB, 並會在擷取第一個 DirectStorage 處理站指標時配置。 如果遊戲只將 DirectStorage 用於記憶體對記憶體的解壓縮 作業,就不需要暫存緩衝區,可以呼叫 SetStagingBufferSize 將暫存緩衝區大小設定為 0。 目前版本的 DirectStorage 支援 0、16、20、24、28 和 32 MiB 的暫存緩衝區大小。小於預設值的大小可以為遊戲節省記憶體,但可能會影響整體讀取效能。 注意:SetStagingBufferSize 只能在不存在任何 IDStorageQueueX 物件或 IDStorageFileX 物件時呼叫,否則會擲回錯誤。

CancelRequestsWithTag

DirectStorage 支援取消要求。每個要求都有一個與其相關聯、 由遊戲定義的 64 位元標記。其用途是作為位元遮罩,決定 要取消哪些要求。遊戲會提供用於取消的遮罩和 值。佇列會嘗試取消所有符合 準則的要求:tag & mask == value。 取消是盡力而為的作業。根據要求在管線中的位置, 可能無法取消。例如,要求可能 正由硬體進行解壓縮,而這是無法取消的。API 會立即傳回,不會封鎖等待所有已取消的要求 處理完畢。遊戲必須等到佇列中較後面的通知發出訊號後, 才能釋放與已取消要求相關聯的資源。 必須小心避免在呼叫 CancelRequestsWithTag 的同時將要求新增至佇列。 在此情況下,行為是未定義的。不過,在 CancelRequestsWithTag 的呼叫傳回後才新增至佇列的要求,即使符合準則也不會遭到取消。只有先前 排入佇列的要求會遭到取消。

GetErrorEvent/RetrieveErrorRecord

如果讀取發生錯誤,該讀取會標示為已完成。佇列中 後續的通知不會因此無法發出訊號。錯誤的通知 是透過與佇列相關聯的 Event 物件處理,該物件可以 使用 GetErrorEvent 擷取。遊戲可以 對 GetErrorEvent 傳回的 Event 使用 WaitForSingleObject。如果事件收到訊號, 遊戲可以呼叫 RetrieveErrorRecord 函式,取得自上次呼叫 RetrieveErrorRecord 函式以來的 第一個錯誤。 RetrieveErrorRecord 傳回的 錯誤記錄只包含自上次 RetrieveErrorRecord 以來,佇列中第一個失敗要求的 資料。如果錯誤 Event 未收到訊號,或資料已 被擷取,則錯誤記錄中的資料是未定義的。

Query

取得佇列的相關資訊。其中包括用來建立佇列的 DSTORAGE_QUEUE_DESC 或 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 DirectStorage 使用方式與內部細節 (NDA 主題) 使用 DirectStorage 和 XBTC 最佳化壓縮內容 (NDA 主題) 分析 DirectStorage 效能 (NDA 主題)
Last modified on October 6, 2026