Skip to main content
UltraInstinct
Back to latest articles
Consumer Technology13 min read

How DirectStorage Asset Streaming Works in Modern Games

Learn how DirectStorage asset streaming bypasses OS file filters via BypassIO, batches NVMe requests, and decompresses GDeflate assets directly on the GPU.

Featured visual representing How DirectStorage Asset Streaming Works in Modern Games

Modern open-world games routinely demand continuous throughput from storage subsystems to stream multi-gigabyte virtual textures, Nanite-style geometry clusters, and volumetric audio banks. At a 60 Hz or 120 Hz render target, a game engine has an immutable frame budget between 16.6 and 8.33 milliseconds. Any hitch or I/O stall in fetching mipmap tails or geometry detail results in frame drops, micro-stutters, and visual asset pop-in.

To overcome this storage bottleneck, modern runtimes rely on DirectStorage asset streaming to fundamentally restructure the path from persistent NVMe storage to high-bandwidth video memory (VRAM). Originating from the console storage subsystem and adapted to modern desktop PC operating systems, this technology eliminates legacy operating system file system bottlenecks, amortizes system call overhead, and shifts compute-intensive decompression workloads entirely from CPU cores onto massive parallel GPU execution units.

+-----------------------------------------------------------------------+
|                       LEGACY I/O STORAGE PATH                         |
| NVMe SSD -> StorNVMe -> OS Filters -> NTFS -> Kernel Cache -> CPU RAM |
|                                                                 |     |
| GPU VRAM <-- PCIe Bus <-- CPU RAM <-- [CPU zlib/LZ4 Decompress] v     |
+-----------------------------------------------------------------------+
|                     DIRECTSTORAGE ASSET PATH                          |
| NVMe SSD -> StorNVMe (BypassIO) -> System / VRAM Staging Area         |
|                                         |                             |
| GPU VRAM <-- [GDeflate Compute Shader] <+                             |
+-----------------------------------------------------------------------+

The Legacy Game Asset Bottleneck: Synchronous I/O and CPU Decompression

To understand why modern rendering architectures required an entirely new storage pipeline, consider the traditional Win32 and POSIX I/O pathways historically employed by game engines. In a conventional game engine architecture, worker threads issue file read requests using standard APIs such as ReadFile or serial pread(). When a thread requests an asset package containing compressed 4K textures or skeletal meshes, the request traverses a deep kernel driver hierarchy:

  1. The I/O Manager: Allocates an I/O Request Packet (IRP).
  2. File System Minifilters: Antivirus scanners, volume shadow copy drivers, and encryption layers inspect the IRP, incurring multiple context switches and CPU cache pollution.
  3. The File System Driver (NTFS/ReFS): Resolves disk allocation clusters, checks file attributes, and attempts to service the request through the operating system page cache.
  4. The Volume and Disk Class Drivers: Translates logical file offsets into physical block addresses.
  5. The Host Controller Interface (StorNVMe): Finally issues commands to the physical hardware controller.

Once the storage device transfers data over the PCIe bus into a temporary kernel-space memory buffer, the kernel copies the data into user-space RAM allocated by the game engine. At this point, the CPU must decompress the package. Formats such as LZ4, zstd, or zlib require sequential scanning of sliding dictionary back-references and variable-length entropy decoding.

Decompressing data at high rates (such as 10 to 14 GB/s, the sequential read saturation point of a PCIe 5.0 NVMe SSD) would monopolize 8 to 16 modern CPU execution cores exclusively for storage decompression tasks. This robs the engine of the thread capacity required for physics simulations, animation state machines, and draw call preparation.

Also, multithreaded staging architectures frequently suffer from synchronization contention. When multiple worker threads write decompressed chunks into adjacent memory blocks within shared staging heaps, thread synchronization primitives and shared cache lines cause severe latency spikes. As explored in our deep analysis of How Cache Line False Sharing Degrades App Performance, CPU cores invalidate their L1 and L2 caches back and forth when modifying shared memory regions, destroying CPU pipeline efficiency before the asset is ever queued for GPU upload.

Finally, the CPU must copy the decompressed asset into a DirectX 12 or Vulkan upload heap (D3D12_HEAP_TYPE_UPLOAD), from which the graphics card's direct memory access (DMA) engine copies the buffer across the PCIe link into dedicated video memory (D3D12_HEAP_TYPE_DEFAULT). This double-buffering architecture introduces severe latency, wastes memory bandwidth, and throttles overall streaming throughput.

The Mechanics of DirectStorage Asset Streaming Pipelines

DirectStorage redesigns this datapath by treating storage operations as first-class asynchronous command queues that mirror native GPU command lists. The runtime abstracts low-level file operations behind three foundational interfaces: IDStorageFactory, IDStorageFile, and IDStorageQueue.

