playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
2 lines (1 loc) • 3.74 kB
TypeScript
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";