How D3D12_MEMORY_POOL Configures Device Memory
This article explores how the D3D12_MEMORY_POOL
enumeration configures physical device memory pools for DirectX 12
resources. It details the operational differences between L0 and L1
memory pools across discrete and integrated hardware architectures,
explains how custom heap properties leverage these pools, and examines
the performance implications of directing allocations to specific
physical memory locations.
Understanding D3D12_MEMORY_POOL
In DirectX 12, memory management is exposed directly to the
application. The D3D12_MEMORY_POOL enumeration defines the
physical location of the memory backing a resource heap. It provides
three primary values:
D3D12_MEMORY_POOL_UNKNOWN: The system decides the physical memory pool based on the heap type and driver heuristics.D3D12_MEMORY_POOL_L0: Specifies physical memory pool zero, which corresponds to system RAM.D3D12_MEMORY_POOL_L1: Specifies physical memory pool one, which corresponds to local video memory (VRAM) physically attached to a discrete GPU.
Architecture: UMA vs. NUMA
The physical behavior of D3D12_MEMORY_POOL depends on
whether the underlying hardware operates under a Unified Memory
Architecture (UMA) or a Non-Unified Memory Architecture (NUMA).
On NUMA systems (typical discrete graphics cards), the GPU and CPU maintain separate physical memory spaces:
- Pool L0 (System Memory): Accessible to the CPU with optimal latency and bandwidth. The GPU accesses L0 across the PCIe bus, resulting in higher latency and lower bandwidth compared to local VRAM.
- Pool L1 (Dedicated VRAM): Positioned directly on the graphics card. It offers high bandwidth and low latency for the GPU. The CPU can only access L1 through a bridge like PCIe, often via a limited size window (Base Address Register/BAR).
On UMA systems (such as integrated graphics or modern consoles), both the CPU and GPU share the same physical system RAM:
- Pool L0: Contains the unified system memory.
- Pool L1: Generally does not exist on pure UMA
systems. Attempting to allocate directly to L1 on such systems may fall
back to L0 or produce an invalid configuration, as queried via
D3D12_FEATURE_DATA_ARCHITECTURE.
Configuration Through Custom Heaps
Standard heap types (D3D12_HEAP_TYPE_DEFAULT,
D3D12_HEAP_TYPE_UPLOAD, and
D3D12_HEAP_TYPE_READBACK) automatically map to underlying
pools. For example, DEFAULT maps to L1 on discrete GPUs,
while UPLOAD and READBACK map to L0.
To override these defaults and manually configure the physical pool,
developers must use D3D12_HEAP_TYPE_CUSTOM inside the
D3D12_HEAP_PROPERTIES structure. Within this structure, the
MemoryPoolPreference field is explicitly assigned to either
D3D12_MEMORY_POOL_L0 or
D3D12_MEMORY_POOL_L1.
Along with the pool preference, developers specify the
CPUPageProperty, which defines how the CPU caches and
accesses the allocated pages (such as
D3D12_CPU_PAGE_PROPERTY_WRITE_COMBINE or
D3D12_CPU_PAGE_PROPERTY_WRITE_BACK).
Practical Application and Optimization
Configuring memory pools directly allows developers to optimize data transfers and resource access patterns:
- CPU-GPU Shared Buffers on Resizable BAR Hardware:
Setting
MemoryPoolPreferencetoD3D12_MEMORY_POOL_L1combined with write-combined CPU access allows the CPU to write directly into fast VRAM, bypassing the need for a separate staging copy from L0 to L1. - Streaming Textures and Geometry: When VRAM capacity
is exhausted, assigning large, lower-priority assets to
D3D12_MEMORY_POOL_L0prevents allocation failures while keeping the resources directly bindable by the GPU, albeit at the cost of PCIe bus bandwidth. - Adaptive Architectures: Applications should query
CheckFeatureSupportwithD3D12_FEATURE_ARCHITECTUREat startup. If the device reportsUMA = TRUE, code paths can be simplified to requestD3D12_MEMORY_POOL_L0, avoiding redundant intermediate copy operations required on discrete NUMA devices.