OverviewFilesCommitsIssuesDocsReleasesPeers

dc-terrain / shaders / scan_single.glsl

106 lines · glsl ref:
#version 450

// ============================================================================
// Single-workgroup exclusive prefix sum (Blelloch up-sweep / down-sweep).
// Handles arrays up to BLOCK_SIZE elements (default 1024) in one dispatch.
//
// Usage: dispatch(1, 1, 1) with N <= BLOCK_SIZE.
// Result: output[i] = sum of input[0..i-1]  (exclusive scan).
//         output[N] (if within buffer) holds total sum.
//
// Bank-conflict-free shared memory via padding.
// ============================================================================

// Each thread handles 2 elements => 512 threads scan 1024 elements.
#define BLOCK_SIZE 1024
#define HALF_BLOCK 512

// Bank conflict avoidance: 32 banks on modern GPUs.
// Pad every 32 elements with 1 slot.
#define NUM_BANKS 32
#define LOG_NUM_BANKS 5
#define CONFLICT_FREE_OFFSET(n) ((n) >> LOG_NUM_BANKS)

// Shared memory: BLOCK_SIZE + padding slots
// Max padding = BLOCK_SIZE / NUM_BANKS = 32 extra slots
shared uint s_data[BLOCK_SIZE + (BLOCK_SIZE >> LOG_NUM_BANKS)];

layout(local_size_x = HALF_BLOCK, local_size_y = 1, local_size_z = 1) in;

layout(set = 0, binding = 0, std430) buffer InputBuf  { uint input_data[];  };
layout(set = 0, binding = 1, std430) buffer OutputBuf { uint output_data[]; };

// Phase B: Push constant replaces UBO — no buffer_update() needed between dispatches.
layout(push_constant) uniform ScanPushConstants {
    uint N;          // number of elements to scan
    uint _pad0;
    uint _pad1;
    uint _pad2;
};

void main() {
    uint thid = gl_LocalInvocationID.x;

    // ---- Load into shared memory with bank-conflict-free indexing ----
    uint ai = thid;
    uint bi = thid + HALF_BLOCK;

    uint bankOffsetA = CONFLICT_FREE_OFFSET(ai);
    uint bankOffsetB = CONFLICT_FREE_OFFSET(bi);

    // Load from global; zero-pad if beyond N
    s_data[ai + bankOffsetA] = (ai < N) ? input_data[ai] : 0u;
    s_data[bi + bankOffsetB] = (bi < N) ? input_data[bi] : 0u;

    // ---- Up-sweep (reduce) phase ----
    uint offset = 1;
    for (uint d = HALF_BLOCK; d > 0; d >>= 1) {
        memoryBarrierShared();
        barrier();

        if (thid < d) {
            uint ai_idx = offset * (2 * thid + 1) - 1;
            uint bi_idx = offset * (2 * thid + 2) - 1;
            ai_idx += CONFLICT_FREE_OFFSET(ai_idx);
            bi_idx += CONFLICT_FREE_OFFSET(bi_idx);
            s_data[bi_idx] += s_data[ai_idx];
        }
        offset <<= 1;
    }

    // ---- Store total sum and clear last element (exclusive scan) ----
    if (thid == 0) {
        uint lastIdx = BLOCK_SIZE - 1 + CONFLICT_FREE_OFFSET(BLOCK_SIZE - 1);
        // Store total sum at output_data[N] if caller allocated space
        if (N <= BLOCK_SIZE) {
            output_data[N] = s_data[lastIdx];
        }
        s_data[lastIdx] = 0u;
    }

    // ---- Down-sweep phase ----
    for (uint d = 1; d < BLOCK_SIZE; d <<= 1) {
        offset >>= 1;
        memoryBarrierShared();
        barrier();

        if (thid < d) {
            uint ai_idx = offset * (2 * thid + 1) - 1;
            uint bi_idx = offset * (2 * thid + 2) - 1;
            ai_idx += CONFLICT_FREE_OFFSET(ai_idx);
            bi_idx += CONFLICT_FREE_OFFSET(bi_idx);

            uint temp = s_data[ai_idx];
            s_data[ai_idx] = s_data[bi_idx];
            s_data[bi_idx] += temp;
        }
    }

    memoryBarrierShared();
    barrier();

    // ---- Write results back to global memory ----
    if (ai < N) output_data[ai] = s_data[ai + bankOffsetA];
    if (bi < N) output_data[bi] = s_data[bi + bankOffsetB];
}