export const computeGsplatLocalBitonicSource: "\n\nconst MAX_TILE_ENTRIES: u32 = 4096u;\nconst INDEX_BITS: u32 = 12u;\nconst INDEX_MASK: u32 = 0xFFFu;\nconst DEPTH_LEVELS: f32 = 1048575.0;\nconst BITONIC_WG_SIZE: u32 = 256u;\n\nvar<workgroup> sData: array<u32, 4096>;\nvar<workgroup> sDepthMin: atomic<u32>;\nvar<workgroup> sDepthMax: atomic<u32>;\n\nfn insertZeroBit(v: u32, bitPos: u32) -> u32 {\n    let mask = (1u << bitPos) - 1u;\n    return ((v >> bitPos) << (bitPos + 1u)) | (v & mask);\n}\n\nfn bitonicSortRange(localIdx: u32, tStart: u32, count: u32) {\n    let clampedCount = min(count, MAX_TILE_ENTRIES);\n\n    if (clampedCount <= 1u) {\n        return;\n    }\n\n    // Phase 1: Load f32 depths (as bitcast u32) into shared memory\n    if (localIdx == 0u) {\n        atomicStore(&sDepthMin, 0xFFFFFFFFu);\n        atomicStore(&sDepthMax, 0u);\n    }\n\n    var sortN: u32 = 1u;\n    while (sortN < clampedCount) {\n        sortN = sortN << 1u;\n    }\n\n    for (var i: u32 = localIdx; i < sortN; i += BITONIC_WG_SIZE) {\n        if (i < clampedCount) {\n            let entryIdx = tileEntries[tStart + i];\n            sData[i] = depthBuffer[entryIdx];\n        } else {\n            sData[i] = 0xFFFFFFFFu;\n        }\n    }\n\n    workgroupBarrier();\n\n    // Phase 2: Per-tile min/max reduction via atomics\n    for (var i: u32 = localIdx; i < clampedCount; i += BITONIC_WG_SIZE) {\n        atomicMin(&sDepthMin, sData[i]);\n        atomicMax(&sDepthMax, sData[i]);\n    }\n\n    workgroupBarrier();\n\n    let depthMinU = atomicLoad(&sDepthMin);\n    let depthMaxU = atomicLoad(&sDepthMax);\n    let depthMin = bitcast<f32>(depthMinU);\n    let depthMax = bitcast<f32>(depthMaxU);\n\n    // Logarithmic quantization: more precision for near depths, less for far,\n    // matching the bucket sort's approach. Reduces depth collisions at distance.\n    let logMin = log(max(depthMin, 1e-6));\n    let logRange = log(max(depthMax, 1e-6)) - logMin;\n    let invLogRange = select(DEPTH_LEVELS / logRange, 0.0, logRange < 1e-10);\n\n    // Phase 3: In-place repack to (depth20 << 12 | localIndex12)\n    for (var i: u32 = localIdx; i < sortN; i += BITONIC_WG_SIZE) {\n        if (i < clampedCount) {\n            let depth = bitcast<f32>(sData[i]);\n            let logDepth = log(max(depth, 1e-6));\n            let depth20 = min(u32((logDepth - logMin) * invLogRange + 0.5), u32(DEPTH_LEVELS));\n            sData[i] = (depth20 << INDEX_BITS) | i;\n        } else {\n            sData[i] = 0xFFFFFFFFu;\n        }\n    }\n\n    workgroupBarrier();\n\n    // Phase 4: Bitonic sort on packed values\n    for (var k: u32 = 2u; k <= sortN; k = k << 1u) {\n        for (var j: u32 = k >> 1u; j > 0u; j = j >> 1u) {\n            let bitPos = countTrailingZeros(j);\n            let halfN = sortN >> 1u;\n            for (var c: u32 = localIdx; c < halfN; c += BITONIC_WG_SIZE) {\n                let l = insertZeroBit(c, bitPos);\n                let r = l | j;\n\n                let ascending = (l & k) == 0u;\n                let shouldSwap = select(sData[l] < sData[r], sData[l] > sData[r], ascending);\n                if (shouldSwap) {\n                    let tmp = sData[l]; sData[l] = sData[r]; sData[r] = tmp;\n                }\n            }\n            workgroupBarrier();\n        }\n    }\n\n    // Phase 5: Extract local indices and write sorted global entries back\n    for (var i: u32 = localIdx; i < clampedCount; i += BITONIC_WG_SIZE) {\n        let localIndex = sData[i] & INDEX_MASK;\n        sData[i] = tileEntries[tStart + localIndex];\n    }\n\n    workgroupBarrier();\n\n    for (var i: u32 = localIdx; i < clampedCount; i += BITONIC_WG_SIZE) {\n        tileEntries[tStart + i] = sData[i];\n    }\n}\n";
