/**
 * Shared GPU buffers and compute passes for many VirtualMeshes.
 *
 * Every mesh's vertices, meshlets, level table and instances are appended to one set of storage buffers, and one
 * fixed set of compute passes selects the cut for all of them. CPU cost, shader count and pipeline compilation
 * therefore do not grow with the number of unique meshes: a level with hundreds of different props costs the
 * same 12 dispatches per frame as a scene with one. Each mesh still gets its own indexed indirect draw (its own
 * material), into a region of the pool's shared index buffer laid out each frame by a prefix sum.
 *
 * Per frame (all on the GPU):
 *  0. reset    - clears the counters.
 *  1. cells    - one thread per cell (CELL_SIZE instances of one mesh, Morton-ordered): frustum, sub-pixel and
 *                draw-distance tests on the cell's bounding sphere. Visible cells are appended to a list.
 *  2. instance - one workgroup per visible cell: per-instance tests, occlusion, then the LOD level range per
 *                cut (camera and shadow). Instances whose few meshlets are all certain to pass emit them
 *                directly (fast path); the others go to a work list.
 *  3. meshlet  - one workgroup per work item: per-meshlet frustum/occlusion culling and the LOD test
 *                `parentError > t && error <= t`, widened to the blend band [t, t * k] when the threshold is high
 *                enough for level switches to show (see lodBlend.ts). Selected meshlets get a draw slot, their blend range and a range inside their
 *                mesh's region of the index buffer.
 *  4. prefix   - one thread: lays the meshes' regions out one after another and writes each mesh's draw args.
 *  5. expand   - one workgroup per drawn meshlet: writes 3 indices per triangle. An index encodes
 *                `drawSlot * 128 + meshletLocalVertex`; the material's positionNode decodes it and pulls the
 *                vertex from storage buffers, so any three.js NodeMaterial works unchanged.
 *
 * A pool is bounded by WebGPU's storage-buffer binding size; VirtualGeometry opens another pool when one is full.
 * Buffers grow by reallocation (the nodes are rebuilt, and the pipelines recompiled in the background).
 *
 * Based on nanite-webgpu (MIT, Marcin Matuszczyk): cullMeshletsPass + rasterizeHwPass.
 */
