Implementing Virtual Texturing with DirectX Tiled Resources

Virtual texturing allows rendering engines to stream and display massive texture datasets without exceeding physical video memory by loading only visible texture regions. DirectX Tiled Resources provide native hardware support for this technique, allowing developers to create massive virtual textures with sparse physical memory backing. This article explains the core concepts of virtual texturing and details the implementation process using the DirectX Tiled Resources API.

What is Virtual Texturing?

Virtual Texturing, often referred to as Sparse Virtual Texturing (SVT) or MegaTexturing, decouples a texture's logical dimensions from its physical memory footprint. Instead of loading an entire texture and its complete mipmap chain into VRAM, the texture is divided into small, uniform square regions called "tiles" or "pages" (commonly 64x64 to 128x128 pixels).

During rendering, the graphics engine determines which tiles are visible at the required mip levels for the current camera view. Only those specific tiles are loaded into a physical memory pool on the GPU. This eliminates redundant data loading, enables consistent memory usage regardless of scene complexity, and allows for texture resolutions that far exceed standard hardware limits (such as 64k x 64k textures).

DirectX Tiled Resources Overview

DirectX Tiled Resources (available in Direct3D 11.2 and Direct3D 12) provide direct hardware support for sparse allocation. A tiled resource divides a texture's memory into fixed 64KB chunks.

DirectX provides two main components for this feature:

  • Virtual Address Space Allocation: Large resources can be declared in virtual memory without committing physical memory upfront.
  • Physical Tile Mapping: A dedicated memory pool (heap) provides physical 64KB backing pages that can be dynamically mapped, unmapped, or shared across various coordinates in the virtual resource.

Implementation Steps

1. Offline Data Preparation

Textures must be preprocessed into standardized page files:

  • Split the source texture and all generated mip levels into uniform tiles that align with DirectX 64KB dimensions (for example, 128x128 pixels for 32-bit RGBA formats).
  • Store these tiles in a streamable package file with an index mapping mip levels and tile coordinates to file offsets.

2. Creating the Reserved Resource and Tile Pool

Using Direct3D 12:

  • Create a reserved texture using ID3D12Device::CreateReservedResource. This reserves the virtual address space for your target resolution (e.g., 16384x16384) without allocating physical VRAM.
  • Create a physical heap using ID3D12Device::CreateHeap with the D3D12_HEAP_FLAG_DENY_BUFFERS | D3D12_HEAP_FLAG_DENY_RT_DS_TEXTURES flags to serve as the physical tile pool.

3. Determining Visibility (Feedback Analysis)

The rendering pipeline must determine which tiles are currently visible on screen. This can be accomplished through two methods:

  • DirectX 12 Sampler Feedback (Recommended): Use a Feedback Texture (Texture2D<DirectX::PackedMipInfo>) in your pixel shader to automatically record which mip levels and tiles are sampled during rendering.
  • Custom Visibility Buffer Pass: Render a low-resolution pass writing out the required tile IDs and mip levels to a readback buffer.

4. Updating Tile Mappings

After the feedback data is read by the streaming system:

  1. Identify missing tiles that are requested by the shader.
  2. Load the required 64KB compressed blocks from disk or system RAM into staging buffers.
  3. Upload the loaded tiles into an available slot within the physical heap.
  4. Call ID3D12CommandQueue::UpdateTileMappings to link the coordinate in the reserved virtual texture to the corresponding physical tile in the heap.
  5. If physical memory is full, unmap least-recently-used (LRU) tiles to free space for incoming requests.

5. Shader Sampling and Fallbacks

In HLSL, tiled resources are sampled like standard textures. However, when high-resolution tiles are still streaming, shaders need a fallback strategy to avoid rendering artifacts:

  • Mip Clamping: Clamp sampling to the highest resident mip level available for that coordinate.
  • Status Checking: Use HLSL intrinsics such as CheckAccessFullyMapped to determine if all sampled data is resident. If not, sample from a low-resolution base mip level that remains permanently resident in memory.