import { CompiledSystem } from '@pylinka/compiler'; import { PylinkaProject, System, ParamDef } from '@pylinka/graph'; import { b as CompiledStats, S as SystemClock, C as CompiledParticlesOptions, a as CompiledParticlesHandle } from '../emitter-DCgFspU4.js'; import { S as SpriteSource, A as AtlasAnim, M as MaskTable } from '../mask-DvK7Ulmr.js'; export { C as CompiledAtlasOptions } from '../mask-DvK7Ulmr.js'; import { K as KnobStore } from '../knobs-CVFP2Gxg.js'; import '../atlas-D83bp0cn.js'; /** * Which system a handle drives. * * Shared by all three backends and kept in its own module so the interpreted * WebGL path does not have to import the WebGPU one to ask the question. */ /** * Pick the system a handle drives (same rule as the interpreted backend). * * `systemId` wins over `systemName` and exists for RE-resolving on a live * edit. A handle used to remember only the name it bound to, so renaming an * emitter meant the next `apply` could not find it, fell through to "the first * enabled system", and silently started rendering a DIFFERENT effect — in the * editor, renaming a tab made its preview turn into a second copy of another * one. An id survives a rename; a name is what the person edits. * * A `systemName` that matches nothing still falls back rather than throwing — * a game asking for an effect that has been renamed should not go down — but * it says so once, because the alternative is playing the wrong effect in * silence. */ declare function pickSystem(project: PylinkaProject, systemName?: string, systemId?: string): System | undefined; /** * WebGPU compute backend (REQUIREMENTS.md §13). The graph is compiled to the * §13.5/§13.6 WGSL kernels and dispatched per frame — emit → update → draw in * one command encoder (§13.11), particle state in STORAGE|VERTEX buffers * (§13.2), inline literals + knobs in the vec4 value table (§13.3) so value * edits never recompile. Structural edits rebuild the compute pipelines and * reset the pool (§7.5). */ interface WebGPUSimOptions { /** render target format (canvas preferred format / pixi's target) */ format: GPUTextureFormat; /** sample count of the target pass (pixi antialias → 4); default 1 */ multisample?: number; sprite?: SpriteSource; /** atlas animation config (column advance + row pick); defaults to static */ anim?: AtlasAnim; /** emission-mask point table (overrides the analytic spawn shape) */ mask?: MaskTable; /** sub-emitter parent: this child spawns on the parent's particle deaths */ subParent?: { hot: GPUBuffer; meta: GPUBuffer; capacity: number; }; /** share a project-wide knob store (KnobBus fan-out); defaults to its own */ knobs?: KnobStore; seed?: number; startX?: number; startY?: number; onRecompile?: (info: { ms: number; reason: 'structural' | 'blend'; }) => void; } /** * Per-system WebGPU simulation + draw. Owns every §13.2 resource; reusable by * the standalone canvas runtime and the pixi render-pipe SimBackend. */ declare class WebGPUSystemSim { readonly stats: CompiledStats; readonly knobs: KnobStore; readonly clock: SystemClock; compiled: CompiledSystem; system: System; private readonly device; private readonly format; private readonly multisample; private readonly onRecompile; private params; private valueTable; private readonly hot; private readonly rnd; private readonly meta; private readonly counters; private readonly freeList; private readonly uBuf; private vBuf; private readonly rBuf; private readonly readback; private readonly maskBuf; private readonly sampler; private texture; private spriteCols; private spriteRows; private spriteFrameW; private spriteFrameH; private spriteAtlasW; private spriteAtlasH; private spritePad; private anim; private computeLayout; private computeBind; private emitPipe; private updatePipe; private renderLayout; private renderBind; private renderPipe; private readonly uStage; private readonly uStageU32; private readonly rStage; private rDirty; private readbackInflight; private destroyed; private readonly _capacity; /** parent-slot count a sub-emitter iterates (prevAlive + subEmit dispatch). * Equals _capacity except under a death-burst, where _capacity = parentCap × max. */ private readonly parentCap; private subPipe; private subBind; private prevAlive; get capacity(): number; /** Parent buffers a sub-emitter child binds (read-only). */ get hotBuffer(): GPUBuffer; get metaBuffer(): GPUBuffer; constructor(device: GPUDevice, system: System, params: ParamDef[], opts: WebGPUSimOptions); private uploadSprite; private buildComputePipelines; /** Build the sub-emitter emit path: a pool of parent-death-triggered spawns. * Bindings 0-6 (own sim) + 8/9 (parent hot/meta, read-only) + 10 (prevAlive). */ private buildSubEmit; private buildComputeBind; private buildRenderPipeline; private buildRenderBind; /** Re-init freeList/counters/flags (§13.2 init; §7.5 pool reset). */ resetPool(): void; /** §13.11 steps 1–4: schedule, stage, and flush the two uniform buffers. */ prepare(dt: number): void; /** §13.11 step 5: emit dispatch (if any) then update dispatch. */ encodeCompute(encoder: GPUCommandEncoder): void; /** Instanced §13.8 draw into an open render pass. */ draw(pass: GPURenderPassEncoder, sx: number, sy: number, ox: number, oy: number, sizeScale: number): void; /** §13.11 step 7: encode the async counters copy every 30 frames. */ maybeEncodeStats(encoder: GPUCommandEncoder): boolean; /** Kick the mapAsync after the encoder containing the stats copy was submitted. */ resolveStats(): void; endFrame(dt: number): void; /** * Apply an edited project. Value edits refresh the table (zero recompile); * a changed graph hash rebuilds compute pipelines + resets the pool; a blend * change rebuilds the render pipeline. Returns false when only a full * re-create can honor the edit (capacity change). */ applyProject(next: PylinkaProject, systemName?: string): boolean; destroy(): void; } /** * Standalone WebGPU runtime: one system per handle; handles on the same canvas * share a device + context (first clears, the rest composite) — the compiled * counterpart of `@pylinka/core/webgl`'s createParticles. */ declare function createParticles(canvas: HTMLCanvasElement, project: PylinkaProject, opts?: CompiledParticlesOptions): Promise; /** * WebGPU render pipeline WGSL (REQUIREMENTS.md §13.8). One instanced draw per * system: 6 verts from a const corner array, instance data pulled straight * from the sim's hot/rnd/meta buffers (they are created STORAGE | VERTEX). * R.atlas = (cols, rows, sizeScale, unused) — .z carries the preview * size-scale option in the slot the spec leaves unused. */ declare const RENDER_WGSL = "\nstruct RenderUniforms {\n scaleOffset: vec4f, // sx, sy, ox, oy\n grid: vec4f, // cols, rows, sizeScale, pad(px)\n anim: vec4f, // fps, play(0 stretch / 1 loop / 2 hold), pick(0 per-particle / 1 fixed), fixedRow\n frame: vec4f, // frameW, frameH, atlasW, atlasH (px)\n}\n@group(0) @binding(0) var R: RenderUniforms;\n@group(0) @binding(1) var samp: sampler;\n@group(0) @binding(2) var tex: texture_2d;\n\nconst CORNERS = array(\n vec2f(-0.5, -0.5), vec2f(0.5, -0.5), vec2f(-0.5, 0.5),\n vec2f(-0.5, 0.5), vec2f(0.5, -0.5), vec2f(0.5, 0.5));\n\nstruct VSOut {\n @builtin(position) clip: vec4f,\n @location(0) uv: vec2f,\n @location(1) tint: vec4f,\n}\n\n@vertex\nfn vs(@builtin(vertex_index) vi: u32,\n @location(0) pos: vec2f, @location(1) color: vec4f,\n @location(2) size: f32, @location(3) rot: f32, @location(4) flags: u32,\n @location(5) age: f32, @location(6) life: f32, @location(7) seed: u32) -> VSOut {\n let corner = CORNERS[vi];\n let s = size * f32(flags & 1u) * R.grid.z;\n let c = cos(rot); let sn = sin(rot);\n let local = vec2f(corner.x * c - corner.y * sn, corner.x * sn + corner.y * c) * s;\n let world = pos + local;\n\n // atlas cell: column advances with life, row is per-particle (or fixed).\n let cols = max(R.grid.x, 1.0);\n let rows = max(R.grid.y, 1.0);\n let tN = clamp(age / max(life, 1e-4), 0.0, 1.0);\n let seedN = f32(seed & 0xffffu) / 65536.0;\n let row = clamp(select(floor(seedN * rows), R.anim.w, R.anim.z > 0.5), 0.0, rows - 1.0);\n let col = select(select(clamp(floor(tN * cols), 0.0, cols - 1.0),\n floor(age * R.anim.x) % cols, R.anim.y > 0.5),\n clamp(floor(age * R.anim.x), 0.0, cols - 1.0), R.anim.y > 1.5);\n let cellPx = vec2f(col, row) * (R.frame.xy + R.grid.w);\n\n var o: VSOut;\n o.clip = vec4f(world * R.scaleOffset.xy + R.scaleOffset.zw, 0.0, 1.0);\n o.uv = (cellPx + (corner + 0.5) * R.frame.xy) / R.frame.zw;\n o.tint = color;\n return o;\n}\n\n@fragment\nfn fs(in: VSOut) -> @location(0) vec4f {\n let t = textureSample(tex, samp, in.uv);\n return vec4f(t.rgb * in.tint.rgb * in.tint.a, t.a * in.tint.a);\n}\n"; /** §13.1 blend states, premultiplied output assumed. */ declare function blendState(mode: 'normal' | 'add' | 'screen'): GPUBlendState; export { CompiledParticlesHandle, CompiledParticlesOptions, CompiledStats, RENDER_WGSL, type WebGPUSimOptions, WebGPUSystemSim, blendState, createParticles, pickSystem };