export const computeGsplatShadowCullSource: "\n\n// Conservative gaussian extent (in std-devs) for the per-splat frustum radius, so a splat whose\n// tail still reaches into the light frustum is not culled at the boundary.\nconst SPLAT_FRUSTUM_SIGMA: f32 = 3.0;\n\nstruct CullUniforms {\n    frustumPlanes: array<vec4f, 6>,\n    numIntervals: u32,\n    splatTextureSize: u32,\n    alphaClip: f32,\n    worldSizeThreshold: f32\n};\n@group(0) @binding(0) var<uniform> uniforms: CullUniforms;\n\n// Pass-1 outputs (read): the shared candidate list + the candidate count at [numIntervals].\n@group(0) @binding(1) var<storage, read> compactedSplatIds: array<u32>;\n@group(0) @binding(2) var<storage, read> candidateCountBuffer: array<u32>;\n\n// Per-light outputs: dense visible work-buffer indices (unordered) + a single atomic visible count.\n@group(0) @binding(3) var<storage, read_write> outputIndices: array<u32>;\n@group(0) @binding(4) var<storage, read_write> globalCount: array<atomic<u32>>;\n\n// Work-buffer format texture bindings (binding 5+) and the splat read/modify helpers. setSplat\n// (gsplatComputeSplatCS) reads uniforms.splatTextureSize, so the uniform struct is declared above.\n#include \"gsplatComputeSplatCS\"\n#include \"gsplatFormatDeclCS\"\n#include \"gsplatFormatReadCS\"\n#include \"gsplatHelpersVS\"\n#include \"gsplatModifyVS\"\n\n// Fine per-splat cull: read + apply the render-stage modifier + opacity/size/frustum tests. Returns\n// true when the splat should be drawn into the shadow map.\nfn fineCull(splatId: u32) -> bool {\n    setSplat(splatId);\n\n    // world-space center + render-stage position modifier (matches the quad VS / forward projector)\n    let originalCenter = getCenter();\n    var center = originalCenter;\n    modifySplatCenter(&center);\n\n    // opacity cull (matches the forward alpha cull + the shadow PS alphaClip)\n    let opacity = getOpacity();\n    if (opacity <= uniforms.alphaClip) {\n        return false;\n    }\n\n    // render-stage rotation/scale modifier; getRotation() is (w,x,y,z), the hook contract is (x,y,z,w)\n    var rotation: vec4f = getRotation().yzwx;\n    var scale: vec3f = getScale();\n    modifySplatRotationScale(originalCenter, center, &rotation, &scale);\n\n    // size cull: the orthographic shadow projection is linear, so a splat whose max world-space\n    // extent maps below the precomputed per-light threshold is sub-pixel in the shadow map. The max\n    // scale axis is an orientation-independent upper bound on the projected radius (conservative \u2014\n    // never culls a splat the forward path would keep). A threshold of 0 disables the test.\n    let maxScale = max(scale.x, max(scale.y, scale.z));\n    if (maxScale < uniforms.worldSizeThreshold) {\n        return false;\n    }\n\n    // per-splat frustum cull: the modified center (\u00B1 a conservative gaussian radius) against the\n    // light's 6 frustum planes. Trims splats in boundary nodes; the ortho near/far planes also cull\n    // by depth.\n    let splatRadius = maxScale * SPLAT_FRUSTUM_SIGMA;\n    for (var p = 0; p < 6; p++) {\n        let plane = uniforms.frustumPlanes[p];\n        if (dot(plane.xyz, center) + plane.w <= -splatRadius) {\n            return false;\n        }\n    }\n\n    return true;\n}\n\n// One global atomicAdd per workgroup; intra-workgroup slots via a workgroup atomic (no subgroups).\nvar<workgroup> wgCount: atomic<u32>;\nvar<workgroup> wgBase: u32;\n\n@compute @workgroup_size({WORKGROUP_SIZE})\nfn main(\n    @builtin(global_invocation_id) gid: vec3u,\n    @builtin(num_workgroups) numWorkgroups: vec3u,\n    @builtin(local_invocation_index) localIdx: u32\n) {\n    if (localIdx == 0u) {\n        atomicStore(&wgCount, 0u);\n    }\n    workgroupBarrier();\n\n    // flat thread index from the (possibly Y-tiled) 2D dispatch grid\n    let threadIdx = gid.y * (numWorkgroups.x * {WORKGROUP_SIZE}u) + gid.x;\n    let candidateCount = candidateCountBuffer[uniforms.numIntervals];\n\n    var valid = false;\n    var splatId = 0u;\n    if (threadIdx < candidateCount) {\n        splatId = compactedSplatIds[threadIdx];\n        valid = fineCull(splatId);\n    }\n\n    var localSlot = 0u;\n    if (valid) {\n        localSlot = atomicAdd(&wgCount, 1u);\n    }\n    workgroupBarrier();\n\n    // workgroup leader reserves a contiguous global block for this workgroup's survivors\n    if (localIdx == 0u) {\n        wgBase = atomicAdd(&globalCount[0], atomicLoad(&wgCount));\n    }\n    workgroupBarrier();\n\n    if (valid) {\n        outputIndices[wgBase + localSlot] = splatId;\n    }\n}\n";
export default computeGsplatShadowCullSource;
