import { Sprite2DMaterial } from "../materials/Sprite2DMaterial.js"; import { SpriteSpatialGrid } from "./SpriteSpatialGrid.js"; import { Sprite2D } from "../sprites/Sprite2D.js"; import { InstancedBufferAttribute, InstancedMesh, InterleavedBufferAttribute, Intersection, Matrix4, Raycaster } from "three"; //#region src/pipeline/SpriteBatch.d.ts /** * Stride (in floats) of the interleaved per-instance core buffer. Layout * matches four vec4 logical slots backed by one underlying buffer: * * offset 0..3 instanceUV (uv.x, uv.y, uv.w, uv.h) * offset 4..7 instanceColor (r, g, b, a) * offset 8..11 instanceSystem (flipX, flipY, sysFlags, enableBits) * offset 12..15 instanceExtras (shadowRadius, reserved×3) * * Packing all four into one `InstancedInterleavedBuffer` collapses what * was previously 3 vertex-buffer bindings (instanceUV / instanceColor / * instanceFlip) into 1, freeing 2 slots under WebGPU's * `maxVertexBuffers = 8` cap for `effectBuf*` growth. */ declare const INSTANCE_STRIDE = 16; /** * A batch of sprites rendered with a single draw call. * * Uses InstancedMesh with: * - `instanceMatrix` — auto-managed by InstancedMesh (1 buffer slot) * - Interleaved core buffer carrying UV / color / system / extras * (1 buffer slot, 4 logical attribute views) * - `effectBuf*` custom attributes from the material's effect schema * * Total vertex-buffer bindings: 0 (synth-quad `position`/`uv` exist for * user TSL but the built-in shader synthesizes from `vertexIndex` * instead, so neither is consumed) + 1 (instanceMatrix) + 1 * (interleaved) + N (effect buffers). N is capped by * `EffectMaterial.MAX_EFFECT_FLOATS / 4 = 6` so the total never exceeds * the WebGPU 8-binding limit. * * Systems write to batch buffers directly via the write methods. * * @internal */ declare class SpriteBatch extends InstancedMesh { /** * Type marker for graph-management code (sceneGraphSyncSystem's prune) * that must distinguish batch meshes from other SpriteGroup children * without a value import of this class. */ readonly isSpriteBatch = true; /** * The material used by all sprites in this batch. */ readonly spriteMaterial: Sprite2DMaterial; /** * Maximum number of sprites this batch can hold. */ readonly maxSize: number; /** * Geometry strategy this batch was built with. Pool recycling must * match it — a synth-quad mesh can't serve a tight-mesh material * (different attribute layouts compiled into the shader). */ readonly geometryKind: 'synth-quad' | 'tight-mesh'; /** * Atlas registry `version` the envelope hull was built from (-1 for * synth-quad, which has no envelope). A merge/degrade on the same * texture bumps the registry's version without necessarily flipping * `geometryKind` — pool recycling in `findOrCreateBatch` compares * this against the live atlas version so a batch whose hull no * longer matches its registration gets rebuilt instead of reused. */ readonly envelopeVersion: number; /** * Picking broadphase: a uniform hash grid of this batch's member * sprites keyed by world position. Maintained by the batch lifecycle * systems (assign/reassign/remove insert + remove entries) and by * `transformSyncSystem` (moves). Queried by {@link SpriteBatch.raycast}. */ readonly grid: SpriteSpatialGrid; /** * Current number of active slots in the batch. */ private _activeCount; /** * Free slot indices for reuse (pooling). */ private _freeList; /** * Next index to allocate when freeList is empty. */ private _nextIndex; /** * Interleaved core buffer (UV + color + system + extras). */ private _interleavedData; private _interleavedBuffer; /** * Attribute views into the interleaved buffer. Each is a separate * vertex-attribute binding from the shader's perspective, but they * all share the same underlying GPU buffer. */ private _uvAttribute; private _colorAttribute; private _systemAttribute; private _extrasAttribute; /** * Custom attribute buffers (from material schema — effect data). */ private _customAttributes; /** * Whether transforms need to be re-read from sprites during upload. */ private _transformsDirty; /** * Per-batch sort-dirty flag. Set by `Sprite2D.zIndex` setter when a * member sprite's zIndex changes (non-gated materials only) and by * `batchAssignSystem` when a new sprite is added. Consumed by * `batchSortSystem` which re-sorts the batch and clears the flag. */ private _sortDirty; /** * Per-buffer dirty trackers. One for the matrix buffer, one for the * interleaved core (covers all 4 logical attributes since they share * the underlying buffer), and one per custom effect buffer. */ private _matrixTracker; private _interleavedTracker; constructor(material: Sprite2DMaterial, maxSize?: number); writeColor(index: number, r: number, g: number, b: number, a: number): void; writeUV(index: number, x: number, y: number, w: number, h: number): void; writeFlip(index: number, flipX: number, flipY: number): void; /** * Write system-level flag bits (e.g., castsShadow, isLit). Stored in * `instanceSystem.z`. Reserved here for lighting integration; sprite- * sort PR leaves it at zero. */ writeSystemFlags(index: number, flags: number): void; /** * Write the MaterialEffect enable-bits bitmask. Stored in * `instanceSystem.w`. The shader reads this to gate per-effect color * contribution in the effect chain. */ writeEnableBits(index: number, bits: number): void; /** * Write the per-instance shadow-occluder radius (world units). * Stored in `instanceExtras.x`. Lighting-only; zero for sprite-sort PR. */ writeShadowRadius(index: number, radius: number): void; writeMatrix(index: number, matrix: Matrix4): void; /** * Expand the matrix dirty range for a slot. * Used by transformSyncSystem which writes the instanceMatrix buffer directly. */ markMatrixDirty(slot: number): void; writeCustom(index: number, name: string, value: number | number[]): void; getCustomBuffer(name: string): { buffer: Float32Array; size: number; } | undefined; getColorAttribute(): InterleavedBufferAttribute; getUVAttribute(): InterleavedBufferAttribute; getSystemAttribute(): InterleavedBufferAttribute; getExtrasAttribute(): InterleavedBufferAttribute; getCustomAttribute(name: string): InstancedBufferAttribute | undefined; writeEffectSlot(index: number, bufferIndex: number, component: number, value: number): void; /** * Swap all per-instance attribute rows between physical slots `a` and `b`. * Used by batchSortSystem to re-order instances by zIndex without * rewriting ECS state — all buffers (matrix, interleaved core, custom * effect buffers) are permuted in lockstep. * * Zero-alloc: uses element-wise writes on typed arrays in place. */ swapSlots(a: number, b: number): void; get activeCount(): number; get isFull(): boolean; get isEmpty(): boolean; allocateSlot(): number; /** * Free a slot. Collapses the instance matrix to zero scale — a * degenerate quad rasterizes no fragments at all, unlike the previous * alpha=0 approach where every freed slot still paid full-quad * rasterization + a per-fragment discard. Alpha is zeroed too as * belt-and-braces (any path that resurrects the matrix before * reassignment still draws nothing). */ freeSlot(index: number): void; /** * Reset all slots without disposing GPU resources. * Used when recycling a batch from the pool. */ resetSlots(): void; /** * Mark transforms as needing update. */ invalidateTransforms(): void; /** * Mark this batch as needing a zIndex re-sort. */ markSortDirty(): void; /** * Read-and-clear the sort-dirty flag. */ consumeSortDirty(): boolean; /** * Sync the instance count to include all allocated slots. * Free slots have alpha=0 so they're invisible. */ syncCount(): void; /** * The batch is never frustum-culled — an infinite bound is the * honest answer at zero cost (InstancedMesh's default would union * all instance spheres). */ computeBoundingSphere(): void; /** * Index `sprite` into the picking broadphase from its local matrix. The * batch systems call this at slot assign/reassign; the group-folded WORLD * position lands later the same schedule run via `transformSyncSystem`'s * `grid.update`. When transform sync is disabled the instance matrix IS * this local affine, so the grid and the rendered position agree either way. */ indexForPicking(sprite: Sprite2D): void; /** * Batch-root broadphase picking. Scene traversal reaches the batch * (member sprites are not graph children), so the batch localizes the * ray and queries its spatial grid for candidate sprites. Each candidate * delegates to `Sprite2D.raycast`, which owns ALL narrow-phase * correctness (on-demand world-matrix compose, hitTestMode * bounds/alpha/radius, near/far) and pushes intersections with * `object === sprite`. three's Raycaster distance-sorts afterward, so * higher-zIndex sprites (closer along +z) surface first — no sorting * here. * * The grid is indexed by world XY, but the ray's XY depends on the depth * at which it is sampled: under an orthographic camera the ray is * z-parallel (XY constant), but under perspective it converges, so a * single z=0 sample would query the wrong cell for a sprite at non-zero * world Z. Localize by sweeping the ray across the grid's [zMin, zMax] * span — the XY endpoints at where the ray ENTERS and EXITS the span, * clamped to the forward (t ≥ 0) half. That collapses to one cell (the * fast path) when the batch is coplanar or the camera is orthographic, * and — unlike intersecting the two z-planes directly — stays correct * when the ray origin sits inside the span or a stale span reaches behind * the camera (those just clamp; they never abort the whole broadphase). */ raycast(raycaster: Raycaster, intersects: Intersection[]): void; /** * Flush per-buffer dirty state to GPU upload ranges. * * Each tracker decides per-buffer whether to emit a single full- * buffer upload (three's `bufferData` fast path) or one * `addUpdateRange` per dirty bucket, based on how many buckets * accumulated changes during the frame. */ /** * Whether any occluder-relevant attribute changed since the last flush. * Reads the matrix tracker (transforms) and interleaved tracker * (frame/castsShadow/alpha/add-remove) — together these capture every * change that alters an occluder silhouette. Must be read BEFORE * `flushDirtyRanges`, which clears the trackers. */ get isDirty(): boolean; flushDirtyRanges(): void; /** * Clone for devtools/serialization compatibility. */ clone(_recursive?: boolean): this; /** * Dispose of resources. */ dispose(): this; } //#endregion export { INSTANCE_STRIDE, SpriteBatch }; //# sourceMappingURL=SpriteBatch.d.ts.map