@godot-scene-web/canvas-effects 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,335 @@
1
+ import { WgslBuiltinOffsets, WgslUniformField } from "@godot-scene-web/effects/shaders";
2
+ import { InstanceBuffer } from "@godot-scene-web/effects";
3
+
4
+ //#region src/webgpu-pipeline.d.ts
5
+ /** Normative WebGPU flags, stated locally so this module needs no browser host. */
6
+ declare const BUFFER_USAGE: {
7
+ readonly COPY_DST: 8;
8
+ readonly UNIFORM: 64;
9
+ };
10
+ declare const SHADER_STAGE: {
11
+ readonly VERTEX: 1;
12
+ readonly FRAGMENT: 2;
13
+ };
14
+ /**
15
+ * Bytes per per-cell uniform slot. 256 is the maximum `minUniformBufferOffsetAlignment` any WebGPU
16
+ * implementation may report, so a buffer laid out at this pitch is bindable with a dynamic offset
17
+ * everywhere; the real limit is read off the device and only ever rounds DOWN from here.
18
+ */
19
+ declare const UNIFORM_SLOT_BYTES = 256;
20
+ /** The first error message from the most recent failed compile/validation (with WGSL line/col when
21
+ * the failure was a shader), for diagnostics. Null until something fails. */
22
+ declare function lastPipelineError(): string | null;
23
+ /**
24
+ * Compile a WGSL module, returning null when the source has an error-severity message.
25
+ *
26
+ * Reading `getCompilationInfo()` rather than waiting for pipeline creation is what makes the
27
+ * message available at all: a module built from bad WGSL is a valid object that only fails later,
28
+ * inside `createRenderPipeline`, with a generic validation error.
29
+ */
30
+ declare function compileModule(device: GPUDevice, code: string, label: string, onError?: () => void): Promise<GPUShaderModule | null>;
31
+ /**
32
+ * Create a render pipeline inside a validation error scope, so a layout/blend/vertex-buffer
33
+ * mismatch comes back as a message and a null instead of as a surface that draws nothing.
34
+ */
35
+ declare function createPipeline(device: GPUDevice, descriptor: GPURenderPipelineDescriptor, label: string, onError?: () => void): Promise<GPURenderPipeline | null>;
36
+ /** A uniform bind-group layout with binding 0 as a DYNAMIC-offset uniform buffer — the shape every
37
+ * ring in this package binds through. `minBindingSize` must not exceed the size the bind group
38
+ * actually binds (the slot, not the buffer), so callers that build their own bind group pass the
39
+ * same number to both. */
40
+ declare function uniformBindGroupLayout(device: GPUDevice, visibility?: number, minBindingSize?: number, label?: string): GPUBindGroupLayout;
41
+ /** The per-cell uniform ring: one buffer holding N slots addressed by dynamic offset, so N cells
42
+ * cost ONE buffer and one `writeBuffer` per frame instead of N of each. */
43
+ interface UniformRing {
44
+ buffer: GPUBuffer;
45
+ layout: GPUBindGroupLayout;
46
+ bindGroup: GPUBindGroup;
47
+ /** Slot pitch in BYTES: the dynamic offset for cell i is `i * pitch`. */
48
+ pitch: number;
49
+ /** Slot size in BYTES — what the bind group binds, always ≤ `pitch`. */
50
+ slotBytes: number;
51
+ /** Slot pitch in FLOATS (`pitch / 4`): cell i's staging window starts at `i * stride`. */
52
+ stride: number;
53
+ /** CPU-side mirror of the whole ring, uploaded in one `writeBuffer`. */
54
+ staging: Float32Array;
55
+ destroy(): void;
56
+ }
57
+ interface UniformRingOptions {
58
+ label?: string;
59
+ /** Reuse a caller-owned layout (e.g. one shared with a pipeline layout) instead of making one. */
60
+ layout?: GPUBindGroupLayout;
61
+ visibility?: number;
62
+ }
63
+ declare function createUniformRing(device: GPUDevice, cells: number, slotFloats?: number, options?: UniformRingOptions): UniformRing;
64
+ /** TEST-ONLY: clear the last recorded pipeline/shader error. */
65
+ declare function __resetPipelineErrorForTest(): void;
66
+ //#endregion
67
+ //#region src/particles-webgpu.d.ts
68
+ /** Resolved texture resource supplied by the host. This package never loads URLs or owns images. */
69
+ interface WebgpuParticleTexture {
70
+ readonly view: GPUTextureView;
71
+ readonly sampler: GPUSampler;
72
+ }
73
+ interface WebgpuParticleDrawOptions {
74
+ width: number;
75
+ height: number;
76
+ texture: unknown | null;
77
+ textured: boolean;
78
+ lutTexture: unknown | null;
79
+ maskTexture: unknown | null;
80
+ hframes: number;
81
+ vframes: number;
82
+ blendMode: number;
83
+ alphaFromRed?: boolean;
84
+ erode?: {
85
+ threshold: number;
86
+ softness: number;
87
+ } | null;
88
+ uvPolar?: boolean;
89
+ }
90
+ interface WebgpuParticleSurface {
91
+ readonly context: GPUCanvasContext;
92
+ readonly textures: {
93
+ sprite: WebgpuParticleTexture | null;
94
+ lut: WebgpuParticleTexture | null;
95
+ mask: WebgpuParticleTexture | null;
96
+ };
97
+ readonly onTexturesChanged?: (callback: () => void) => () => void;
98
+ }
99
+ interface WebgpuParticleRendererOptions {
100
+ readonly device: GPUDevice;
101
+ readonly format: GPUTextureFormat;
102
+ readonly readTexturePixels?: (texture: GPUTexture, width: number, height: number) => Promise<Uint8Array>;
103
+ readonly onPipelineError?: () => void;
104
+ }
105
+ /** Bytes per packed instance record: the shipped `INSTANCE_STRIDE` (10 floats) × 4. Stated as a
106
+ * literal because it is what the vertex-buffer layout declares as its `arrayStride` and what every
107
+ * attribute offset below is measured in. */
108
+ declare const INSTANCE_STRIDE_BYTES = 40;
109
+ /** Vertex entry point of `PARTICLE_WGSL`. Referenced by the pipeline descriptors, never spelled twice. */
110
+ declare const PARTICLE_VS_ENTRY = "vs_particles";
111
+ /** Fragment entry point for MIX (and every non-additive) blend: premultiplied colour out. */
112
+ declare const PARTICLE_FS_ENTRY = "fs_particles";
113
+ /** Fragment entry point for the ADDITIVE accumulate pass: raw light out, alpha 0. */
114
+ declare const PARTICLE_FS_ADDITIVE_ENTRY = "fs_particles_additive";
115
+ /** Vertex entry point of `ADDITIVE_RESOLVE_WGSL` (a full-target strip). */
116
+ declare const RESOLVE_VS_ENTRY = "vs_resolve";
117
+ /** Fragment entry point of `ADDITIVE_RESOLVE_WGSL`. */
118
+ declare const RESOLVE_FS_ENTRY = "fs_resolve";
119
+ /**
120
+ * The blend state every non-additive draw uses, and the other half of the premultiply contract in
121
+ * the module header.
122
+ *
123
+ * `one / one-minus-src-alpha` on colour AND alpha: the fragment already carries `rgb * a`, so the
124
+ * source contributes its light unscaled and the destination is attenuated by the coverage the source
125
+ * claims — `dst' = src.rgb*src.a + dst*(1-src.a)`, which is what `SRC_ALPHA / ONE_MINUS_SRC_ALPHA`
126
+ * computes over a straight-alpha source (the GL path's blend). The alpha channel gets the same pair
127
+ * so the canvas's own alpha accumulates the same way.
128
+ */
129
+ declare const PREMULTIPLIED_BLEND: GPUBlendState;
130
+ /** The ACCUMULATE pass's blend: `ONE, ONE` on both channels, i.e. `render-webgl`'s
131
+ * `blendFactorsFor(1)` — overlapping additive particles SUM their raw light (Godot
132
+ * `BLEND_MODE_ADD`) instead of compositing over one another. */
133
+ declare const ADDITIVE_BLEND: GPUBlendState;
134
+ /**
135
+ * The vertex buffer layout: slot 0 is the static unit-quad corner (stepped per VERTEX), slot 1 is the
136
+ * packed instance record (stepped per INSTANCE).
137
+ *
138
+ * The attribute offsets are the shipped float offsets × 4 — center@0, scale@8, rotation@16,
139
+ * color@20, frame@36 — i.e. exactly `INSTANCE_ATTRS` in `render-webgl.ts`, in bytes. Exported so a
140
+ * test can read the stride back without building a device.
141
+ */
142
+ declare const PARTICLE_VERTEX_BUFFERS: GPUVertexBufferLayout[];
143
+ /**
144
+ * The instanced particle module — `render-webgl.ts`'s two shader stages, transcribed.
145
+ *
146
+ * PER-SYSTEM VALUES ARE UNIFORMS, NOT PIPELINE VARIANTS. `textured`/`lut`/`mask`/`uvPolar` etc. select
147
+ * branches at runtime out of ONE pipeline, exactly as the GL program's `u_textured`/`u_lut` do — the
148
+ * alternative (a pipeline per feature combination) is 64 pipelines for a fragment whose branches are
149
+ * uniform-valued and therefore free of divergence. Sampling inside those branches is legal for two
150
+ * independent reasons: a branch on a uniform value is UNIFORM CONTROL FLOW (so even
151
+ * derivative-taking `textureSample` would be allowed), and the samples below use
152
+ * `textureSampleLevel(..., 0.0)`, which needs no derivatives at all. Level 0 is not an approximation
153
+ * here: every texture this backend binds is created with a single mip level (see `../webgpu/textures`).
154
+ */
155
+ declare const PARTICLE_WGSL = "struct Params {\n viewport: vec2f, // canvas backing size, device px \u2014 REWRITTEN EVERY DRAW (canvases resize)\n grid: vec2f, // hframes, vframes\n erodeFactors: vec2f, // threshold, softness\n textured: u32,\n lut: u32,\n alphaFromRed: u32,\n erode: u32,\n mask: u32,\n uvPolar: u32,\n};\n\n@group(0) @binding(0) var<uniform> params: Params;\n@group(1) @binding(0) var sprite_tex: texture_2d<f32>;\n@group(1) @binding(1) var sprite_smp: sampler;\n@group(1) @binding(2) var lut_tex: texture_2d<f32>;\n@group(1) @binding(3) var lut_smp: sampler;\n@group(1) @binding(4) var mask_tex: texture_2d<f32>;\n@group(1) @binding(5) var mask_smp: sampler;\n\n// GLSL's `mod` is FLOOR-signed; WGSL's `%` is TRUNC-signed. The flipbook cell index and the polar\n// wrap are both written against GLSL semantics in the shader this ports, so re-derive them rather\n// than swap in an operator that agrees only for positive operands.\nfn godot_mod(x: f32, y: f32) -> f32 {\n return x - y * floor(x / y);\n}\n\nstruct VsOut {\n @builtin(position) pos: vec4f,\n @location(0) uv: vec2f,\n @location(1) quad: vec2f,\n @location(2) cell: vec2f,\n @location(3) color: vec4f,\n};\n\n@vertex\nfn vs_particles(\n @location(0) corner: vec2f,\n @location(1) center: vec2f,\n @location(2) scale: vec2f,\n @location(3) rotation: f32,\n @location(4) color: vec4f,\n @location(5) frame: f32,\n) -> VsOut {\n let c = cos(rotation);\n let s = sin(rotation);\n let rotated = vec2f(corner.x * c - corner.y * s, corner.x * s + corner.y * c);\n let px = center + rotated * scale;\n var clip = (px / params.viewport) * 2.0 - 1.0;\n clip.y = -clip.y; // canvas Y-down -> clip Y-up\n var out: VsOut;\n out.pos = vec4f(clip, 0.0, 1.0);\n let uv01 = corner + 0.5; // 0..1 across the SPRITE quad\n let cell = vec2f(godot_mod(frame, params.grid.x), floor(frame / params.grid.x));\n out.uv = (cell + uv01) / params.grid;\n // The flipbook cell INDEX, so a fragment that re-derives its own sprite-local UV (the polar remap)\n // can map it back into the same cell instead of over the whole sheet.\n out.cell = cell;\n // Quad-local 0..1, INDEPENDENT of the flipbook grid: the untextured dot and the coverage mask are\n // both measured from this. Deriving them from the atlas-mapped uv put the dot's centre at the\n // SHEET's centre, so any grid > 1x1 left it off-centre and clipped to a sliver of one cell.\n out.quad = uv01;\n out.color = color;\n return out;\n}\n\nstruct Shaded {\n col: vec4f,\n coverage: f32,\n};\n\n// The whole fragment feature set, shared by both entry points below so the mix and additive paths\n// can never disagree about it (in GL they are one shader and a branch).\nfn shade(in: VsOut) -> Shaded {\n var uv = in.uv;\n if (params.uvPolar == 1u) {\n // Godot polar_coordinates(UV, vec2(0.5), 1, 1) (shaders/vfx/_util/polar_coordinates.gdshaderinc):\n // x = radius from the sprite centre (0..1.41 at the corners), y = the angle mapped to 0..1, both\n // wrapped \u2014 then RE-WRAPPED INTO THE SAME CELL through in.cell, so a flipbook stays a flipbook.\n // A radial sheet (common_ring_polar_a) is a RING only through this; sampled flat it is a\n // vertical BAR.\n let dir = in.quad - vec2f(0.5);\n let radius = length(dir) * 2.0;\n let angle = atan2(dir.y, dir.x) * (1.0 / (3.1416 * 2.0));\n uv = (in.cell + vec2f(godot_mod(radius, 1.0), godot_mod(angle, 1.0))) / params.grid;\n }\n var tex: vec4f;\n if (params.textured == 1u) {\n tex = textureSampleLevel(sprite_tex, sprite_smp, uv, 0.0);\n } else {\n // Soft round dot when the system has no texture \u2014 measured across the QUAD, not the atlas-mapped\n // uv, so it stays centred whatever the (meaningless, textureless) grid is.\n let r = length(in.quad - vec2f(0.5)) * 2.0;\n tex = vec4f(1.0, 1.0, 1.0, 1.0 - smoothstep(0.7, 1.0, r));\n }\n // COVERAGE \u2014 taken PRE-LUT, because the LUT is a colour lookup INDEXED by that same red channel:\n // reading it afterwards would sample the LUT's own (usually white) output instead of the sheet's\n // shape. Grayscale VFX sheets are alpha-less PNGs, so tex.a is 1.0 everywhere and the alpha branch\n // draws a SQUARE.\n var coverage = select(tex.a, tex.r, params.alphaFromRed == 1u);\n if (params.lut == 1u) {\n // Godot's VFX particle-shader family: COLOR = vec4(texture(lut, texture_color.rr).rgb, alpha) *\n // vertex_color. The sheet is a single-channel MASK, so its own RGB is meaningless; the LUT holds\n // the real colours. Sampled AFTER the texture/dot resolve so both branches are recoloured, and\n // the source ALPHA is preserved untouched \u2014 only RGB comes from the LUT.\n tex = vec4f(textureSampleLevel(lut_tex, lut_smp, vec2f(tex.r, 0.5), 0.0).rgb, tex.a);\n }\n if (params.erode == 1u) {\n // Godot erosion_from_factors(vec2(threshold, softness), coverage) \u2014 a CONSTANT erosion curve,\n // i.e. the threshold does not sweep over the particle's life. AFTER the LUT, BEFORE the mask.\n coverage = smoothstep(params.erodeFactors.x, params.erodeFactors.x + params.erodeFactors.y, coverage);\n }\n if (params.mask == 1u) {\n // Godot's mask sampler reads the sprite's own UV, NOT the flipbook cell \u2014 it shapes the whole quad.\n coverage = coverage * textureSampleLevel(mask_tex, mask_smp, in.quad, 0.0).r;\n }\n tex.a = coverage;\n var out: Shaded;\n out.col = tex * in.color;\n out.coverage = coverage;\n return out;\n}\n\n@fragment\nfn fs_particles(in: VsOut) -> @location(0) vec4f {\n let col = shade(in).col;\n // PREMULTIPLIED \u2014 see the module header. Only correct under PREMULTIPLIED_BLEND.\n return vec4f(col.rgb * col.a, col.a);\n}\n\n@fragment\nfn fs_particles_additive(in: VsOut) -> @location(0) vec4f {\n // Additive sprites contribute light = colour x alpha (Godot BLEND_MODE_ADD adds src.rgb * src.a to\n // the framebuffer, so an opaque-black glow background or an alpha-shaped sprite's transparent area\n // both add nothing). Emit that light RAW and SUM it across particles (ADDITIVE_BLEND, into the\n // accumulator); the resolve pass converts the per-pixel TOTAL to coverage ONCE. Normalizing per\n // PARTICLE amplified every faint texel to full brightness and let overlaps clamp to white while\n // stacking coverage \u2014 a subtle 5-particle fog rendered as an opaque white haze wall.\n // ALPHA IS 0: the accumulator holds light, not coverage.\n let shaded = shade(in);\n return vec4f(shaded.col.rgb * shaded.coverage * in.color.a, 0.0);\n}\n";
156
+ /**
157
+ * The additive RESOLVE pass: read the summed light out of the accumulator and present it.
158
+ *
159
+ * THE ALGEBRA. A premultiplied canvas wants the accumulated light itself: `(light, cov)` composites
160
+ * to `light + dst*(1-cov)`, with no division at all — and therefore no `cov > 0` guard either, since
161
+ * the division that would have needed one is gone (at cov = 0 the fragment is (0,0,0,0), which
162
+ * composites to `dst` exactly). `RESOLVE_FRAGMENT_SRC` in `./render-webgl.ts` is now the same
163
+ * expression, its canvas being premultiplied too; it used to divide by `cov` for a straight-alpha
164
+ * canvas and rely on the blit into the node canvas to multiply it back.
165
+ *
166
+ * `textureLoad` at integer pixel coordinates, like GL's `texelFetch`: the accumulate pass renders
167
+ * into the top-left w×h rect of a grow-only accumulator, and WebGPU framebuffer coordinates are
168
+ * Y-DOWN in both passes, so texel (x, y) is fragment (x, y) with no flip arithmetic anywhere.
169
+ */
170
+ declare const ADDITIVE_RESOLVE_WGSL = "@group(0) @binding(0) var accum_tex: texture_2d<f32>;\n\n@vertex\nfn vs_resolve(@builtin(vertex_index) index: u32) -> @builtin(position) vec4f {\n // TRIANGLE_STRIP corner order, matching the particle quad's: (-1,-1) (1,-1) (-1,1) (1,1).\n var corners = array<vec2f, 4>(\n vec2f(-1.0, -1.0),\n vec2f(1.0, -1.0),\n vec2f(-1.0, 1.0),\n vec2f(1.0, 1.0)\n );\n return vec4f(corners[index], 0.0, 1.0);\n}\n\n@fragment\nfn fs_resolve(@builtin(position) pos: vec4f) -> @location(0) vec4f {\n let light = textureLoad(accum_tex, vec2i(pos.xy), 0).rgb;\n let cov = max(light.r, max(light.g, light.b));\n return vec4f(light, cov);\n}\n";
171
+ /** Per-surface GPU state, supplied with a presentation context and resolved handles by the host. */
172
+ interface WebgpuParticleSurfaceState extends WebgpuParticleSurface {
173
+ ring: UniformRing;
174
+ words: Uint32Array;
175
+ instances: GPUBuffer | null;
176
+ instanceBytes: number;
177
+ bindGroup: GPUBindGroup | null;
178
+ boundViews: [unknown, unknown, unknown];
179
+ bindGroupDirty: boolean;
180
+ disposeTextureListener: (() => void) | null;
181
+ }
182
+ /** Construct a renderer after its device program is compiled. Presentation contexts and texture handles stay host-owned. */
183
+ declare function createWebgpuParticleRenderer(options: WebgpuParticleRendererOptions): Promise<WebgpuParticleRenderer | null>;
184
+ declare function peekWebgpuParticleRenderer(options: WebgpuParticleRendererOptions): WebgpuParticleRenderer | null | undefined;
185
+ interface WebgpuParticleRenderer {
186
+ createSurface(surface: WebgpuParticleSurface): WebgpuParticleSurfaceState;
187
+ disposeSurface(surface: WebgpuParticleSurfaceState): void;
188
+ beginFrame(): void;
189
+ endFrame(): void;
190
+ clear(surface: WebgpuParticleSurfaceState): void;
191
+ draw(surface: WebgpuParticleSurfaceState, buffer: InstanceBuffer, opts: WebgpuParticleDrawOptions): void;
192
+ captureSurface(surface: WebgpuParticleSurfaceState, buffer: InstanceBuffer, opts: WebgpuParticleDrawOptions): Promise<Uint8Array | null>;
193
+ submits(): number;
194
+ }
195
+ /** TEST-ONLY: drop the device-scope pipelines so a suite can re-probe with a fresh stub device. */
196
+ declare function __resetWebgpuParticleProgramForTest(): void;
197
+ //#endregion
198
+ //#region src/shader-webgpu.d.ts
199
+ declare function createWebgpuShaderBindGroupLayout(device: GPUDevice, label: string, entries: Iterable<GPUBindGroupLayoutEntry>): GPUBindGroupLayout;
200
+ declare function createWebgpuShaderUniformBuffer(device: GPUDevice, label: string, size: number): GPUBuffer;
201
+ declare function createWebgpuShaderBindGroup(device: GPUDevice, label: string, layout: GPUBindGroupLayout, entries: Iterable<GPUBindGroupEntry>): GPUBindGroup;
202
+ interface WebgpuShaderDraw {
203
+ context?: GPUCanvasContext;
204
+ target?: GPUTextureView;
205
+ pipeline: GPURenderPipeline;
206
+ bindGroup: GPUBindGroup;
207
+ uniformBuffer: GPUBuffer;
208
+ uniformBytes: ArrayBuffer | ArrayBufferView;
209
+ width: number;
210
+ height: number;
211
+ }
212
+ /** Owns one command encoder per host frame. It never creates contexts or texture handles. */
213
+ declare class WebgpuShaderExecutor {
214
+ private readonly device;
215
+ private encoder;
216
+ private recorded;
217
+ private implicit;
218
+ private submitCount;
219
+ constructor(device: GPUDevice);
220
+ beginFrame(): void;
221
+ endFrame(): void;
222
+ submits(): number;
223
+ draw(draw: WebgpuShaderDraw): boolean;
224
+ capture(draw: Omit<WebgpuShaderDraw, "context" | "target" | "width" | "height"> & {
225
+ width: number;
226
+ height: number;
227
+ read: (texture: GPUTexture, width: number, height: number) => Promise<Uint8Array>;
228
+ }): Promise<Uint8Array | null>;
229
+ private ensureEncoder;
230
+ private currentView;
231
+ private flush;
232
+ }
233
+ //#endregion
234
+ //#region src/webgpu-pack-uniforms.d.ts
235
+ /** A shader parameter's value as the runtime parsed it out of `data-godot-shader-params`: a scalar,
236
+ * a vector/array flattened into one number list, or absent (the declared default is used). Kept
237
+ * structurally identical to the shader runtime's `ShaderParamValue` so the two cannot drift. */
238
+ type PackedParamValue = number | number[];
239
+ /** The layout half of a `TranspiledWgslShader` — everything `packShaderUniforms` needs and nothing
240
+ * else, so a test can hand-build one. */
241
+ interface ShaderUniformLayout {
242
+ /** Size of the whole struct in bytes (already rounded up to its alignment). */
243
+ uniformStructSizeBytes: number;
244
+ builtinOffsets: WgslBuiltinOffsets;
245
+ uniforms: WgslUniformField[];
246
+ }
247
+ /** The values one render supplies. The built-ins mirror the WebGL backend's uniform writes one for
248
+ * one (see `renderNodeGl`), so the two renderers are fed the SAME numbers; the optional ones are
249
+ * written only when the shader declared them, which is exactly when the offset exists. */
250
+ interface ShaderUniformValues {
251
+ /** `_godot_uv_fit` — the fraction of the canvas the fitted texture covers. */
252
+ uvFit: readonly [number, number];
253
+ /** `_godot_uv_window` — the node-local sub-rect this canvas covers, [u0,v0,du,dv]. */
254
+ uvWindow: readonly number[];
255
+ /** `TIME`, seconds. */
256
+ time?: number;
257
+ /** `TEXTURE_PIXEL_SIZE` — 1/texture width, 1/texture height. */
258
+ texturePixelSize?: readonly [number, number];
259
+ /** `MODULATE` — the node's colour multiply, RGBA. */
260
+ modulate?: readonly number[];
261
+ /** `SCREEN_UV`'s node origin and size within the scene-root viewport. */
262
+ screenOrigin?: readonly [number, number];
263
+ screenSize?: readonly [number, number];
264
+ /** User `shader_parameter/<name>` values, by GODOT name (`WgslUniformField.name`). */
265
+ params?: Record<string, PackedParamValue | undefined>;
266
+ /** Godot's own type name per parameter (`PackedColorArray`, …) — the one case where the wire
267
+ * format needs re-interpreting; see `PACKED_COLOR_ARRAY` below. */
268
+ paramKinds?: Record<string, string>;
269
+ }
270
+ /** The staging buffer for one binding: the same bytes seen as floats and as i32s, because one
271
+ * uniform struct legitimately holds both. `bytes` is what a `writeBuffer` uploads. */
272
+ interface UniformStaging {
273
+ bytes: ArrayBuffer;
274
+ floats: Float32Array;
275
+ ints: Int32Array;
276
+ }
277
+ /** Allocate a staging block big enough for `sizeBytes` (rounded up to a whole float). */
278
+ declare function createUniformStaging(sizeBytes: number): UniformStaging;
279
+ /**
280
+ * Write `values` into `staging` at the byte offsets `layout` declares, and return it.
281
+ *
282
+ * ZEROED FIRST, deliberately: a uniform whose value disappeared between frames (a param attribute
283
+ * dropped, a `MODULATE` that stopped applying) must read as 0, not as whatever the previous frame
284
+ * left in that lane. The struct is small (tens of bytes) and this happens once per binding per
285
+ * frame, so the clear is not worth optimising away for the class of bug it removes.
286
+ */
287
+ declare function packShaderUniforms(layout: ShaderUniformLayout, values: ShaderUniformValues, staging?: UniformStaging): UniformStaging;
288
+ //#endregion
289
+ //#region src/webgpu-readback.d.ts
290
+ interface WebgpuReadbackDevice {
291
+ readonly device: GPUDevice;
292
+ }
293
+ /**
294
+ * Read `width`×`height` RGBA bytes out of `texture`, tightly packed (`width * 4` bytes per row,
295
+ * top-down, PREMULTIPLIED alpha as stored — every fragment on this backend emits `vec4f(rgb*a, a)`
296
+ * under `PREMULTIPLIED_BLEND`, so the bytes in the texture are already multiplied through. A
297
+ * consumer that needs straight alpha (an encode into a 2D canvas) converts: see
298
+ * `./still-capture`).
299
+ *
300
+ * `texture` must have been created with COPY_SRC usage — a texture that will be read back has to
301
+ * declare it at creation, and there is no way to add it afterwards.
302
+ */
303
+ declare function readTexturePixels(shared: WebgpuReadbackDevice, texture: GPUTexture, width: number, height: number): Promise<Uint8Array>;
304
+ //#endregion
305
+ //#region src/webgpu-textures.d.ts
306
+ /** Device-scoped texture allocation and upload primitives. Hosts own decoding and cache keys. */
307
+ declare const WEBGPU_UPLOAD_TEXTURE_USAGE: number;
308
+ interface WebgpuTextureUpload {
309
+ readonly texture: GPUTexture;
310
+ readonly width: number;
311
+ readonly height: number;
312
+ }
313
+ declare function createWebgpuRgbaTexture(device: GPUDevice, width: number, height: number, label?: string): WebgpuTextureUpload;
314
+ declare function uploadWebgpuRgba(device: GPUDevice, target: WebgpuTextureUpload, pixels: Uint8Array | Uint8ClampedArray): void;
315
+ /** Upload a host-decoded external image. The host decides its source and crop. */
316
+ declare function uploadWebgpuExternalImage(device: GPUDevice, target: WebgpuTextureUpload, source: GPUCopyExternalImageSourceInfo): void;
317
+ declare function createWebgpuSampler(device: GPUDevice, opts: {
318
+ readonly nearest: boolean;
319
+ readonly repeat: boolean;
320
+ }): GPUSampler;
321
+ declare function destroyWebgpuTexture(texture: GPUTexture): void;
322
+ //#endregion
323
+ //#region src/webgpu.d.ts
324
+ /**
325
+ * Explicit WebGPU execution entry point.
326
+ *
327
+ * Callers supply their device and presentation surface; this
328
+ * package never creates a canvas, queries the DOM, fetches images, or schedules
329
+ * animation frames.
330
+ */
331
+ /** Configure a caller-supplied WebGPU presentation context for premultiplied output. */
332
+ declare function configureWebgpuSurface(context: GPUCanvasContext, device: GPUDevice, format: GPUTextureFormat): void;
333
+ //#endregion
334
+ export { ADDITIVE_BLEND, ADDITIVE_RESOLVE_WGSL, BUFFER_USAGE, INSTANCE_STRIDE_BYTES, PARTICLE_FS_ADDITIVE_ENTRY, PARTICLE_FS_ENTRY, PARTICLE_VERTEX_BUFFERS, PARTICLE_VS_ENTRY, PARTICLE_WGSL, PREMULTIPLIED_BLEND, PackedParamValue, RESOLVE_FS_ENTRY, RESOLVE_VS_ENTRY, SHADER_STAGE, ShaderUniformLayout, ShaderUniformValues, UNIFORM_SLOT_BYTES, UniformRing, UniformRingOptions, UniformStaging, WEBGPU_UPLOAD_TEXTURE_USAGE, WebgpuParticleDrawOptions, WebgpuParticleRenderer, WebgpuParticleRendererOptions, WebgpuParticleSurface, WebgpuParticleSurfaceState, WebgpuParticleTexture, WebgpuReadbackDevice, WebgpuShaderDraw, WebgpuShaderExecutor, WebgpuTextureUpload, __resetPipelineErrorForTest, __resetWebgpuParticleProgramForTest, compileModule, configureWebgpuSurface, createPipeline, createUniformRing, createUniformStaging, createWebgpuParticleRenderer, createWebgpuRgbaTexture, createWebgpuSampler, createWebgpuShaderBindGroup, createWebgpuShaderBindGroupLayout, createWebgpuShaderUniformBuffer, destroyWebgpuTexture, lastPipelineError, packShaderUniforms, peekWebgpuParticleRenderer, readTexturePixels, uniformBindGroupLayout, uploadWebgpuExternalImage, uploadWebgpuRgba };
335
+ //# sourceMappingURL=webgpu.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webgpu.d.mts","names":[],"sources":["../src/webgpu-pipeline.ts","../src/particles-webgpu.ts","../src/shader-webgpu.ts","../src/webgpu-pack-uniforms.ts","../src/webgpu-readback.ts","../src/webgpu-textures.ts","../src/webgpu.ts"],"mappings":"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"}