/**
 * Per-light shadow draw entry. One cheap material + mesh instance over the shared quad mesh, plus a
 * visible-index buffer and an atomic visible-count buffer. The mesh instance is registered as a
 * shadow caster and is visible only for its light's shadow camera.
 */
export type ShadowLightEntry = object;
/**
 * Per-light shadow draw entry. One cheap material + mesh instance over the shared quad mesh, plus a
 * visible-index buffer and an atomic visible-count buffer. The mesh instance is registered as a
 * shadow caster and is visible only for its light's shadow camera.
 *
 * @typedef {object} ShadowLightEntry
 * @ignore
 * @property {Light} light - The directional light this entry casts for.
 * @property {ShaderMaterial} material - The per-light shadow draw material.
 * @property {MeshInstance} meshInstance - The cast mesh instance (registered as a shadow caster).
 * @property {StorageBuffer|null} indexBuffer - Dense visible work-buffer index list (grows with splat count).
 * @property {number} allocatedIndexCount - Capacity of `indexBuffer` in splats.
 * @property {StorageBuffer} countBuffer - Single-element atomic visible counter (also bound as `numSplatsStorage`).
 * @property {Compute|null} cullCompute - Per-entry cull compute (own uniform buffer/bind group),
 * lazily (re)created against the current cull shader; see {@link ShadowLightEntry.cullComputeGen}.
 * @property {number} cullComputeGen - Cull-shader generation `cullCompute` was built for; when it no
 * longer matches the renderer's {@link GSplatShadowRenderer#_cullShaderGen} the compute is rebuilt.
 * @property {Compute} argsCompute - Per-entry indirect-args compute (own uniform buffer/bind group).
 */
/**
 * Casts gsplat directional shadows on behalf of the GPU-sort ({@link GSplatHybridRenderer}) path,
 * which cannot self-cast. It shares the manager's {@link GSplatWorld} (work buffer + camera-
 * independent cull bounds + world states) and never allocates a world of its own.
 *
 * For each non-cascaded directional light affecting the manager's layer it maintains a cheap draw
 * entry (a per-light material + mesh instance over one shared quad mesh, plus a visible-index and
 * count buffer). A projection-free compute cull against the light's frustum produces the visible
 * index list, and a quad-style per-vertex-projected indirect draw writes the shadow map via the
 * standard caster pipeline. No sort and no projection cache are needed.
 *
 * Lifecycle is split across the frame:
 * - {@link syncLights} runs pre-cull (from {@link GSplatManager#update}) to reconcile the per-light
 *   pool and register/unregister shadow casters, so `cullComposition` sees them.
 * - {@link cull} runs post-cull (from {@link GSplatManager#updateShadows} via the director) once
 *   each light's shadow-camera frustum has been fitted, to dispatch the culls and bind results.
 *
 * @ignore
 */
