@weasel-js/renderer 0.0.0-stage → 1.9.4

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,372 @@
1
+ import { TextureHandle, TilePatternSpec, PosedFill, CommandFill, FillStyle } from '@weasel-js/paint';
2
+ import { Reflection } from '@weasel-js/registry';
3
+ import { Mesh } from '@weasel-js/geom/tessellate';
4
+ import { GlMat3 } from '@weasel-js/geom';
5
+ import { b as ShaderProgram } from './paintKinds-B2vEy9A4.js';
6
+
7
+ /**
8
+ * registerTexture — accepts an image source, assigns an opaque id, stores in
9
+ * a module-level registry. Actual GL upload happens lazily at draw time in
10
+ * drawShader() via GLTextureCache.upload (which is idempotent).
11
+ *
12
+ * Lifecycle: a texture lives until `unregisterTexture`. Each renderer frees
13
+ * its own GL copy at the top of its next frame.
14
+ *
15
+ * Convention §9: this registry stores image data only — no per-renderer state.
16
+ * Each WeaselRenderer's GLTextureCache does its own dedup by id and source.
17
+ */
18
+
19
+ /** A registered texture's image source, uploaded to GL lazily at first draw. */
20
+ interface TextureEntry {
21
+ source: HTMLImageElement | ImageBitmap;
22
+ }
23
+ /** Every registered texture, keyed by its handle id. */
24
+ declare const textureRegistry: Reflection<TextureEntry>;
25
+ /** @internal Test helper — do not call from product code. */
26
+ declare function _resetTextureRegistryForTests(): void;
27
+ /**
28
+ * Register an image for use as a shader texture uniform.
29
+ *
30
+ * Returns a TextureHandle whose `id` can be passed as a ShaderUniform value.
31
+ * The handle is valid for the lifetime of the renderer it will be used with.
32
+ *
33
+ * @param image The image to register. HTMLImageElement must be fully loaded.
34
+ * @returns Opaque TextureHandle.
35
+ */
36
+ declare function registerTexture(image: HTMLImageElement | ImageBitmap): TextureHandle;
37
+ /**
38
+ * Drop a registered texture. Every renderer that uploaded it frees its GL
39
+ * copy at the top of its next frame; a draw command still holding the handle
40
+ * then draws nothing and warns. The image itself is the caller's to release.
41
+ *
42
+ * @returns Whether the handle was registered.
43
+ */
44
+ declare function unregisterTexture(handle: TextureHandle): boolean;
45
+
46
+ /**
47
+ * Clipping staged geometry to an axis-aligned rect on the CPU, so a rect clip
48
+ * needs no stencil and does not break the run.
49
+ *
50
+ * A stencil clip is GL state, so content inside one cannot share a draw with
51
+ * content outside it, and every clipped group paid a flush on the way in and
52
+ * another on the way out. A rect under a transform that keeps it axis-aligned
53
+ * is the common clip, and for that one the clip can ride the geometry instead:
54
+ * cut each staged polygon to the rect before it joins the run.
55
+ *
56
+ * Exact, not approximate: every attribute a batch vertex carries is affine
57
+ * across the polygon it belongs to, so interpolating along a cut edge gives
58
+ * the value the rasterizer would have produced there, and the cut polygon
59
+ * covers the same pixel centers the stencil would have passed.
60
+ */
61
+
62
+ /** In the space batch vertices are staged in: after the model transform,
63
+ * before the projection. */
64
+ interface ClipRect {
65
+ x0: number;
66
+ y0: number;
67
+ x1: number;
68
+ y1: number;
69
+ }
70
+
71
+ /**
72
+ * Growable vertex staging for a run of solid-fill geometry, image quads and
73
+ * glyphs.
74
+ *
75
+ * Geometry only: `draw.ts` owns when a run starts, what breaks it, and the
76
+ * uniforms the flush draws under.
77
+ *
78
+ * **One batch for all three, because a page interleaves them.** A grid of
79
+ * thumbnails is a ground rect under an atlas quad under a caption, per cell,
80
+ * and while each kind staged separately every one had to drain the others
81
+ * before it could stage — so a shape that batches perfectly in any one half
82
+ * alone paid a flush per command. Everything a vertex needs to say which it is
83
+ * fits in the same vertex: solids carry the UV of a 1x1 white texel and are
84
+ * their own color, quads carry their atlas UV and a white color, glyphs carry
85
+ * a font atlas UV, their text color, and a paint mode saying the texel is a
86
+ * distance field rather than a color. Gradients join as a fourth off the ramp
87
+ * atlas — a linear one without a mode of its own, since (ramp position, row) is
88
+ * what the plain mode already samples, and a radial or conic one with a mode
89
+ * that says its UV is a gradient-space coordinate to take a `length` or an
90
+ * `atan` of. See `shaders/batchFill.ts`.
91
+ *
92
+ * Colors ride the vertices because shapes in a run differ in color and a merged
93
+ * draw has one set of uniforms — and so does the model transform, applied here
94
+ * rather than uploaded, so shapes under different transforms still share a
95
+ * draw. The texture rides them too, as a slot index into the units the flush
96
+ * binds: slot 0 is the white texel every solid samples, and `draw.ts` hands out
97
+ * the rest per bitmap. A run holds as many bitmaps as there are slots, so an
98
+ * atlas is what makes a wall of one sheet coalesce and a handful of loose
99
+ * bitmaps no longer breaks a run per command.
100
+ */
101
+
102
+ /** vec2 a_position + vec4 a_vertexColor + vec2 a_uv + float a_post + float
103
+ * a_texSlot. Exported so the buffer-replay tests read fields by name rather
104
+ * than by a stride they repeat — a hard-coded one silently reads a different
105
+ * field when the vertex grows, instead of failing. */
106
+ declare const FLOATS_PER_VERTEX = 10;
107
+ /**
108
+ * A gradient vertex's `a_uv`, each channel affine in the coordinates the
109
+ * geometry arrives in: `u = ux*x + uy*y + u0`, and the same for `v`.
110
+ *
111
+ * One shape for all three gradients. A linear one's `v` row is the constant
112
+ * atlas row and its `u` row is the ramp position; a radial or conic one's two
113
+ * rows are the gradient-space coordinate, and the row rides `a_post` instead.
114
+ */
115
+ interface GradientUV {
116
+ ux: number;
117
+ uy: number;
118
+ u0: number;
119
+ vx: number;
120
+ vy: number;
121
+ v0: number;
122
+ }
123
+ declare class DrawBatch {
124
+ private readonly gl;
125
+ private readonly aPos;
126
+ private readonly aColor;
127
+ private readonly aUv;
128
+ private readonly aPost;
129
+ private readonly aSlot;
130
+ /** One ring per tier, cycled per flush. Slots are created on first use, so a
131
+ * renderer that flushes rarely allocates as few as it flushes. */
132
+ private readonly rings;
133
+ private readonly nextInRing;
134
+ /** The same, for flushes past the largest tier; these sets grow to fit. */
135
+ private readonly largeRing;
136
+ private nextLarge;
137
+ private verts;
138
+ private idx;
139
+ private nVerts;
140
+ private nIdx;
141
+ /** Whether the staged run is quads alone, so its indices are the canonical
142
+ * pattern and a slot already holding that pattern needs no upload. */
143
+ private pureRects;
144
+ /** The rect every push is cut to, or `null`. `draw.ts` sets it as rect
145
+ * clips enter and leave — see `batchClip.ts`. A push returns `false`, having
146
+ * staged nothing, for geometry it cannot cut exactly; see `clipQuad`. */
147
+ clip: ClipRect | null;
148
+ /** Polygon scratch for `clip`: a quad cut by four edges has at most eight
149
+ * corners. */
150
+ private readonly polyIn;
151
+ private readonly polyOut;
152
+ private readonly polyTmp;
153
+ constructor(gl: WebGL2RenderingContext, prog: ShaderProgram);
154
+ get length(): number;
155
+ /** Whether staging `vertices` more would put the run past the per-flush cap. */
156
+ wouldOverflow(vertices: number): boolean;
157
+ /**
158
+ * Append one rect's four corners through `m`, all carrying `rgba` (straight
159
+ * alpha). An affine maps a rect to a parallelogram, so two triangles still
160
+ * cover it and the batch draws at `u_model` identity.
161
+ */
162
+ pushRect(x: number, y: number, w: number, h: number, m: GlMat3, r: number, g: number, b: number, a: number): boolean;
163
+ /**
164
+ * Append one image quad: the destination rect `(x, y, w, h)` mapped through
165
+ * `m`, sampling `(u0, v0)`-`(u1, v1)`, every corner carrying `post` as the
166
+ * after-the-color-matrix alpha factor.
167
+ *
168
+ * Corners wind top-left, top-right, bottom-right, bottom-left with UVs
169
+ * following — the same winding `pushRect` uses, which is what lets a run of
170
+ * mixed rects and quads keep the canonical index pattern. The flips a command
171
+ * asks for are already in the `u` / `v` the caller passes.
172
+ *
173
+ * `slot` is the texture unit the corners sample, which `draw.ts` assigns per
174
+ * bitmap within the run.
175
+ */
176
+ pushQuad(x: number, y: number, w: number, h: number, m: GlMat3, u0: number, v0: number, u1: number, v1: number, post: number, slot: number): boolean;
177
+ /**
178
+ * Append one glyph quad: the box `(x0, y0)`-`(x1, y1)` mapped through `m`,
179
+ * sampling `(u0, v0)`-`(u1, v1)` of the font atlas at `slot`, painted in
180
+ * `rgba`.
181
+ *
182
+ * `mode` says which channels of that atlas carry the distance field, and
183
+ * rides the vertices packed into the slot — so a run off a baked MSDF atlas
184
+ * and one off the runtime canvas bake still share a draw. The synthetic-bold
185
+ * threshold does not: it is `u_synthBold`, and `draw.ts` breaks the run when
186
+ * it changes.
187
+ *
188
+ * **A synthetic oblique is sheared here rather than in the shader.** The old
189
+ * text program carried the baseline per vertex and skewed against
190
+ * `u_synthItalic`; the batch places its own corners, so the shear is one
191
+ * multiply while they are being placed, and `tanItalic` is 0 for an upright
192
+ * face. It has to happen before `m`, which is where the shader had it too.
193
+ *
194
+ * Corners wind top-left, top-right, bottom-right, bottom-left — `pushQuad`'s
195
+ * winding, which is what lets a run of mixed rects, image quads and glyphs
196
+ * keep the canonical index pattern.
197
+ */
198
+ pushGlyph(x0: number, y0: number, x1: number, y1: number, baselineY: number, tanItalic: number, m: GlMat3, u0: number, v0: number, u1: number, v1: number, r: number, g: number, b: number, a: number, slot: number, mode: number): boolean;
199
+ /**
200
+ * Append one rect filled by a gradient: the corners of `(x, y, w, h)` mapped
201
+ * through `m`, sampling the ramp atlas at `slot`.
202
+ *
203
+ * `uv` gives each channel of `a_uv` as an affine function of the coordinates
204
+ * the corners arrive in, which is what lets a gradient ride the vertices at
205
+ * all: a linear one's ramp position is affine in position outright, and a
206
+ * radial or conic one's gradient-space *coordinate* is, even though the ramp
207
+ * position it yields is not. Either way the rasterizer's interpolation across
208
+ * a triangle is exact. `mode` says which of the two the fragment shader is
209
+ * looking at, and `post` carries the atlas row for the modes that read it —
210
+ * see `shaders/batchFill.ts`.
211
+ *
212
+ * Values outside 0..1 are the sampler's business; the atlas clamps to the
213
+ * edge texel, which is what the gradient shader's own `clamp` did.
214
+ */
215
+ pushGradientRect(x: number, y: number, w: number, h: number, m: GlMat3, uv: GradientUV, post: number, slot: number, mode: number, r: number, g: number, b: number, a: number): boolean;
216
+ /** `pushMesh` for a mesh filled by a gradient — see `pushGradientRect` for
217
+ * what `uv`, `post` and `mode` carry. */
218
+ pushGradientMesh(mesh: Mesh, m: GlMat3, uv: GradientUV, post: number, slot: number, mode: number, r: number, g: number, b: number, a: number): boolean;
219
+ /**
220
+ * Append a tessellated mesh through `m`, all vertices carrying `rgba`. The
221
+ * mesh's own indices are rebased onto the staged vertices, which is why the
222
+ * index buffer is uploaded per flush rather than written once.
223
+ */
224
+ pushMesh(mesh: Mesh, m: GlMat3, r: number, g: number, b: number, a: number): boolean;
225
+ /** Upload the staged geometry into the next set of buffers and bind its VAO.
226
+ * Returns the index count for the caller's `drawElements`. */
227
+ uploadAndBind(): number;
228
+ reset(): void;
229
+ dispose(): void;
230
+ private writeVertex;
231
+ /**
232
+ * Fit the quad just written at `v0` to `rect`: keep it if it lies inside,
233
+ * drop it if it misses, and cut it if it crosses — but only if its edges
234
+ * run along the axes. Cutting a slanted edge moves its endpoint off the
235
+ * subpixel grid the rasterizer snaps to, and the shortened edge then covers
236
+ * different samples than the whole one did; along an axis the cut edge is
237
+ * the same line exactly. So a slanted quad that crosses is rolled back and
238
+ * refused, for `draw.ts` to clip through the stencil instead.
239
+ *
240
+ * A cut axis-aligned quad is still four corners, so the run stays pure.
241
+ */
242
+ private clipQuad;
243
+ /** `clipQuad` for a mesh staged from `v0`. A mesh's edges are slanted in
244
+ * general, so one that crosses is refused rather than cut. */
245
+ private clipMesh;
246
+ /** Append the `n`-corner convex polygon in `poly` as a triangle fan. */
247
+ private appendFan;
248
+ /** Two triangles over the four corners just written. */
249
+ private pushQuadIndices;
250
+ /** Grow the CPU arrays so `vertices` / `indices` more fit. */
251
+ private reserve;
252
+ private nextRingSlot;
253
+ private nextLargeSlot;
254
+ private createSet;
255
+ private deleteSet;
256
+ }
257
+
258
+ /**
259
+ * `TilePatternSpec` → `TextureHandle`. The bridge between the serializable
260
+ * half of a pattern paint and the GL texture that paints it.
261
+ *
262
+ * Memoized on the spec's field values, so identical specs across nodes share
263
+ * one texture and a re-render never re-rasterizes. The memo keeps the
264
+ * `PATTERN_TILE_CACHE_LIMIT` tiles most recently resolved or drawn and
265
+ * unregisters the rest. A handle outliving its tile still draws: its id is
266
+ * the spec, and the texture registry asks this module to rebuild it.
267
+ */
268
+
269
+ /** @internal Test helper — do not call from product code. */
270
+ declare function _resetPatternSpecCacheForTests(): void;
271
+ /**
272
+ * Build (or fetch) the texture for a spec. Returns `null` when the tile
273
+ * can't be rasterized — no `OffscreenCanvas`, same as `createTilePattern`.
274
+ */
275
+ declare function resolvePatternSpec(spec: TilePatternSpec): TextureHandle | null;
276
+ /** Whether a pattern payload is a spec rather than an already-built handle. */
277
+ declare function isPatternSpec(payload: TextureHandle | TilePatternSpec): payload is TilePatternSpec;
278
+ /**
279
+ * Swap a pattern paint's spec payload for the texture that paints it,
280
+ * leaving every other fill untouched. The built-in painters run this
281
+ * alongside `fillInPoseFrame`; a consumer emitting its own draw commands
282
+ * runs it at the same place it runs that one.
283
+ *
284
+ * A spec that can't rasterize drops the fill rather than painting garbage.
285
+ */
286
+ declare function resolveFillPattern(fill: PosedFill): CommandFill | null;
287
+ declare function resolveFillPattern(fill: FillStyle): FillStyle | null;
288
+
289
+ /**
290
+ * imageCache — the kit's per-`src` image loader + cache.
291
+ *
292
+ * The shape-painter chain (`kit:image` in `NodeShape.ts`) is synchronous, but
293
+ * decoding an image is async. This module bridges the two: `getImageBitmap` is
294
+ * a synchronous read that returns the decoded `ImageBitmap` when ready, and
295
+ * otherwise returns `undefined` and lazily kicks off a de-duped async load.
296
+ * When a load resolves (or errors), every `subscribeImageReady` listener fires
297
+ * — `<SceneCanvas>` subscribes and calls `requestRedraw()`, so the painter
298
+ * re-runs and emits the now-ready bitmap.
299
+ *
300
+ * `src` is any browser-loadable image string: a remote URL, a `blob:` URL, or
301
+ * a `data:image/…;base64,…` URI (the embedded-bytes path). The decoded bitmap
302
+ * lives only here, keyed by `src` — never in node `data` — so `scene.toJSON()`
303
+ * round-trips the (serializable) `src` string untouched.
304
+ */
305
+ /** Public shape of an image node's `data`. `src` round-trips through
306
+ * serialization; the decoded bitmap is held in this cache, not on the node. */
307
+ interface ImageNodeData {
308
+ image: {
309
+ src: string;
310
+ opacity?: number;
311
+ /** The part of the bitmap drawn into the pose rect, as fractions of the
312
+ * bitmap's width and height from its top-left. Omitted draws the whole
313
+ * bitmap. Fractions, so the data means the same thing before the bitmap
314
+ * has decoded and after `src` is swapped for another resolution. */
315
+ source?: {
316
+ x: number;
317
+ y: number;
318
+ width: number;
319
+ height: number;
320
+ };
321
+ /** Mirror the drawn region within the pose rect. The rect does not move. */
322
+ flipX?: boolean;
323
+ flipY?: boolean;
324
+ };
325
+ }
326
+ /** Where an image is in its load: never requested, in flight, decoded, or
327
+ * failed. Renderers paint a placeholder for anything but `'ready'`. */
328
+ type ImageStatus = 'idle' | 'loading' | 'ready' | 'error';
329
+ /** A size in device pixels. */
330
+ interface ImageRasterSize {
331
+ width: number;
332
+ height: number;
333
+ }
334
+ /** `size` is absent for the first load, which decodes at natural size. */
335
+ type Loader = (src: string, size?: ImageRasterSize) => Promise<ImageBitmap>;
336
+ /** True when `src` names SVG — a `data:image/svg+xml` URI, or a URL whose path
337
+ * ends in `.svg`/`.svgz`. Such sources re-rasterize at the size they are drawn
338
+ * at; everything else decodes once. A `blob:` URL carries no type and counts
339
+ * as raster. */
340
+ declare function isVectorImageSrc(src: string): boolean;
341
+ /** Synchronous read. Returns the decoded bitmap when ready; otherwise returns
342
+ * `undefined` and lazily starts a de-duped async load for `src`.
343
+ *
344
+ * `drawnAt` is the size in device pixels the image is about to cover. A
345
+ * vector source ({@link isVectorImageSrc}) uses it to re-rasterize in
346
+ * power-of-two steps — the current raster is returned until the sharper one
347
+ * is ready. Raster sources ignore it. */
348
+ declare function getImageBitmap(src: string, drawnAt?: ImageRasterSize): ImageBitmap | undefined;
349
+ /** Current load status for `src` (`'idle'` if never requested). */
350
+ declare function imageStatus(src: string): ImageStatus;
351
+ /**
352
+ * Forget `src`: drop its decoded bitmap, its decoded SVG, and the texture every
353
+ * live renderer holds for it, and abandon a load in flight. The cache is keyed
354
+ * by `src` across every scene on the page, so it cannot know when the last use
355
+ * is gone; whoever does know calls this. A later {@link getImageBitmap} for the
356
+ * same `src` loads it again.
357
+ */
358
+ declare function releaseImage(src: string): void;
359
+ /** Load a `src` whose last load failed again. Does nothing for a `src` in any
360
+ * other state; one that failed otherwise stays `'error'` for the page's life. */
361
+ declare function retryImage(src: string): void;
362
+ /** Subscribe to load-resolution events (fires after any `src` resolves or
363
+ * errors). Returns an unsubscribe. */
364
+ declare function subscribeImageReady(cb: () => void): () => void;
365
+ /** @internal test seam — override the async loader so unit tests don't need
366
+ * a real DOM image. Not part of the public barrel. */
367
+ declare function __setImageLoaderForTests(fn: Loader): void;
368
+ /** @internal test seam — clear cache + subscribers and restore the default
369
+ * loader. Not part of the public barrel. */
370
+ declare function _resetImageCacheForTests(): void;
371
+
372
+ export { DrawBatch as D, FLOATS_PER_VERTEX as F, type ImageNodeData as I, type TextureEntry as T, __setImageLoaderForTests as _, type ImageRasterSize as a, type ImageStatus as b, isPatternSpec as c, isVectorImageSrc as d, releaseImage as e, resolveFillPattern as f, getImageBitmap as g, resolvePatternSpec as h, imageStatus as i, retryImage as j, _resetImageCacheForTests as k, _resetPatternSpecCacheForTests as l, _resetTextureRegistryForTests as m, registerTexture as r, subscribeImageReady as s, textureRegistry as t, unregisterTexture as u };