playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
2 lines (1 loc) • 17.6 kB
TypeScript
export const computeGsplatLocalRasterizeSource: "\n\n#include \"halfTypesCS\"\n#ifndef PICK_MODE\n #include \"decodePS\"\n #if FOG != NONE\n #include \"fogMathPS\"\n #include \"gammaPS\"\n #endif\n#endif\n\nconst CACHE_STRIDE: u32 = 7u;\nconst BATCH_SIZE: u32 = 64u;\nconst ALPHA_THRESHOLD: half = half(1.0) / half(255.0);\nconst EXP4: half = exp(half(-4.0));\nconst INV_EXP4: half = half(1.0) / (half(1.0) - EXP4);\n\n// Shared uniforms and storage buffers (same layout for both color and pick modes).\n// Pick-only uniforms (nearClip, farClip, alphaClip) are present but unused in color mode.\nstruct Uniforms {\n screenWidth: u32,\n screenHeight: u32,\n numTilesX: u32,\n nearClip: f32,\n farClip: f32,\n alphaClip: f32,\n #if FOG != NONE\n fog_color: vec3f,\n fog_start: f32,\n fog_end: f32,\n fog_density: f32,\n #endif\n}\n@group(0) @binding(0) var<uniform> uniforms: Uniforms;\n@group(0) @binding(1) var<storage, read> tileEntries: array<u32>;\n@group(0) @binding(2) var<storage, read> tileSplatCounts: array<u32>;\n@group(0) @binding(3) var<storage, read> projCache: array<u32>;\n@group(0) @binding(4) var<storage, read> rasterizeTileList: array<u32>;\n@group(0) @binding(5) var<storage, read> tileListCounts: array<u32>;\n@group(0) @binding(6) var<storage, read> depthBuffer: array<u32>;\n\n// Mode-specific output textures appended after the shared bindings.\n#ifdef PICK_MODE\n @group(0) @binding(7) var pickIdTexture: texture_storage_2d<r32uint, write>;\n @group(0) @binding(8) var pickDepthTexture: texture_storage_2d<rgba16float, write>;\n#else\n @group(0) @binding(7) var outputTexture: texture_storage_2d<rgba16float, write>;\n #ifdef DEPTH_TEST\n @group(0) @binding(8) var sceneDepthMap: texture_2d<f32>;\n #endif\n#endif\n\nvar<workgroup> sharedCenterScreen: array<vec2f, 64>;\nvar<workgroup> sharedCoeffs: array<vec3f, 64>;\n#ifdef HEATMAP_MODE\n var<workgroup> sharedHeatCount: atomic<u32>;\n#endif\n\n// Pick mode stores per-splat opacity, ID and depth; color mode stores packed RGBA.\n// Depth test mode also needs per-splat view depth for occlusion against scene geometry.\n#ifdef PICK_MODE\n var<workgroup> sharedOpacity: array<half, 64>;\n var<workgroup> sharedPickId: array<u32, 64>;\n var<workgroup> sharedViewDepth: array<f32, 64>;\n#else\n var<workgroup> sharedColor: array<half4, 64>;\n #ifdef DEPTH_TEST\n var<workgroup> sharedViewDepth: array<f32, 64>;\n #endif\n#endif\n\n// Evaluate a single splat for picking. Records the front-most pick ID (first splat above\n// alphaClip) and accumulates alpha-weighted depth for sub-pixel depth reconstruction.\n#ifdef PICK_MODE\nfn evalSplatPick(pixelCoord: vec2f, center: vec2f, coeffX: f32, coeffY: f32, coeffXY: f32,\n opacity: half, pickId: u32, viewDepth: f32, alphaClip: half,\n bestPickId: ptr<function, u32>, depthAccum: ptr<function, f32>,\n weightAccum: ptr<function, f32>, T: ptr<function, half>) {\n let dx = pixelCoord - center;\n let power = coeffX * dx.x * dx.x + coeffXY * dx.x * dx.y + coeffY * dx.y * dx.y;\n let gauss = (half(exp(power)) - EXP4) * INV_EXP4;\n let alpha = half(min(half(0.99), opacity * gauss));\n let newT = *T * (half(1.0) - alpha);\n let visible = power > -4.0 && alpha > ALPHA_THRESHOLD && *T >= ALPHA_THRESHOLD;\n if (!visible) { return; }\n\n // Per-pixel alphaClip: only solid splat centers contribute to pick\n if (alpha >= alphaClip) {\n if (*bestPickId == 0xFFFFFFFFu) {\n *bestPickId = pickId;\n }\n let normalizedDepth = saturate((viewDepth - uniforms.nearClip) / (uniforms.farClip - uniforms.nearClip));\n let w = f32(alpha) * f32(*T);\n *depthAccum += w * normalizedDepth;\n *weightAccum += w;\n }\n\n *T = newT;\n}\n#endif\n\n#ifdef HEATMAP_MODE\nfn heatmapColor(v: f32) -> vec3f {\n let t = saturate(v / 2000.0);\n if (t < 0.2) {\n return mix(vec3f(0.0, 0.0, 1.0), vec3f(0.0, 1.0, 1.0), t * 5.0);\n } else if (t < 0.4) {\n return mix(vec3f(0.0, 1.0, 1.0), vec3f(1.0, 1.0, 0.0), (t - 0.2) * 5.0);\n } else if (t < 0.6) {\n return mix(vec3f(1.0, 1.0, 0.0), vec3f(1.0, 0.0, 0.0), (t - 0.4) * 5.0);\n }\n return mix(vec3f(1.0, 0.0, 0.0), vec3f(0.15, 0.0, 0.0), (t - 0.6) * 2.5);\n}\n#endif\n\n@compute @workgroup_size(8, 8)\nfn main(\n @builtin(local_invocation_id) lid: vec3u,\n @builtin(local_invocation_index) localIdx: u32,\n @builtin(workgroup_id) wid: vec3u,\n @builtin(num_workgroups) numWorkgroups: vec3u\n) {\n let workgroupIdx = wid.y * numWorkgroups.x + wid.x;\n if (workgroupIdx >= tileListCounts[2]) {\n return;\n }\n let tileIdx = rasterizeTileList[workgroupIdx];\n let tileX = tileIdx % uniforms.numTilesX;\n let tileY = tileIdx / uniforms.numTilesX;\n let tStart = tileSplatCounts[tileIdx];\n let tEnd = tileSplatCounts[tileIdx + 1u];\n\n let basePixel = vec2u(tileX * 16u + lid.x * 2u, tileY * 16u + lid.y * 2u);\n let p00 = vec2f(f32(basePixel.x) + 0.5, f32(basePixel.y) + 0.5);\n let p10 = p00 + vec2f(1.0, 0.0);\n let p01 = p00 + vec2f(0.0, 1.0);\n let p11 = p00 + vec2f(1.0, 1.0);\n\n // Per-pixel state for the 2x2 quad.\n\n #ifdef PICK_MODE\n var T00: half = half(1.0); var T10: half = half(1.0);\n var T01: half = half(1.0); var T11: half = half(1.0);\n\n // front-most pick ID per pixel\n var pickId00: u32 = 0xFFFFFFFFu; var pickId10: u32 = 0xFFFFFFFFu;\n var pickId01: u32 = 0xFFFFFFFFu; var pickId11: u32 = 0xFFFFFFFFu;\n\n // alpha-weighted depth accumulators\n var dAcc00: f32 = 0.0; var dAcc10: f32 = 0.0;\n var dAcc01: f32 = 0.0; var dAcc11: f32 = 0.0;\n\n // alpha-weighted weight accumulators\n var wAcc00: f32 = 0.0; var wAcc10: f32 = 0.0;\n var wAcc01: f32 = 0.0; var wAcc11: f32 = 0.0;\n let clipH = half(uniforms.alphaClip);\n #else\n // Transmittance for the 2x2 quad packed as vec4<half> (x=00, y=10, z=01, w=11).\n // Tracking how much light passes through the splat stack at that pixel. Packing\n // them into a single vec4 lets the compiler use vector ALU for the branchless\n // alpha-blending update and the all-saturated early-out test.\n var T = half4(1.0);\n\n // accumulated color per pixel\n var c00 = half3(0.0); var c10 = half3(0.0);\n var c01 = half3(0.0); var c11 = half3(0.0);\n\n #ifdef DEPTH_TEST\n // Load per-pixel linear scene depth for the 2x2 quad (x=00, y=10, z=01, w=11).\n // Splats behind this depth are skipped during rasterization.\n var sceneDepth = vec4f(1e20);\n let depthY0 = uniforms.screenHeight - 1u - basePixel.y;\n let depthY1 = depthY0 - 1u;\n if (basePixel.x < uniforms.screenWidth && basePixel.y < uniforms.screenHeight) {\n sceneDepth.x = textureLoad(sceneDepthMap, vec2i(vec2u(basePixel.x, depthY0)), 0).r;\n }\n if (basePixel.x + 1u < uniforms.screenWidth && basePixel.y < uniforms.screenHeight) {\n sceneDepth.y = textureLoad(sceneDepthMap, vec2i(vec2u(basePixel.x + 1u, depthY0)), 0).r;\n }\n if (basePixel.x < uniforms.screenWidth && basePixel.y + 1u < uniforms.screenHeight) {\n sceneDepth.z = textureLoad(sceneDepthMap, vec2i(vec2u(basePixel.x, depthY1)), 0).r;\n }\n if (basePixel.x + 1u < uniforms.screenWidth && basePixel.y + 1u < uniforms.screenHeight) {\n sceneDepth.w = textureLoad(sceneDepthMap, vec2i(vec2u(basePixel.x + 1u, depthY1)), 0).r;\n }\n #endif\n #endif\n\n let tileCount = tEnd - tStart;\n\n #ifdef HEATMAP_MODE\n if (localIdx == 0u) { atomicStore(&sharedHeatCount, 0u); }\n workgroupBarrier();\n var processedCount: u32 = 0u;\n #endif\n\n let numBatches = (tileCount + BATCH_SIZE - 1u) / BATCH_SIZE;\n var threadDone = false;\n\n for (var batch: u32 = 0u; batch < numBatches; batch++) {\n\n let batchOffset = batch * BATCH_SIZE + localIdx;\n if (batchOffset < tileCount) {\n let cacheIdx = tileEntries[tStart + batchOffset];\n let base = cacheIdx * CACHE_STRIDE;\n sharedCenterScreen[localIdx] = vec2f(\n bitcast<f32>(projCache[base + 0u]),\n bitcast<f32>(projCache[base + 1u])\n );\n // Conic values cx/cy/cz stored as f32; convert to evaluation coefficients.\n let cx = bitcast<f32>(projCache[base + 2u]);\n let cy = bitcast<f32>(projCache[base + 3u]);\n let cz = bitcast<f32>(projCache[base + 4u]);\n sharedCoeffs[localIdx] = vec3f(cx * -0.5, cz * -0.5, -cy);\n\n #ifdef PICK_MODE\n sharedPickId[localIdx] = projCache[base + 5u];\n sharedOpacity[localIdx] = half(unpack2x16float(projCache[base + 6u]).y);\n sharedViewDepth[localIdx] = bitcast<f32>(depthBuffer[cacheIdx]);\n #else\n let rg = unpack2x16float(projCache[base + 5u]);\n let ba = unpack2x16float(projCache[base + 6u]);\n\n #if FOG != NONE\n let viewDepth = bitcast<f32>(depthBuffer[cacheIdx]);\n #if (FOG == LINEAR)\n let fogFactor = evaluateFogFactorLinear(viewDepth, uniforms.fog_start, uniforms.fog_end);\n #elif (FOG == EXP)\n let fogFactor = evaluateFogFactorExp(viewDepth, uniforms.fog_density);\n #elif (FOG == EXP2)\n let fogFactor = evaluateFogFactorExp2(viewDepth, uniforms.fog_density);\n #endif\n var foggedColor = decodeGamma3(vec3f(rg.x, rg.y, ba.x));\n foggedColor = mix(uniforms.fog_color, foggedColor, fogFactor);\n sharedColor[localIdx] = half4(half3(gammaCorrectOutput(foggedColor)), half(ba.y));\n #else\n sharedColor[localIdx] = half4(half(rg.x), half(rg.y), half(ba.x), half(ba.y));\n #endif\n\n #ifdef DEPTH_TEST\n sharedViewDepth[localIdx] = bitcast<f32>(depthBuffer[cacheIdx]);\n #endif\n #endif\n }\n\n workgroupBarrier();\n\n if (!threadDone) {\n let batchCount = min(BATCH_SIZE, tileCount - batch * BATCH_SIZE);\n\n for (var i: u32 = 0u; i < batchCount; i++) {\n let center = sharedCenterScreen[i];\n let coeffs = sharedCoeffs[i];\n\n #ifdef PICK_MODE\n let splatOpacity = sharedOpacity[i];\n let splatPickId = sharedPickId[i];\n let splatDepth = sharedViewDepth[i];\n\n evalSplatPick(p00, center, coeffs.x, coeffs.y, coeffs.z, splatOpacity, splatPickId, splatDepth, clipH, &pickId00, &dAcc00, &wAcc00, &T00);\n evalSplatPick(p10, center, coeffs.x, coeffs.y, coeffs.z, splatOpacity, splatPickId, splatDepth, clipH, &pickId10, &dAcc10, &wAcc10, &T10);\n evalSplatPick(p01, center, coeffs.x, coeffs.y, coeffs.z, splatOpacity, splatPickId, splatDepth, clipH, &pickId01, &dAcc01, &wAcc01, &T01);\n evalSplatPick(p11, center, coeffs.x, coeffs.y, coeffs.z, splatOpacity, splatPickId, splatDepth, clipH, &pickId11, &dAcc11, &wAcc11, &T11);\n\n if (all(vec4<half>(T00, T10, T01, T11) < half4(ALPHA_THRESHOLD))) {\n threadDone = true;\n break;\n }\n #else\n let splatColor = sharedColor[i];\n\n #ifdef DEPTH_TEST\n let splatDepth = sharedViewDepth[i];\n\n // Splats are front-to-back; if behind all four depth samples, all remaining splats will be too.\n if (all(vec4f(splatDepth) > sceneDepth)) {\n threadDone = true;\n break;\n }\n #endif\n\n // Vectorized Gaussian evaluation for the 2x2 pixel quad. Compute dx\n // once from p00, build the four pixel offsets as vec4f (exploiting the\n // regular +1 grid), and evaluate power/gauss/alpha/transmittance as\n // vec4 operations to share ALU across the quad.\n let d = p00 - center;\n let dxV = vec4f(d.x, d.x + 1.0, d.x, d.x + 1.0);\n let dyV = vec4f(d.y, d.y, d.y + 1.0, d.y + 1.0);\n let power4 = coeffs.x * dxV * dxV + coeffs.z * dxV * dyV + coeffs.y * dyV * dyV;\n let gauss4 = (half4(exp(power4)) - half4(EXP4)) * half4(INV_EXP4);\n let alpha4 = min(half4(0.99), half4(splatColor.a) * gauss4);\n let newT = T * (half4(1.0) - alpha4);\n\n var valid = (power4 > vec4f(-4.0)) & (alpha4 > half4(ALPHA_THRESHOLD)) & (T >= half4(ALPHA_THRESHOLD));\n #ifdef DEPTH_TEST\n valid = valid & (vec4f(splatDepth) <= sceneDepth);\n #endif\n\n let weight = alpha4 * T * select(half4(0.0), half4(1.0), valid);\n c00 += splatColor.rgb * weight.x;\n c10 += splatColor.rgb * weight.y;\n c01 += splatColor.rgb * weight.z;\n c11 += splatColor.rgb * weight.w;\n T = select(T, newT, valid);\n\n #ifdef HEATMAP_MODE\n processedCount += 1u;\n #endif\n\n if (all(T < half4(ALPHA_THRESHOLD))) {\n threadDone = true;\n break;\n }\n #endif\n }\n }\n\n workgroupBarrier();\n }\n\n #ifdef HEATMAP_MODE\n atomicAdd(&sharedHeatCount, processedCount);\n workgroupBarrier();\n let avgCount = f32(atomicLoad(&sharedHeatCount)) / 64.0;\n let heatColor = vec4f(heatmapColor(avgCount), 1.0);\n if (basePixel.x < uniforms.screenWidth && basePixel.y < uniforms.screenHeight) {\n textureStore(outputTexture, basePixel, heatColor);\n }\n if (basePixel.x + 1u < uniforms.screenWidth && basePixel.y < uniforms.screenHeight) {\n textureStore(outputTexture, vec2u(basePixel.x + 1u, basePixel.y), heatColor);\n }\n if (basePixel.x < uniforms.screenWidth && basePixel.y + 1u < uniforms.screenHeight) {\n textureStore(outputTexture, vec2u(basePixel.x, basePixel.y + 1u), heatColor);\n }\n if (basePixel.x + 1u < uniforms.screenWidth && basePixel.y + 1u < uniforms.screenHeight) {\n textureStore(outputTexture, vec2u(basePixel.x + 1u, basePixel.y + 1u), heatColor);\n }\n #else\n\n // Write results for the 2x2 pixel quad owned by this thread.\n // Pick mode: store the front-most pick ID and (accumulated depth, weight) per pixel.\n // Color mode: convert accumulated gamma-space color to linear via decodeGamma3 and store\n // to the rgba16float output texture; alpha holds total opacity (1 - transmittance).\n if (basePixel.x < uniforms.screenWidth && basePixel.y < uniforms.screenHeight) {\n #ifdef PICK_MODE\n textureStore(pickIdTexture, basePixel, vec4u(pickId00, 0u, 0u, 0u));\n textureStore(pickDepthTexture, basePixel, vec4f(dAcc00, wAcc00, 0.0, 0.0));\n #else\n textureStore(outputTexture, basePixel, vec4f(decodeGamma3(vec3f(c00)), f32(half(1.0) - T.x)));\n #endif\n }\n if (basePixel.x + 1u < uniforms.screenWidth && basePixel.y < uniforms.screenHeight) {\n let px10 = vec2u(basePixel.x + 1u, basePixel.y);\n #ifdef PICK_MODE\n textureStore(pickIdTexture, px10, vec4u(pickId10, 0u, 0u, 0u));\n textureStore(pickDepthTexture, px10, vec4f(dAcc10, wAcc10, 0.0, 0.0));\n #else\n textureStore(outputTexture, px10, vec4f(decodeGamma3(vec3f(c10)), f32(half(1.0) - T.y)));\n #endif\n }\n if (basePixel.x < uniforms.screenWidth && basePixel.y + 1u < uniforms.screenHeight) {\n let px01 = vec2u(basePixel.x, basePixel.y + 1u);\n #ifdef PICK_MODE\n textureStore(pickIdTexture, px01, vec4u(pickId01, 0u, 0u, 0u));\n textureStore(pickDepthTexture, px01, vec4f(dAcc01, wAcc01, 0.0, 0.0));\n #else\n textureStore(outputTexture, px01, vec4f(decodeGamma3(vec3f(c01)), f32(half(1.0) - T.z)));\n #endif\n }\n if (basePixel.x + 1u < uniforms.screenWidth && basePixel.y + 1u < uniforms.screenHeight) {\n let px11 = vec2u(basePixel.x + 1u, basePixel.y + 1u);\n #ifdef PICK_MODE\n textureStore(pickIdTexture, px11, vec4u(pickId11, 0u, 0u, 0u));\n textureStore(pickDepthTexture, px11, vec4f(dAcc11, wAcc11, 0.0, 0.0));\n #else\n textureStore(outputTexture, px11, vec4f(decodeGamma3(vec3f(c11)), f32(half(1.0) - T.w)));\n #endif\n }\n\n #endif\n}\n";