export class GSplatShadowRenderer {
    /**
     * @param {GraphicsDevice} device - The graphics device.
     * @param {GraphNode} node - The graph node the cast mesh instances are parented to.
     * @param {GraphNode} cameraNode - The main camera node this manager renders for; used to
     * resolve each light's shadow camera via `light.getRenderData(sceneCamera, 0)`.
     * @param {Layer} layer - The layer to register shadow casters on (and read directional lights from).
     * @param {GSplatWorld} world - The shared world (work buffer, cull bounds, world states).
     * @param {import('./gsplat-hybrid-renderer-scratch.js').GSplatHybridRendererScratch|null} [scratch] -
     * Manager-owned shared scratch; forwarded to the pass-1 compaction so its candidate index list is
     * shared with the forward hybrid renderer (they use it at disjoint points in the frame).
     */
    constructor(device: GraphicsDevice, node: GraphNode, cameraNode: GraphNode, layer: Layer, world: GSplatWorld, scratch?: import("./gsplat-hybrid-renderer-scratch.js").GSplatHybridRendererScratch | null);
    /** @type {GraphicsDevice} */
    device: GraphicsDevice;
    /** @type {GraphNode} */
    node: GraphNode;
    /** @type {GraphNode} */
    cameraNode: GraphNode;
    /** @type {Layer} */
    layer: Layer;
    /** @type {GSplatWorld} */
    world: GSplatWorld;
    /**
     * Per-light draw entries, keyed by light.
     *
     * @type {Map<Light, ShadowLightEntry>}
     */
    entries: Map<Light, ShadowLightEntry>;
    /**
     * Reused scratch set of the qualifying directional shadow lights, rebuilt each {@link syncLights}
     * to diff against {@link entries}.
     *
     * @type {Set<Light>}
     * @private
     */
    private _desiredLights;
    /**
     * Pass 1 (coarse): interval compaction run with each light's frustum, producing a dense candidate
     * list (`compactedSplatIds`) + candidate count (`countBuffer[numIntervals]`). Reused across all
     * lights — one shared scratch, since lights are culled sequentially (light A's pass 2 consumes the
     * candidate list before light B's pass 1 overwrites it). The expensive per-splat fine cull (pass
     * 2) then runs flat over the candidate list, so occupancy is independent of interval count.
     *
     * @type {GSplatIntervalCompaction|null}
     */
    _compaction: GSplatIntervalCompaction | null;
    /** @type {Vec2} */
    _cullDispatchSize: Vec2;
    /**
     * Reused light frustum planes (6 × vec4(normal, distance)) for the cull uniform, refilled per
     * light entry from its shadow camera.
     *
     * @type {Float32Array}
     */
    _frustumPlanes: Float32Array;
    /**
     * Change-detection key for the scene material's shader chunks; when it changes, the user
     * `gsplatModifyVS` chunk is re-applied to the per-light shadow materials.
     *
     * @type {string}
     * @private
     */
    private _userChunksKey;
    /**
     * The scene material's user `gsplatModifyVS` WGSL chunk source (or null for the default no-op).
     * The shadow path is WebGPU-only, so only the WGSL variant is tracked.
     *
     * @type {string|null}
     * @private
     */
    private _userModifyWgsl;
    /** @type {Shader|null} */
    _cullShader: Shader | null;
    /** @type {BindGroupFormat|null} */
    _cullBindGroupFormat: BindGroupFormat | null;
    /**
     * Work-buffer format version the cull shader was last built for. The cull shader reads the work
     * buffer (texture bindings + read code derived from the format), so a format change rebuilds it.
     *
     * @type {number}
     * @private
     */
    private _cullFormatVersion;
    /**
     * The scene material's shader-chunks key the cull shader was last built for. A change means the
     * user `gsplatModifyVS` chunk changed, so the cull shader is rebuilt to match the shadow draw.
     *
     * @type {string|null}
     * @private
     */
    private _cullBuiltChunksKey;
    /**
     * Monotonic cull-shader generation, bumped on every (re)build. Per-light cull Computes reference
     * the shared shader, so they are recreated when this changes (see {@link _cullEntry}).
     *
     * @type {number}
     * @private
     */
    private _cullShaderGen;
    /** @type {Shader|null} */
    _argsShader: Shader | null;
    /** @type {BindGroupFormat|null} */
    _argsBindGroupFormat: BindGroupFormat | null;
    destroy(): void;
    /**
     * (Re)builds the shared cull shader when the work-buffer format or the user `gsplatModifyVS`
     * chunk changes, bumping {@link _cullShaderGen} so per-light Computes are recreated. Must run
     * after {@link _syncUserModify} (which refreshes the tracked modify chunk) and once the work
     * buffer is ready.
     *
     * @private
     */
    private _ensureCullShader;
    /**
     * Builds the pass-2 fine-cull shader + bind group format against the current work-buffer format
     * and the tracked user modify chunk. The fixed bindings (0..4) are followed by the work-buffer
     * format texture bindings; the shader reads each candidate splat (center/opacity/rotation/scale),
     * applies the render-stage modifier, and runs the opacity/size/frustum fine tests.
     *
     * @private
     */
    private _buildCullShader;
    /** @private */
    private _createArgsShader;
    /**
     * Rebinds to a new work buffer after a format/resize swap. The per-light materials read the
     * work-buffer textures, so they must be re-pointed when the manager recreates it.
     *
     * @param {GSplatWorkBuffer} workBuffer - The new work buffer.
     */
    setDataSource(workBuffer: GSplatWorkBuffer): void;
    /**
     * Sets the world-space AABB on every cast mesh instance. The directional shadow cull derives
     * each cascade's depth range from the casters' AABBs (which world-space PCSS penumbra scaling
     * depends on), and the shared quad mesh has no meaningful spatial bounds of its own — so without
     * this the depth range is wrong and soft shadows are mis-scaled. Must run pre-cull (the manager
     * calls it before cullComposition fits the shadow cameras). `setCustomAabb` copies, so passing a
     * shared box instance to every entry is safe.
     *
     * @param {import('../../core/shape/bounding-box.js').BoundingBox|null} aabb - World-space splat AABB.
     */
    setCastersAabb(aabb: import("../../core/shape/bounding-box.js").BoundingBox | null): void;
    /**
     * Pre-cull pass: reconcile the per-light caster pool against the layer's directional shadow
     * lights (enabled, shadow-casting, non-cascaded). Adds entries for new lights and tears down
     * entries for lights that were disabled, removed, stopped casting, or became cascaded — freeing
     * their GPU resources and unregistering their caster. Cascaded directional lights are skipped
     * (warned once); they would need a per-cascade cull.
     */
    syncLights(): void;
    /**
     * Post-cull pass: for each light entry run the two-pass cull (coarse candidate compaction with
     * the light frustum, then a flat per-splat fine cull) and the indirect-args write, then bind the
     * results to the entry's mesh instance. Runs after `cullComposition` and before the frame graph
     * renders the shadow maps.
     *
     * @param {GSplatParams} gsplatParams - Scene gsplat params (alphaClip etc.).
     */
    cull(gsplatParams: GSplatParams): void;
    /**
     * Applies the scene material's user `gsplatModifyVS` chunk to every per-light shadow material
     * (recompiling only when the chunk changes) and forwards the scene material's parameters (e.g.
     * `uTime`) to them each frame. This keeps cast shadows in sync with any forward-pass vertex
     * animation, since the shadow draw uses the same quad VS + modify hooks.
     *
     * @param {GSplatParams} gsplatParams - Scene gsplat params (carries the template material).
     * @private
     */
    private _syncUserModify;
    /**
     * Sets (or clears) the tracked user `gsplatModifyVS` chunk on one entry's material and rebuilds
     * its shader. Called when the chunk changes and when a new entry is created.
     *
     * @param {ShadowLightEntry} entry - The light entry.
     * @private
     */
    private _applyUserModify;
    /**
     * Dispatches the cull + indirect-args for one light entry and binds the results.
     *
     * @param {ShadowLightEntry} entry - The light entry.
     * @param {number} numIntervals - Total interval count.
     * @param {number} totalActiveSplats - Max output index count.
     * @param {number} textureSize - Work buffer texture size.
     * @param {GSplatParams} gsplatParams - Scene gsplat params.
     * @private
     */
    private _cullEntry;
    /**
     * Fills {@link _frustumPlanes} from a frustum: 6 planes packed as vec4(normal.xyz, distance).
     *
     * @param {import('../../core/shape/frustum.js').Frustum} frustum - The light's shadow-camera frustum.
     * @private
     */
    private _fillFrustumPlanes;
    /**
     * Creates a per-light shadow draw entry (material + caster mesh instance + count buffer).
     *
     * @param {Light} light - The directional light.
     * @returns {ShadowLightEntry} The created entry.
     * @private
     */
    private _createEntry;
    /**
     * Tears down a light entry: unregisters the caster and frees its GPU resources.
     *
     * @param {ShadowLightEntry} entry - The entry to destroy.
     * @private
     */
    private _destroyEntry;
    /**
     * Creates the quad-style shadow draw material. Uses the same gsplat vertex/fragment chunks as
     * the forward quad renderer (direct per-vertex projection from the bound view/projection — the
     * shadow camera's, supplied by the engine's shadow pass), trimmed to depth + alpha-clip (the
     * engine injects `SHADOW_PASS` when compiling the shadow variant). Indirect-draw mode reads the
     * visible index list and GPU count.
     *
     * @returns {ShaderMaterial} The configured material.
     * @private
     */
    private _createMaterial;
    /**
     * Injects the work-buffer format shader chunks and binds its textures + format-dependent defines
     * to a material. Mirrors the relevant parts of {@link GSplatQuadRenderer}.
     *
     * @param {ShaderMaterial} material - The material to configure.
     * @private
     */
    private _configureMaterialWorkBuffer;
    /**
     * Creates the cast mesh instance for a light, visible only for that light's shadow camera.
     *
     * @param {Light} light - The directional light.
     * @param {ShaderMaterial} material - The entry's material.
     * @returns {MeshInstance} The mesh instance.
     * @private
     */
    private _createMeshInstance;
}
import type { GraphicsDevice } from '../../platform/graphics/graphics-device.js';
import type { GraphNode } from '../graph-node.js';
import type { Layer } from '../layer.js';
import type { GSplatWorld } from './gsplat-world.js';
import type { Light } from '../light.js';
import { GSplatIntervalCompaction } from './gsplat-interval-compaction.js';
import { Vec2 } from '../../core/math/vec2.js';
import { Shader } from '../../platform/graphics/shader.js';
import { BindGroupFormat } from '../../platform/graphics/bind-group-format.js';
import type { GSplatWorkBuffer } from './gsplat-work-buffer.js';
import type { GSplatParams } from './gsplat-params.js';
