UAVs, Atomics & Append/Consume Buffers

UAVs (RW* types) allow arbitrary read/write from compute and pixel shaders (and other stages in D3D12).

28.1 RW resources

UAVs (RW* types) allow arbitrary read/write from compute and pixel shaders (and other stages in D3D12). Writes from different threads are unordered; use atomics or barriers for correctness. globallycoherent makes UAV writes visible across thread groups within a dispatch (with DeviceMemoryBarrier).

28.2 Interlocked (atomic) operations

InterlockedAdd(dest, v [, orig]) signed/unsigned by type InterlockedExchange(dest, v, orig)

InterlockedCompareExchange(dest, cmp, v, orig) InterlockedCompareStore(dest, cmp, v) CAS without return

SM 6.6: 64-bit atomics on uint64_t resources/groupshared; InterlockedCompareExchangeFloatBitwise, InterlockedCompareStoreFloatBitwise

RWByteAddressBuffer       gCounters  : register(u0);
RWTexture2D<uint>         gMaxDepth  : register(u1);
RWStructuredBuffer<uint>  gHistogram : register(u2);
groupshared uint          sLocalHist[256];

[numthreads(16, 16, 1)]
void HistogramCS(uint3 dtid : SV_DispatchThreadID, uint gi : SV_GroupIndex)
{
    sLocalHist[gi] = 0;                                   // 256 threads clear 256 bins
    GroupMemoryBarrierWithGroupSync();

    if (all(dtid.xy < gSize))
    {
        float lum = dot(gInput[dtid.xy].rgb, float3(0.2126, 0.7152, 0.0722));
        uint bin = (uint)(saturate(log2(lum + 1e-4) / 16.0 + 0.5) * 255.0);
        InterlockedAdd(sLocalHist[bin], 1);               // fast shared-memory atomic
    }
    GroupMemoryBarrierWithGroupSync();

    InterlockedAdd(gHistogram[gi], sLocalHist[gi]);       // one global atomic per bin per group

    uint original;
    gCounters.InterlockedAdd(0, 1, original);             // byte-address-buffer method form
    InterlockedMax(gMaxDepth[dtid.xy / 8], asuint(0.5f)); // positive floats compare correctly as uint
}

28.3 Append / consume buffers & hidden counters

The UAV must be created with a counter (D3D12: pCounterResource + CounterOffsetInBytes in CreateUnorderedAccessView; D3D11: D3D11_BUFFER_UAV_FLAG_APPEND). RWStructuredBuffer with a counter exposes IncrementCounter()/DecrementCounter() for index allocation. Many engines replace append buffers with an explicit RWByteAddressBuffer counter + InterlockedAdd (more portable, works with Vulkan).

struct Particle { float3 pos; float life; float3 vel; float pad; };

ConsumeStructuredBuffer<Particle> gAlive   : register(u0);   // input list
AppendStructuredBuffer<Particle>  gSurvive : register(u1);   // output list
cbuffer Sim : register(b0) { float gDt; uint gAliveCount; };

[numthreads(256, 1, 1)]
void UpdateParticlesCS(uint3 id : SV_DispatchThreadID)
{
    if (id.x >= gAliveCount) return;          // never consume more than the counter holds
    Particle p = gAlive.Consume();
    p.vel.y -= 9.81 * gDt;
    p.pos   += p.vel * gDt;
    p.life  -= gDt;
    if (p.life > 0) gSurvive.Append(p);
}

28.4 Rasterizer ordered views (ROVs)

RasterizerOrderedTexture2D<float4> guarantees per-pixel accesses happen in primitive order — enabling programmable blending and order-independent transparency. Costly; requires ROVsSupported.