Instead of allocating ad-hoc staging buffers and issuing blocking read calls, a game engine establishes persistent, high-capacity submission queues. The engine populates these queues with declarative asset requests (DSTORAGE_REQUEST), specifying the source file, physical byte offset, source size, target destination (a GPU buffer, texture resource, or system memory address), and decompression options.

       +---------------------------------------------------+
       |            IDStorageQueue Submission              |
       |  [Req 0: MipTail] [Req 1: Cluster] [Req 2: Audio] |
       +---------------------------------------------------+
                                 |
                                 v
    +---------------------------------------------------------+
    |           DirectStorage Core Dispatch Engine            |
    |  - Request batching & queue depth optimization          |
    |  - Path validation (BypassIO qualification)             |
    +---------------------------------------------------------+
             |                                       |
             | (Storage Read)                        | (Decompression)
             v                                       v
+--------------------------+           +--------------------------+
|  NVMe Controller Ring    |           |   GPU Compute Pipeline   |
|  - Submission Queue (SQ) |           |  - DirectCompute Shaders |
|  - Hardware DMA Transfer |           |  - GDeflate Decoders     |
|  - Completion Queue (CQ) |           |  - VRAM Tile Blitting    |
+--------------------------+           +--------------------------+

This model is architecturally aligned with modern high-performance kernel bypass models. In the Linux systems ecosystem, the emergence of queue-based asynchronous submission dramatically outpaced legacy blocking interfaces; our examination of How io_uring Submission Queue Polling Actually Works illustrates the massive throughput gains achieved when serial system calls are replaced by batched ring buffer submissions. DirectStorage implements this exact design philosophy for consumer operating systems, batching hundreds of distinct asset requests into a unified batch and dispatching them down to the hardware controller in a single programmatic action.

The DirectStorage Request Cycle

When the game calls IDStorageQueue::EnqueueRequest and subsequently executes IDStorageQueue::Submit, the following operations execute in locked sequence:

  1. Aggregation: The runtime coalesces contiguous physical file read regions, reducing the total count of distinct disk access commands.
  2. Dispatch: The batched requests pass directly into the storage miniport driver, bypassing intermediate OS caching logic entirely.
  3. Hardware Retrieval: The NVMe drive reads the compressed byte stream directly into pinned system memory or mapped GPU virtual memory pages via PCIe DMA.
  4. Decompression Routing: If a compressed format such as GDeflate is signaled in the request structure, DirectStorage skips the CPU host entirely and enqueues a series of compute dispatch dispatches directly onto the DirectX 12 direct or copy queues.
  5. Synchronization: An ID3D12Fence is signaled on the GPU timeline when the decompression and memory copies are completed. The CPU never stalls waiting for disk I/O or decompressors.

BypassIO and NVMe Command Queue Submission

The core operating system component enabling DirectStorage's high bandwidth is BypassIO. Historically, the Windows storage stack required every file I/O request to traverse the File System Minifilter driver stack. While minifilters provide essential security and virtualization facilities, their execution model introduces significant instruction overhead and context-switching latency.

BypassIO establishes an optimized, low-overhead fast path for storage access. When an application opens an asset file using IDStorageFactory::OpenFile, the operating system queries each driver in the storage filter stack to determine whether it supports BypassIO for the requested volume and file handle. A driver qualifies for BypassIO only if it does not require inspection, encryption, or modification of the underlying raw data blocks.

LEGACY OS STORAGE STACK:
[App] -> [I/O Manager] -> [Minifilter 1] -> [Minifilter 2] -> [NTFS] -> [Class Driver] -> [StorNVMe] -> [NVMe SSD]

BYPASSIO FAST PATH:
[DirectStorage Runtime] -----------------------------------------------------> [StorNVMe] -> [NVMe SSD]

When BypassIO is active:

  • The entire NTFS file system stack, intermediate cache managers, and non-essential minifilters are bypassed during read dispatches.
  • The I/O Manager transitions from an asynchronous IRP creation pipeline to a direct hardware dispatch.
  • Read operations translate directly into NVMe Submission Queue entries (SQEs) directed to the controller's hardware registers.

NVMe drives utilize circular ring buffers mapped into host memory (Submission Queues and Completion Queues). In a high-throughput gaming scenario, the host writes NVMe commands containing the Starting LBA (Logical Block Address) and Number of Logical Blocks directly to the Submission Queue, subsequently writing to the controller's doorbell register. The NVMe controller fetches the commands via DMA, transfers the data across the PCIe physical lanes, and writes a Completion Queue Entry (CQE) back to host memory. By stripping out the intermediate file system layers, DirectStorage saturates the NVMe hardware queue depth (often operating at Queue Depth 32 or 64) with virtually zero CPU cycle investment.

GPU Decompression Architecture: GDeflate Compute Shaders