import * as THREE from 'three/webgpu';
import type { VirtualMeshData } from '../preprocess/buildVirtualMesh.js';
import type { VirtualGeometry } from './VirtualGeometry.js';
type Node = any;
/** World-space normal of the current vertex, usable in material color/roughness nodes. */
export declare const vgWorldNormal: THREE.PropertyNode<"vec3">;
export declare const vgMeshletVarying: THREE.PropertyNode<"float">;
export declare const vgLodVarying: THREE.PropertyNode<"float">;
export declare const vgInstanceVarying: THREE.PropertyNode<"float">;
export declare const vgTintVarying: THREE.PropertyNode<"vec3">;
/** World position of the current vertex's instance origin (vertex stage): e.g. to make whole trees sway from their base. */
export declare const vgInstanceOrigin: THREE.PropertyNode<"vec3">;
/** Bits of an index used for the meshlet-local vertex; the rest is the draw slot. */
export declare const LOCAL_VERTEX_BITS: number;
/** Instances per cell. A cell is a contiguous range of one mesh's Morton-sorted instances. */
export declare const CELL_SIZE = 128;
/** Fraction of `maxDrawDistance` over which instances shrink to nothing before they are culled. */
export declare const DRAW_DISTANCE_FADE = 0.15;
/** Stand-in for an infinite draw distance on the GPU. */
export declare const NO_DRAW_DISTANCE = 1e+30;
export interface PoolCapacity {
    /** Max meshlets drawn per frame, per cut. */
    meshlets: number;
    /** Max triangles drawn per frame by the camera cut (its index buffer). */
    triangles: number;
    /** Max triangles drawn per frame into shadow maps. */
    shadowTriangles: number;
}
/** CPU-side record of one mesh's place in the pool. */
export interface PoolEntry {
    slot: number;
    data: VirtualMeshData;
    vertexBase: number;
    meshletBase: number;
    meshletVertexBase: number;
    triangleBase: number;
    levelBase: number;
    levelCount: number;
    instanceBase: number;
    instanceCount: number;
    cellBase: number;
    cellCount: number;
    uvScale: [number, number];
    uvOffset: [number, number];
}
/** Growable typed array with a GPU attribute; growth replaces the attribute (the pool then rebuilds its nodes). */
declare class GrowBuffer<T extends Float32Array | Uint32Array> {
    private readonly make;
    readonly itemSize: number;
    /** Largest capacity (items) whose binding stays under the pool's byte limit. */
    private readonly maxItems;
    array: T;
    attribute: THREE.StorageBufferAttribute;
    used: number;
    constructor(make: (n: number) => T, itemSize: number, capacity: number, 
    /** Largest capacity (items) whose binding stays under the pool's byte limit. */
    maxItems?: number);
    get capacity(): number;
    /** Reserves `count` items; returns the first, or -1 when the buffer must grow first. */
    reserve(count: number): number;
    /** Reallocates to hold at least `needed` items (keeps the contents). */
    grow(needed: number): void;
    /** Marks items [first, first + count) for upload. */
    touch(first: number, count: number): void;
}
export declare class GeometryPool {
    private readonly context;
    readonly entries: (PoolEntry | null)[];
    /** Highest used mesh slot + 1 (the prefix pass loops over this many). */
    readonly meshSlots: THREE.UniformNode<"uint", number>;
    readonly cellCount: THREE.UniformNode<"uint", number>;
    readonly maxBytes: number;
    readonly capacity: PoolCapacity;
    readonly vertexData: GrowBuffer<Uint32Array>;
    readonly vertexAttrs: GrowBuffer<Uint32Array>;
    readonly meshletBounds: GrowBuffer<Float32Array>;
    readonly meshletInfo: GrowBuffer<Uint32Array>;
    readonly meshletVertices: GrowBuffer<Uint32Array>;
    readonly meshletTriangles: GrowBuffer<Uint32Array>;
    readonly meshTable: GrowBuffer<Float32Array>;
    readonly instances: GrowBuffer<Float32Array>;
    readonly cells: GrowBuffer<Float32Array>;
    private meshSlotCapacity;
    private levelUsed;
    private countersAttribute;
    /** First word of the capacity controller's state in the counters buffer (see S_SCALE). */
    private lodStateBase;
    /** From the last readback: the GPU controller is still moving its scale, so the cut must be redone every frame. */
    controllerActive: boolean;
    drawArgsAttribute: THREE.IndirectStorageBufferAttribute;
    cameraIndex: THREE.StorageBufferAttribute;
    shadowIndex: THREE.StorageBufferAttribute;
    computeNodes: THREE.ComputeNode[];
    positionNode: Node;
    /** Bumped on every rebuild: meshes re-point their geometry and material when it changes. */
    version: number;
    private needsRebuild;
    private indexBuffersReady;
    pipelineState: 'none' | 'compiling' | 'ready';
    /** The cut needs recomputing (camera moved, instances or settings changed). */
    dirty: boolean;
    /** True once a cut was computed (its camera index buffer can then serve as occluders). */
    hasCut: boolean;
    /** Holds one mesh too large for a shared pool, sized for it (no other mesh joins). */
    dedicated: boolean;
    /** Set when the device cannot bind this pool's buffers: it is skipped entirely and its meshes are hidden. */
    disabled: boolean;
    constructor(context: VirtualGeometry, capacity: PoolCapacity, maxBytes: number);
    /** Bytes the geometry of `data` takes in this pool (the largest single binding is what must fit). */
    static bytesOf(data: VirtualMeshData): number;
    /** True when `data` (and `instanceCount` instances) still fit under the binding-size limit. */
    fits(data: VirtualMeshData, instanceCount: number): boolean;
    /** Appends a mesh's geometry and instances. Returns its entry (`slot` is its index in the mesh table). */
    add(data: VirtualMeshData, matrices: Float32Array, colors: Float32Array | undefined, gpuToInput: Uint32Array): PoolEntry;
    /** Frees a mesh's slot (its buffer ranges are reclaimed when the pool is next repacked). */
    remove(entry: PoolEntry): void;
    private growMeshSlots;
    private writeGeometry;
    /** Writes a mesh record: bounding spheres, levers, level table location, uv packing, flags. */
    writeMeshRecord(e: PoolEntry, o: {
        maxDrawDistance: number;
        minPixelRadius: number;
        enabled: number;
        shadow: number;
        occlusion: number;
    }): void;
    /** Updates some of a mesh's per-frame levers (only what changed is uploaded). */
    writeFlags(e: PoolEntry, o: {
        maxDrawDistance?: number;
        minPixelRadius?: number;
        enabled?: number;
        shadow?: number;
        occlusion?: number;
    }): void;
    readFlag(e: PoolEntry, index: 'maxDrawDistance' | 'minPixelRadius' | 'enabled' | 'shadow' | 'occlusion'): number;
    private writeInstances;
    /** Writes one instance matrix (GPU order index within the mesh). Call `commitInstances` afterwards. */
    setMatrix(e: PoolEntry, gpuIndex: number, matrix: THREE.Matrix4): void;
    /** Uploads changed instances and refreshes the bounds of the given cells. */
    commitInstances(e: PoolEntry, cells: Iterable<number>): void;
    /**
     * Bounding sphere of one cell's instances: the AABB of their world-space object spheres gives the center, and
     * the radius reaches every instance sphere. Both radii are inflated a little so float32 rounding on the GPU can
     * never make the cell test stricter than the per-instance test.
     */
    private computeCell;
    /**
     * Index buffers are written by compute and read by draws. three.js picks GPU buffer usage from the first use,
     * so create them as index buffers (INDEX | STORAGE) before any compute. Also rebuilds the nodes after growth.
     */
    prepare(renderer: THREE.WebGPURenderer): void;
    /** Builds the nodes if the pool's buffers grew since the last build (or it was never built). */
    ensureBuilt(): void;
    /** Recreates the per-frame buffers and every node (after the pool's buffers grew). */
    build(): void;
    /** Stats of the last computed cut, from a readback of the counters buffer. */
    readCounters(renderer: THREE.WebGPURenderer): Promise<{
        camera: {
            selected: number;
            drawn: number;
            survived: number;
            requestedTriangles: number;
        };
        shadow: {
            selected: number;
            drawn: number;
            survived: number;
            requestedTriangles: number;
        };
        occluded: number;
        perMesh: (c: number, slot: number) => number;
        scale: number[];
        fill: number[];
    }>;
    /**
     * Byte offset of a mesh's draw args for a cut (0: camera, 1: shadow) and part (0: meshlets drawn over every pixel,
     * 1: meshlets inside an LOD blend band, drawn with the blend mask) in `drawArgsAttribute`.
     */
    drawArgsOffset(cut: number, slot: number, part?: number): number;
    dispose(): void;
}
/**
 * World-space bounding sphere of the instance whose column-major matrix starts at `base`, computed the same way
 * the instance pass does: center = model * sphere center, radius = sphere radius * max column length.
 */
export declare function instanceSphere(m: ArrayLike<number>, base: number, sphere: number[], out: Float64Array): void;
export declare function encodeOctNormal(nx: number, ny: number, nz: number): number;
/** xyz as float bits + octahedral-encoded normal, 4 x u32 per vertex. */
export declare function packVertices(positions: Float32Array, normals: Float32Array): Uint32Array;
/**
 * uv per vertex as two unorm16 in one u32, quantized over the bounds of all UVs (also tiling UVs outside 0..1).
 * Decode: unpackUnorm2x16(packed) * scale + offset. One step is 1/65535 of the range: well below a texel of a 4K
 * texture for 0..1 UVs.
 */
export declare function packUvs(uvs: Float32Array): {
    packed: Uint32Array;
    scale: [number, number];
    offset: [number, number];
};
/**
 * Instance indices sorted along a 30-bit Morton curve over the instance centers, so that consecutive slots are
 * spatially close and a fixed-size run of slots forms a compact cell.
 */
export declare function mortonOrder(matrices: Float32Array, sphere: number[]): Uint32Array;
export {};
