Understanding D3D12_CPU_PAGE_PROPERTY in DirectX 12

In DirectX 12, memory management is exposed directly to the developer, requiring precise control over how resources are allocated and cached across the CPU and GPU. The D3D12_CPU_PAGE_PROPERTY enumeration is a specialized tool within custom heap configurations that dictates the CPU cache policies for physical memory pages. By fine-tuning CPU page properties, graphics engineers can balance CPU access latency, bandwidth usage, and cache coherency based on the specific read/write patterns of a resource.

The Role of Custom Heaps

DirectX 12 provides high-level heap types—D3D12_HEAP_TYPE_DEFAULT, D3D12_HEAP_TYPE_UPLOAD, and D3D12_HEAP_TYPE_READBACK—which internally assign default CPU page properties. However, when developers define a D3D12_HEAP_TYPE_CUSTOM heap via D3D12_HEAP_PROPERTIES, they must manually declare the CPUPageProperty member.

Selecting the proper property prevents performance hazards, such as CPU cache pollution, read-stall penalties, and inefficient cache coherency traffic over the interconnect bus (such as PCIe).

Enumeration Values and Caching Mechanisms

The D3D12_CPU_PAGE_PROPERTY enum defines four distinct states:

  • D3D12_CPU_PAGE_PROPERTY_NOT_AVAILABLE
    The CPU cannot access the memory allocated in this heap. The memory is not mapped into the CPU's virtual address space, eliminating any CPU cache overhead. This is the optimal setting for GPU-exclusive resources (textures, render targets, depth buffers) living in discrete VRAM.

  • D3D12_CPU_PAGE_PROPERTY_WRITE_COMBINE (WC)
    CPU access is configured to bypass the L1/L2/L3 caches. Writes are accumulated in internal CPU write-combining buffers and burst-written to system memory in cache-line-sized chunks.

    • Use Case: Highly efficient for write-only CPU buffers, such as per-frame dynamic vertex buffers, instance buffers, and constant buffers.
    • Performance Implication: Prevents CPU writes from evicting useful cached application data. However, reading from Write-Combine memory incurs an extreme performance penalty because every read must fetch directly from system memory without cache acceleration.
  • D3D12_CPU_PAGE_PROPERTY_WRITE_BACK (WB)
    Memory allocations utilize full CPU caching. Reads and writes are cached across the CPU's cache hierarchy (L1, L2, L3) and written back to main memory as lines are evicted.

    • Use Case: Critical for resources where the CPU must read data generated by the GPU (such as occlusion query results, compute readbacks, or staging memory), or for resources that the CPU reads and modifies iteratively before the GPU consumes them.
    • Performance Implication: Greatly improves read performance for the CPU. On the GPU side, the hardware must either invalidate CPU caches or utilize hardware snooping to maintain coherency, which can add overhead on discrete systems.
  • D3D12_CPU_PAGE_PROPERTY_UNKNOWN
    A default/invalid value indicating that the page property is uninitialized or not applicable to standard heaps.

Optimizing for UMA and NUMA Architectures

The effectiveness of D3D12_CPU_PAGE_PROPERTY heavily depends on the memory architecture:

  1. Unified Memory Architecture (UMA):
    On systems where the CPU and GPU share physical RAM (such as APUs, mobile chips, and modern consoles), WRITE_BACK combined with D3D12_MEMORY_POOL_L0 allows the GPU to read directly from CPU-cached memory without redundant transfer copies. This enables zero-copy resource updates with minimal latency.

  2. Non-Unified Memory Architecture (NUMA):
    On systems with discrete GPUs, memory pools are strictly divided between system RAM (L0) and dedicated video memory (L1). Assigning WRITE_COMBINE to upload buffers residing in system memory ensures that high-speed CPU writes stream over PCIe without polluting the CPU cache or triggering unneeded cache-line write-backs.

Best Practices

  • Never read from WRITE_COMBINE memory: Even a single accidental read operation from a mapped WC buffer stalls the CPU pipeline while waiting for non-cached memory access.
  • Default to NOT_AVAILABLE when possible: If the CPU never maps or touches a resource after creation, keeping CPU pages unmapped maximizes GPU access performance and saves virtual address space.
  • Use WRITE_BACK sparingly for uploads: Unless the CPU reads back values from the buffer prior to submission, WRITE_BACK introduces unnecessary cache invalidation and eviction costs on write-only streams.