Eliminating the file system bottleneck exposes the next major throughput limiter: data decompression. Standard asset archives are compressed to minimize distribution footprint and maximize effective storage capacity. If a title streams uncompressed assets at 10 GB/s over PCIe 4.0/5.0, the storage bus is quickly saturated. However, streaming compressed data at 8 GB/s that unpacks to 16 GB/s requires decompression hardware capable of keeping pace with that data rate in real time.

DirectStorage addresses this via GDeflate, a lossless compression format engineered specifically for massively parallel SIMD and SIMT architectures. Traditional compression formats (such as DEFLATE or LZ4) maintain a strictly serial decoding state: an output byte depends continuously on sliding window offsets that were unpacked only a few cycles earlier. A single CPU thread cannot easily parallelize a single LZ4 or DEFLATE stream.

       +-----------------------------------------------------------+
       |             GDeflate Compressed Bitstream                 |
       |  [Tile 0: 64 KB]  |  [Tile 1: 64 KB]  |  [Tile 2: 64 KB]  |
       +-----------------------------------------------------------+
               |                   |                   |
               v                   v                   v
       +---------------+   +---------------+   +---------------+
       | GPU Thread-   |   | GPU Thread-   |   | GPU Thread-   |
       | Group (LDS)   |   | Group (LDS)   |   | Group (LDS)   |
       |  - Huffman    |   |  - Huffman    |   |  - Huffman    |
       |    Decode     |   |    Decode     |   |    Decode     |
       |  - LZ History |   |  - LZ History |   |  - LZ History |
       |    Unroll     |   |    Unroll     |   |    Unroll     |
       +---------------+   +---------------+   +---------------+
               |                   |                   |
               +-------------------+-------------------+
                                   |
                                   v
       +-----------------------------------------------------------+
       |             Decompressed Output in GPU VRAM               |
       |  Directly accessible as Raw Buffer or Swizzled Texture    |
       +-----------------------------------------------------------+

GDeflate solves the serialization problem by splitting the source stream into independent, self-contained data tiles (typically 64 KiB chunks). Within each tile, data is split into multiple parallel bitstreams encoding:

  1. Prefix/Huffman symbol tables.
  2. Literal byte sequences.
  3. Match lengths and back-reference offsets (LZ77-style dictionary copying).

Compute Shader Execution Model

