XG Memory is an auxiliary runtime library for creating specialized memory mappings for Xbox One titles. It was designed as a successor to several different ESRAM mapping methods introduced with the Direct3D 11.X API. For Direct3D 12.X on Xbox One, XG Memory is the recommended way to access high-speed ESRAM memory.
This overview document describes the components of XG Memory and how they work together and with the system memory mapping APIs. XG Memory is provided in source code form (all inline) in xgmemory.h and xgmemory.inl in the Xbox One XDK, so it can be customized if necessary.
Fundamentally, XG Memory is one solution of how to map physical memory pages (both system and ESRAM pages) to a virtual address range. The Xbox One game OS and graphics driver stack provide low level APIs such as VirtualAlloc and AllocateTitlePhysicalPages to perform these operations, but they can be difficult to use effectively. XG Memory is built on top of the low level APIs, and was designed with the following principles in mind:
The legacy ESRAM APIs for Direct3D 11.X always treated ESRAM as a contiguous block of memory, which greatly limited flexibility for sharing ESRAM between two or more resources. The legacy APIs also increased complexity when adjustments had to be made to any resource’s ESRAM consumption; other resources had to be fixed up to match the changes. XG Memory is designed specifically to address these issues, as it operates on pages, not ranges, and furthermore it does the math for you to correctly allocate pages where they are needed.
The XG Memory API is comprised of three API objects: the engine, the layout, and the mapping.
Before you start using XG Memory, think about your title and where XG Memory would fit in. Do you have just one or two resources that need to use ESRAM? Do you have an entire subsystem that needs to reduce memory footprint via sharing? Can you configure all of your engine’s rendertargets in one place? XG Memory becomes more useful as more resources are placed into the same layout, as more memory sharing opportunities become evident. XG Memory’s layout design is also best designed for engines where the rendertarget resources are configured serially (as a sequential series of steps on the same thread).
To start, create an engine containing the maximum amount of physical memory that you would like to use for all of the resources that will be created using XG Memory. For example, you may want to create an engine with 32MB (512 pages) of ESRAM and another 64MB (1024 pages) of system memory. When creating the engine, you can either pass in a pre-allocated list of physical page numbers from AllocateTitlePhysicalPages, or you can instruct XG Memory to call AllocateTitlePhysicalPages on your behalf. ESRAM page numbers can be trivially generated using indices 0-511 to refer to the 512 64KB pages that comprise all of ESRAM.
Next consider the total virtual size of all of the resources you plan to configure, including sharing (i.e. double count overlaps). For example, we can create a layout with 128MB of virtual address space to span approximately 10 1080p 32bpp targets plus a bit extra. This number can be overestimated with very little penalty, as layout creation will simply reserve virtual address space, and virtual address space reservation is very inexpensive (and you have hundreds of GB of virtual address space available). The virtual address reservation is done via calling VirtualAlloc with MEM_RESERVE.
In addition to reserving virtual address space, the layout contains free lists of physical memory pages obtained from the engine. As mappings are created, pages are taken from the free list in a LIFO (last in first out) fashion.
For each resource you wish to configure, determine the resource’s size in virtual address space using the XG library, and round up and divide to obtain a 64KB page count. The XG memory layout class has several methods for creating mappings, but they all share a common concept; with each mapping, you specify a number of ESRAM and a number of system memory pages to use when committing physical pages to the mapping. The various map creation APIs differ only in the complexity of the mapping pattern to generate (the specific ordering of ESRAM and system pages across the mapping). Internally, XG Memory uses a combination of MapTitlePhysicalPages and D3DMapEsramPages to commit system and ESRAM pages respectively to the reserved virtual address space from the layout.
After a mapping is created, you can use the MappingBaseAddress member of the XGMemoryLayoutMapping struct as input to a placement resource creation API, such as CreatePlacedResourceX. Remember that graphics virtual addresses on Xbox One are uniform between CPU and GPU, so you can cast a UINT64 address value to void* or vice versa.
If you wish to share pages between two or more resources, you can “relinquish” one mapping to place its physical pages back on the free list within the layout. Note that the relinquish operation does not decommit the memory for the mapping; it allows the pages committed to that mapping to be reassigned to new mappings. As a result, once you relinquish a mapping, you cannot use that mapping’s memory once the memory associated with subsequent mappings is in use. Relinquish can be thought of as a barrier between one use of memory and another use of the same memory. Relinquish also assists tooling and derivation of intent because memory is only reused at the physical level, and not the virtual level; the two different mappings have unique virtual addresses. If you wish to intentionally alias memory (for example, overlaying a color target onto a stencil buffer to fast-fill stencil), you should do that by creating two placement resources using the same virtual address from the same mapping.
Consider the following sequence of pseudo API calls, assuming a memory layout engine has already been created:
This sequence creates three rendertargets: a shadow map, a color target, and a post effects target. Using the relinquish feature, the shadow map and the post effects target share the same memory. Note that there are no dependent calculations done by the title here; all of the allocations are computed by the layout as each mapping is created in order.
Now let’s say that you have done some profiling, and decide that the ESRAM page balance between the resources needs to be updated; the color target needs more ESRAM. All that has to be done is reduce the ShadowMapEsramPageCount value and increase the ColorTargetEsramPageCount value. XG Memory takes care of the rest – ESRAM pages are flexibly reallocated from the shadow map and the post effects target to the color target. Note that at any time while creating the layout, the current counts of free ESRAM pages and free system pages can be obtained.
The same technique can be used if you need to insert a new rendertarget into the sequence. For example, we can add a fourth mapping (a depth target) into the layout as follows:
Note that no changes needed to be made to the surrounding code. When adding a new mapping, you may need to rebalance ESRAM page counts amongst the other mappings, but you never need to worry about contiguous ranges, virtual address overlaps, or any other dependent calculations.
Next, let’s say that you need to dynamically change the size of the rendertargets, for a dynamic resolution change. However, you have already well balanced the ESRAM usage between all of the resources, and you don’t want to have to rework that balance. This is no problem with XG Memory – simply adjust the page count variables (ShadowMapPageCount, ColorTargetPageCount, etc) and XG Memory will take care of the rest, keeping your ESRAM allocations intact. All you have to do is ensure the WorstCaseTotalPageCount value is large enough to handle the largest configuration of all of your resources.
XG Memory can greatly reduce the complexity of using ESRAM on Xbox One. Additionally, it can prove useful even with memory layouts where page reuse is desirable and yet are 100% comprised of system pages.
Feedback on XG memory is welcome, through Game Developer Support.