設計理念
基本上,XG 記憶體是將實體記憶體頁面 (包括系統頁面和 ESRAM 頁面) 對應至虛擬位址範圍的一種解決方案。Microsoft Game Development Kit (GDK) 遊戲作業系統和圖形驅動程式堆疊提供 XMemVirtualAlloc 和 XMemAllocatePhysicalPages 等低階 API 來執行這些作業,但它們可能難以有效使用。XG 記憶體建置在低階 API 之上,並以下列原則進行設計。- 提供高階、易於使用的 API,讓所有遊戲都能將其用於 D3D12.X 甚至 D3D11.X 開發。同時隨附原始程式碼,以便在必要時檢查和改善底層的複雜性。
- 在設計時考量對動態記憶體配置的支援。現代遊戲越來越多地採用動態解析度和其他需要在執行階段頻繁重新對應記憶體配置的技術。
- 在設計時考量支援在同一個畫面中輕鬆重新對應記憶體。有效率地使用 ESRAM 需要採用一些策略,讓不同的資源在未同時使用時可以共用相同的記憶體頁面。
- 確保更清楚地區分資源之間的「記憶體別名」與「資料別名」,以便於工具處理。記憶體別名是指兩個資源共用相同的記憶體,但記憶體中的資料永遠不會由兩個資源同時檢視以重新解譯。資料別名是指兩個資源共用相同的記憶體,但有意以兩種不同的方式重新解譯其中的資料。
XG 記憶體 API 物件
XG 記憶體 API 有三個 API 物件:引擎、配置和對應。- 引擎 (XGMemoryLayoutEngine 類別) 是代表您指派給一或多個記憶體配置之記憶體預算的物件。它會儲存將對應至配置的 ESRAM 和/或系統記憶體實體頁面清單。配置物件也是在此建立的。
- 配置 (XGMemoryLayout 類別) 是代表單一記憶體配置的物件,由保留的虛擬位址範圍及其中的多個對應所組成。雖然每個引擎可以建立任意數量的配置,但在任何指定時間,每個引擎都應只有一個配置在使用中。來自同一個引擎的配置會共用相同的實體記憶體頁面。
- 對應 (XGMemoryLayoutMapping 結構) 代表將一組實體頁面單一對應至配置內的虛擬位址範圍。建構對應之後,可以在對應的虛擬位址上以放置方式建立一或多個圖形資源。
XG 記憶體逐步解說和使用範例
準備使用 XG 記憶體
在開始使用 XG 記憶體之前,請思考您的遊戲以及 XG 記憶體適合的位置。您是否只有一兩個資源需要使用 ESRAM?您是否有整個子系統需要透過共用來減少記憶體使用量?您能否在同一個地方設定引擎的所有轉譯目標?放入同一個配置的資源越多,以及記憶體共用的機會越明顯,XG 記憶體就越有用。XG 記憶體的配置設計也最適合以「循序」方式 (在同一個執行緒上以一系列連續步驟) 設定轉譯目標資源的引擎。步驟 1:建立 XG 記憶體配置引擎
首先,建立一個引擎,其中包含您要用於所有將使用 XG 記憶體建立之資源的實體記憶體上限。例如,您可能想要建立具有 32 MB (512 個頁面) ESRAM 和另外 64 MB (1024 個頁面) 系統記憶體的引擎。建立引擎時,您可以傳入從 XMemAllocatePhysicalPages 預先配置的實體頁碼清單,也可以指示 XG 記憶體代您呼叫 XMemAllocatePhysicalPages。ESRAM 頁碼可以輕鬆地使用索引 0-511 產生,用來指稱構成整個 ESRAM 的 512 個 64 KB 頁面。步驟 2:建立 XG 記憶體配置
接著,考量您打算設定的所有資源的虛擬大小總計,包括共用 (也就是重疊部分重複計算)。例如,我們可以建立具有 128 MB 虛擬位址空間的配置,以涵蓋大約 10 個 1080p 32 bpp 目標再加上一些額外空間。這個數字即使高估,代價也很小,因為建立配置只是保留虛擬位址空間。保留虛擬位址空間的成本非常低,而且您有數百 GB 的虛擬位址空間可用。虛擬位址保留是透過以 MEM_RESERVE 呼叫 XMemVirtualAlloc 來完成。 除了保留虛擬位址空間之外,配置還包含從引擎取得的實體記憶體頁面可用清單。建立對應時,會以後進先出 (LIFO) 的方式從可用清單取得頁面。步驟 3:建立對應和放置資源
針對您要設定的每個資源,使用 XG 程式庫判斷資源在虛擬位址空間中的大小,然後無條件進位並相除,以取得 64 KB 頁面數。XG 記憶體配置類別有數個建立對應的方法,但它們都有一個共同的概念。對於每個對應,您會指定將實體頁面認可至對應時要使用的 ESRAM 頁面數和系統記憶體頁面數。各種對應建立 API 的差異只在於要產生的對應模式的複雜度 (ESRAM 頁面和系統頁面在整個對應中的特定順序)。在內部,XG 記憶體會結合使用 XMemMapPhysicalPages 和 D3DMapEsramPages,分別將系統頁面和 ESRAM 頁面認可至配置中保留的虛擬位址空間。 建立對應之後,您可以使用 XGMemoryLayoutMapping 結構的 MappingBaseAddress 成員,作為放置資源建立 API (例如 ID3D12Device::CreatePlacedResourceX) 的輸入。請記住,XBOX One 上的圖形虛擬位址在 CPU 和 GPU 之間是一致的,因此您可以將 UINT64 位址值轉換為 void*,反之亦然。步驟 4:(選擇性) 放棄對應以重複使用實體頁面
如果您想要在兩個或多個資源之間共用頁面,可以「放棄」一個對應,將其實體頁面放回配置內的可用清單。請注意,放棄作業不會解除認可對應的記憶體。它允許將認可至該對應的頁面重新指派給新的對應。因此,放棄對應之後,一旦與後續對應相關聯的記憶體開始使用,您就不能再使用該對應的記憶體。放棄可以視為某次記憶體使用與同一記憶體另一次使用之間的屏障。放棄也有助於工具處理和推斷意圖,因為記憶體只在實體層級重複使用,而不是在虛擬層級。這兩個不同的對應具有唯一的虛擬位址。如果您想要刻意為記憶體建立別名 (例如,將色彩目標重疊到樣板緩衝區上以快速填滿樣板),應該使用同一個對應的相同虛擬位址建立兩個放置資源來達成。案例虛擬程式碼
請考慮下列虛擬 API 呼叫序列,假設已建立記憶體配置引擎。- CreateLayout(“Rendertargets”, WorstCaseTotalPageCount, &layout)
- CreateMapping(“Shadow Map”, &mapping1)
- Map(mapping1, ShadowMapPageCount, ShadowMapEsramPageCount)
- CreatePlacedResourceX(mapping1.address, ShadowMapDesc, &pShadowMapResource)
- CreateMapping(“Color Target”, &mapping2)
- Map(mapping2, ColorTargetPageCount, ColorTargetEsramPageCount)
- CreatePlacedResourceX(mapping2.address, ColorTargetDesc, &pColorTargetResource)
- RelinquishMapping(mapping1)
- CreateMapping(“Post Effects Target”, &mapping3)
- Map(mapping3, PostEffectsPageCount, PostEffectsEsramPageCount)
- CreatePlacedResourceX(mapping3.address, PostEffectsDesc, &pPostEffectsResource)
- CreateLayout(“Rendertargets”, WorstCaseTotalPageCount, &layout)
- CreateMapping(“Shadow Map”, &mapping1)
- Map(mapping1, ShadowMapPageCount, ShadowMapEsramPageCount)
- CreatePlacedResourceX(mapping1.address, ShadowMapDesc, &pShadowMapResource)
- CreateMapping(“Color Target”, &mapping2)
- Map(mapping2, ColorTargetPageCount, ColorTargetEsramPageCount)
- CreatePlacedResourceX(mapping2.address, ColorTargetDesc, &pColorTargetResource)
- CreateMapping(“Depth Target”, &mapping4)
- Map(mapping4, DepthTargetPageCount, DepthTargetEsramPageCount)
- CreatePlacedResourceX(mapping4.address, DepthTargetDesc, &pDepthTargetResource)
- RelinquishMapping(mapping1)
- CreateMapping(“Post Effects Target”, &mapping3)
- Map(mapping3, PostEffectsPageCount, PostEffectsEsramPageCount)
- CreatePlacedResourceX(mapping3.address, PostEffectsDesc, &pPostEffectsResource)