When DirectStorage receives compressed blocks from the NVMe controller, it binds the source staging buffer as a Shader Resource View (SRV) and the final target GPU resource as an Unordered Access View (UAV). It then executes compute shaders across thousands of shader cores:

  • Workgroup Mapping: Each threadgroup (typically consisting of 32 or 64 threads, matching the GPU's native wave/warp size) is assigned a dedicated GDeflate tile.
  • Local Data Share (LDS) Utilization: Threads load the Huffman coding tables and sliding dictionary windows into fast on-chip shared memory (LDS/Shared Memory), completely avoiding global VRAM roundtrips during dictionary reconstruction.
  • Bit-Level Parallelism: Threads within a single wavefront execute parallel prefix sums (scans) to calculate variable-length bit offsets concurrently, decoding multiple tokens per clock cycle.
  • Direct Output Swizzling: Because modern GPUs store textures in proprietary swizzled layouts (such as Morton order or tiled Z-curves) for optimal spatial cache locality during sampling, the decompression shader can write decompressed texels directly into the swizzled target memory format, eliminating a separate post-process texture copy.

Just as modern display rendering engines use real-time spatial compression pipelines—such as those described in How Display Stream Compression Actually Works in GPUs—GDeflate relies on deterministic hardware parallelism to achieve real-time decoding rates exceeding 20 GB/s on modern discrete GPUs.

Memory Footprint, Residency Management, and Barrier Synchronization

DirectStorage asset streaming fundamentally changes resource residency models in DirectX 12. Instead of allocating monolithic textures into GPU memory up front, engines utilize Reserved Resources (also known as Virtual Textures or Tiled Textures).

In this paradigm, a large 8K surface is backed by a sparse virtual memory allocation where only the mipmap levels currently visible to the camera are committed to physical VRAM pages. As the player traverses the environment, the engine computes mipmap residency requirements using feedback maps generated by the pixel shader. When higher-resolution mips are required, DirectStorage requests stream only the required 64 KiB tiles directly into the sparse resource's physical pages.

// DirectStorage Queue Initialization and Request Dispatch Setup
DSTORAGE_QUEUE_DESC queueDesc = {};
queueDesc.Capacity = 128;
queueDesc.Priority = DSTORAGE_PRIORITY_NORMAL;
queueDesc.SourceType = DSTORAGE_REQUEST_SOURCE_FILE;
queueDesc.Device = pD3D12Device;

IDStorageQueue* pStorageQueue = nullptr;
pDStorageFactory->CreateQueue(&queueDesc, IID_PPV_ARGS(&pStorageQueue));

// Define the asynchronous streaming request
DSTORAGE_REQUEST request = {};
request.Options.SourceType = DSTORAGE_REQUEST_SOURCE_FILE;
request.Options.DestinationType = DSTORAGE_REQUEST_DESTINATION_MULTIPLE_SUBRESOURCES;
request.Options.CompressionFormat = DSTORAGE_COMPRESSION_FORMAT_GDEFLATE;

request.Source.File.Source = pAssetFile;
request.Source.File.Offset = assetByteOffset;
request.Source.File.Size = compressedSizeOnDisk;

request.Destination.MultipleSubresources.Resource = pVRAMTexture;
request.Destination.MultipleSubresources.FirstSubresource = targetMipLevel;
request.UncompressedSize = uncompressedMipSize;

// Enqueue request and synchronize via a DirectX 12 Fence
pStorageQueue->EnqueueRequest(&request);
pStorageQueue->EnqueueSignal(pAssetFence, expectedFenceValue);
pStorageQueue->Submit();

Barrier Synchronization and Timeline Coordination

To ensure that the graphics pipeline does not sample a sparse texture tile while a GDeflate compute shader is actively writing decompressed bytes into it, strict synchronization semantics are enforced:

  1. Queue Enqueue: The application enqueues the DSTORAGE_REQUEST alongside a fence signal command via IDStorageQueue::EnqueueSignal.
  2. GPU Timeline Tracking: The DirectStorage runtime handles the internal state transitions, issuing UAV barriers on its internal copy and compute queues.
  3. Engine Command Synchronization: The game engine's main direct command queue executes ID3D12CommandQueue::Wait(pAssetFence, expectedFenceValue) prior to submitting draw calls referencing the newly updated mip levels.
  4. Zero CPU Interactivity: The entire handoff—from storage transfer to decompression to texture state transitions—occurs strictly on the GPU timeline. The CPU simply checks the fence value asynchronously on subsequent frames to retire tracking metadata.

Conclusion

DirectStorage represents a foundational architectural transition in consumer computing systems, restructuring the storage pipeline to match the throughput capabilities of high-speed NVMe storage and modern GPU microarchitectures. By removing the file system filter stack via BypassIO, batching requests into hardware-aligned submission queues, and executing decompression directly on GPU compute units via parallel algorithms like GDeflate, DirectStorage eliminates the historical I/O bottlenecks that have plagued game engines for decades.

As game worlds scale in geometric complexity and texture resolution, the ability to stream assets directly into sparse VRAM allocations on a sub-millisecond timeline bridges the gap between persistent storage and volatile memory. For systems engineers and graphics architects, understanding this direct hardware-to-accelerator storage path is essential for building real-time rendering systems that fully utilize modern computing hardware.

Measured on our own hardware: What 64 Bytes of Padding Are Worth

How much throughput does false sharing cost when several threads increment counters that share one cache line, compared with the same counters padded onto separate lines?

We ran it. The numbers below come from a program executed on the server hosting this site on 2026-09-08 — an AMD EPYC 9354P 32-Core Processor with 8 cores visible, 31.3 GB of memory, Linux 6.8.0-139-generic.

Metric Value
slowdown factor 15.39
iterations per thread 10000000
padded best seconds 0.0273
padded mean seconds 0.0291
padded million ops per sec 2931.56
repeats 5
shared line best seconds 0.42
shared line mean seconds 0.5119
shared line million ops per sec 190.47
threads 8

This is a shared virtual server, not an isolated test rig, so treat the absolute figures as indicative and the ratio between the two cases as the finding. The full method, the machine specification, and the complete source code are on the What 64 Bytes of Padding Are Worth benchmark page, so you can check the method or run it yourself.

Measured on our own hardware: io_uring vs pread: Random Reads at Queue Depth 32

How many random 4 KiB reads per second can one thread sustain with serial pread() syscalls, compared with io_uring submitting 32 at a time, when the page cache is taken out of the picture?

We ran it. The numbers below come from a program executed on the server hosting this site on 2026-09-08 — an AMD EPYC 9354P 32-Core Processor with 8 cores visible, 31.3 GB of memory, Linux 6.8.0-139-generic.

Metric Value
io uring speedup factor 7.44
block bytes 4096
blocks read 20000
io uring best seconds 0.4827
io uring iops 41433
o direct true
pread best seconds 3.5923
pread iops 5567
pread mean latency us 179.62
queue depth 32
repeats 3

This is a shared virtual server, not an isolated test rig, so treat the absolute figures as indicative and the ratio between the two cases as the finding. The full method, the machine specification, and the complete source code are on the io_uring vs pread: Random Reads at Queue Depth 32 benchmark page, so you can check the method or run it yourself.

References