DirectX 11 vs DirectX 12 Command List Recording
DirectX 11 and DirectX 12 represent fundamentally different architectural philosophies regarding how CPU work is translated into GPU commands. While DirectX 11 uses an abstracted model relying on immediate and deferred contexts managed heavily by the driver, DirectX 12 removes driver intervention to provide low-level, direct control over command generation, allocation, and submission. This article explains the technical differences between how both APIs record command lists, focusing on threading models, memory allocation, pipeline state handling, and command execution.
Architectural Model: Contexts vs. Lists and Allocators
In DirectX 11, command recording revolves around the
ID3D11DeviceContext interface. The API provides two context
types:
- Immediate Context: Interacts directly with the GPU driver to record and immediately dispatch draw calls and state changes on the main rendering thread.
- Deferred Contexts: Intended for secondary threads
to record commands into an
ID3D11CommandListtoken stream, which must subsequently be executed on the immediate context.
In contrast, DirectX 12 decouples command recording from command execution entirely. The API introduces three distinct objects:
- Command Allocators
(
ID3D12CommandAllocator): Backing storage in system or video memory where recorded commands are written. - Command Lists
(
ID3D12GraphicsCommandList): The recording interface through which commands (draws, dispatches, state updates) are encoded into an associated allocator. - Command Queues (
ID3D12CommandQueue): The GPU timeline interface to which completed command lists are submitted for hardware execution.
Multithreading and CPU Scalability
DirectX 11 struggles with true multithreaded command generation. Because deferred contexts rely on the driver to manage underlying allocations and state inheritance, driver-level locking is frequently required. In many drivers, deferred command lists are replayed or patched sequentially on the immediate context during submission, which creates CPU bottlenecks and diminishes the performance benefits of multithreaded generation.
DirectX 12 enables lock-free, concurrent command recording. Separate threads can record into separate command lists simultaneously, provided each active command list uses its own command allocator per thread. Because DirectX 12 does not support an immediate context, all recording happens offline relative to the GPU, allowing draw call generation to scale linearly across CPU cores without driver contention.
Pipeline States and Validation Overhead
DirectX 11 records commands using a dynamic state machine model.
Programmers alter states independently (e.g., calling
RSSetState, OMSetBlendState, or binding
shaders individually). Consequently, when a command list is recorded or
executed, the graphics driver must validate whether the combination of
individual states is valid for the underlying hardware, introducing
unpredictable CPU overhead during recording.
DirectX 12 requires states to be pre-baked into immutable Pipeline
State Objects (PSOs). A PSO compiles the vertex, pixel, and compute
shaders, rasterizer state, blend state, depth-stencil state, and input
layout into hardware-ready instructions ahead of time. During command
list recording in DirectX 12, changing the pipeline state requires only
a single pointer change via SetPipelineState, drastically
reducing validation logic and CPU overhead during the recording
phase.
Resource Tracking and Hazards
In DirectX 11, the runtime and driver automatically track resource dependencies, hazard detection, and subresource state transitions (such as transitioning a texture from a render target to a shader resource view). This synchronization logic runs invisibly during command recording and execution.
In DirectX 12, resource tracking is entirely explicit. Developers
must insert manual resource barriers (ResourceBarrier) into
the command list to transition subresources between usage states (e.g.,
D3D12_RESOURCE_STATE_RENDER_TARGET to
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE). While this
places the burden of synchronization and cache flushing on the
application, it eliminates the heavy CPU cost of driver-side tracking
during recording.
Command Submission and Reuse
DirectX 11 command lists are recorded by calling
FinishCommandList on a deferred context, then consumed via
ExecuteCommandList on the immediate context, after which
the list is typically discarded or preserved as a static set of
instructions.
DirectX 12 separates memory recycling from command structures. A
closed command list can be immediately submitted to an
ID3D12CommandQueue. Once the GPU finishes processing the
submitted commands, the application resets the
ID3D12CommandAllocator to reclaim memory and calls
Reset on the ID3D12GraphicsCommandList to
begin recording a new frame, avoiding repeated memory allocations and
maximizing throughput.