Quick answer: Track resource state per-resource. Issue ResourceBarrier before any state-changing use. Consider enhanced barriers for new code.

A custom DX12 renderer logs validation: D3D12 ERROR: Resource state at index 0 is INCOMPATIBLE with current state. PIX flags the resource. The render-target was sampled without transitioning to SHADER_RESOURCE.

State Tracking

struct Resource {
    ID3D12Resource* res;
    D3D12_RESOURCE_STATES state;
};

void TransitionTo(ID3D12GraphicsCommandList* cl, Resource& r, D3D12_RESOURCE_STATES toState) {
    if (r.state == toState) return;
    D3D12_RESOURCE_BARRIER b = {};
    b.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
    b.Transition.pResource = r.res;
    b.Transition.StateBefore = r.state;
    b.Transition.StateAfter = toState;
    b.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
    cl->ResourceBarrier(1, &b);
    r.state = toState;
}

Wrapper struct + transition function. Manage per-resource state, only transition when actually needed.

Batch Barriers

Multiple transitions at once is faster than separate barriers:

D3D12_RESOURCE_BARRIER barriers[4];
// fill barriers...
cl->ResourceBarrier(4, barriers);

One API call per group of changes. Driver batches internally.

Enhanced Barriers

D3D12 enhanced barriers separate the access from the layout:

D3D12_TEXTURE_BARRIER tb = {};
tb.SyncBefore = D3D12_BARRIER_SYNC_RENDER_TARGET;
tb.SyncAfter = D3D12_BARRIER_SYNC_PIXEL_SHADING;
tb.AccessBefore = D3D12_BARRIER_ACCESS_RENDER_TARGET;
tb.AccessAfter = D3D12_BARRIER_ACCESS_SHADER_RESOURCE;
tb.LayoutBefore = D3D12_BARRIER_LAYOUT_RENDER_TARGET;
tb.LayoutAfter = D3D12_BARRIER_LAYOUT_SHADER_RESOURCE;
tb.pResource = resource.res;
cl->Barrier(...);

More explicit; matches Vulkan’s split-barrier model. Requires Agility SDK and feature-level support.

Debug Layer

ID3D12Debug* dbg = nullptr;
D3D12GetDebugInterface(IID_PPV_ARGS(&dbg));
dbg->EnableDebugLayer();

Enables validation. Run in dev builds; messages identify exact resources and required states.

Verifying

Run with debug layer. No validation errors. PIX capture shows clean state transitions per draw. No GPU hangs on TDR.

“D3D12 demands explicit state. Manage per resource, batch transitions, validate in dev.”

For shipping render code, build a small state-tracker wrapper. Catches bugs in dev that would otherwise GPU-hang in release.