@weasel-js/renderer 1.9.4 → 1.10.1
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.
- package/README.md +2 -2
- package/dist/{chunk-2VOBNWBN.js → chunk-43YL7HZV.js} +3 -3
- package/dist/chunk-43YL7HZV.js.map +1 -0
- package/dist/{chunk-VHQJ56XQ.js → chunk-K7XCWVQH.js} +236 -168
- package/dist/chunk-K7XCWVQH.js.map +1 -0
- package/dist/{chunk-ORBXHIRB.js → chunk-Q4SIHV7Q.js} +4 -4
- package/dist/chunk-Q4SIHV7Q.js.map +1 -0
- package/dist/{imageCache-oJVgxhkr.d.ts → imageCache-DucOxC7l.d.ts} +73 -115
- package/dist/index.d.ts +201 -204
- package/dist/index.js +577 -291
- package/dist/index.js.map +1 -1
- package/dist/mesh.d.ts +34 -67
- package/dist/mesh.js +5 -5
- package/dist/mesh.js.map +1 -1
- package/dist/{paintKinds-B2vEy9A4.d.ts → paintKinds-BmPAT51k.d.ts} +22 -38
- package/dist/patterns-builtin.d.ts +10 -16
- package/dist/patterns-builtin.js +7 -7
- package/dist/patterns-builtin.js.map +1 -1
- package/dist/test-seams.d.ts +22 -65
- package/dist/test-seams.js +2 -2
- package/dist/testing/glRecorder.d.ts +2 -5
- package/dist/testing/glRecorder.js +3 -0
- package/dist/testing/glRecorder.js.map +1 -1
- package/dist/testing/vertexUploads.d.ts +4 -6
- package/dist/testing/vertexUploads.js.map +1 -1
- package/package.json +7 -7
- package/dist/chunk-2VOBNWBN.js.map +0 -1
- package/dist/chunk-ORBXHIRB.js.map +0 -1
- package/dist/chunk-VHQJ56XQ.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TextureHandle,
|
|
1
|
+
import { TextureHandle, Paint, PosedPaint, PaintUnits, GradStop, ColorSpace } from '@weasel-js/paint';
|
|
2
2
|
import { Reflection } from '@weasel-js/registry';
|
|
3
3
|
import { GlMat3 } from '@weasel-js/geom';
|
|
4
4
|
|
|
@@ -10,13 +10,14 @@ import { GlMat3 } from '@weasel-js/geom';
|
|
|
10
10
|
* Designed so test code can stub `getShaderParameter` / `getProgramParameter`
|
|
11
11
|
* via the recorder Proxy without further special-casing.
|
|
12
12
|
*/
|
|
13
|
-
|
|
13
|
+
/** The step of building a program that a `ShaderCompileError` came from. */
|
|
14
|
+
type ShaderStage = 'vertex' | 'fragment' | 'link';
|
|
14
15
|
/** Thrown when a shader fails to compile or link, carrying which stage failed
|
|
15
16
|
* and the driver's log. */
|
|
16
17
|
declare class ShaderCompileError extends Error {
|
|
17
|
-
readonly stage:
|
|
18
|
+
readonly stage: ShaderStage;
|
|
18
19
|
readonly log: string;
|
|
19
|
-
constructor(stage:
|
|
20
|
+
constructor(stage: ShaderStage, log: string);
|
|
20
21
|
}
|
|
21
22
|
/** An array uniform a program can be written as a whole: its element type as
|
|
22
23
|
* declared, its declared length, and the location of slot 0. */
|
|
@@ -48,17 +49,10 @@ declare class ShaderProgram {
|
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
/**
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* calls getProgramSource() and compiles the result. This keeps registerProgram
|
|
56
|
-
* GL-context-agnostic — identical pattern to registerFont storing ImageBitmap.
|
|
57
|
-
*
|
|
58
|
-
* Module-level state = source strings only; compiled GL
|
|
59
|
-
* programs live on each renderer's programRegistry (Map<id, ShaderProgram>).
|
|
60
|
-
*
|
|
61
|
-
* Lifecycle: program sources live for the module lifetime. No unregister in v1.
|
|
52
|
+
* Public API for registering custom shader programs. Module-level state is
|
|
53
|
+
* source strings only; each WeaselRenderer compiles them into its own
|
|
54
|
+
* programRegistry, which keeps registration GL-context-agnostic. Program
|
|
55
|
+
* sources live for the module lifetime.
|
|
62
56
|
*/
|
|
63
57
|
|
|
64
58
|
/** Opaque handle to a compiled custom shader program. */
|
|
@@ -123,22 +117,18 @@ declare function getProgramSource(id: string): ProgramSource | null;
|
|
|
123
117
|
* to compile, the renderer logs the error once and keeps drawing the old one.
|
|
124
118
|
* - Prod mode: calling with an existing id throws.
|
|
125
119
|
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
120
|
+
* Nothing compiles here. A renderer compiles the source the first time it
|
|
121
|
+
* draws with it and reports a failure to the console, drawing nothing for
|
|
122
|
+
* that command; `WeaselRenderer.registerProgram()` compiles ahead of that and
|
|
123
|
+
* throws `ShaderCompileError`.
|
|
130
124
|
*/
|
|
131
125
|
declare function registerProgram(id: string, vert: string, frag: string): ShaderProgramHandle;
|
|
132
126
|
|
|
133
127
|
/**
|
|
134
128
|
* Resolve a `units: 'bounds'` gradient into the frame its node is painted in.
|
|
135
|
-
*
|
|
136
129
|
* Node painters bake the pose into their geometry (`pathInPoseFrame`) rather
|
|
137
130
|
* than emitting a per-node transform, so by the time the renderer sees a
|
|
138
131
|
* scene node there is no node-local space left for a gradient to refer to.
|
|
139
|
-
* This is the paint-side counterpart: it maps box-relative gradient geometry
|
|
140
|
-
* onto the same box the path was just projected into, so the two arrive in
|
|
141
|
-
* the renderer in one frame.
|
|
142
132
|
*/
|
|
143
133
|
|
|
144
134
|
/** The box a `'bounds'` gradient's normalized geometry is mapped onto —
|
|
@@ -160,20 +150,16 @@ interface FillPoseBox {
|
|
|
160
150
|
* so a non-square box cannot make it the ellipse SVG would — this keeps the
|
|
161
151
|
* radius proportionate instead.
|
|
162
152
|
*/
|
|
163
|
-
declare function fillInPoseFrame(fill:
|
|
153
|
+
declare function fillInPoseFrame(fill: Paint, box: FillPoseBox): PosedPaint;
|
|
164
154
|
/**
|
|
165
155
|
* Inverse of {@link fillInPoseFrame}: express a gradient already positioned
|
|
166
156
|
* in `box`'s frame as `0..1` fractions of that box (`units: 'bounds'`), so
|
|
167
157
|
* it follows the node when moved or resized.
|
|
168
158
|
*
|
|
169
|
-
* The importer's counterpart — SVG `userSpaceOnUse` coordinates arrive in
|
|
170
|
-
* page space, and normalizing them here makes an imported gradient behave
|
|
171
|
-
* exactly like one drawn in the app.
|
|
172
|
-
*
|
|
173
159
|
* A zero-width or zero-height box has no frame to normalize against; the
|
|
174
160
|
* fill is returned untouched rather than divided by zero.
|
|
175
161
|
*/
|
|
176
|
-
declare function fillToBoundsFrame(fill:
|
|
162
|
+
declare function fillToBoundsFrame(fill: Paint, box: FillPoseBox): Paint;
|
|
177
163
|
|
|
178
164
|
/**
|
|
179
165
|
* The renderer's half of a paint kind: what it takes to draw one.
|
|
@@ -181,9 +167,7 @@ declare function fillToBoundsFrame(fill: FillStyle, box: FillPoseBox): FillStyle
|
|
|
181
167
|
* Solids, patterns and the three gradients are branches inside the GL
|
|
182
168
|
* renderer and never come through here. A kind beyond those registers its
|
|
183
169
|
* bind slot, its frame conversions and its shaders, and the renderer consults
|
|
184
|
-
* this registry for any kind it does not recognize.
|
|
185
|
-
* serializer needs of a kind is not the renderer's to know; whoever owns
|
|
186
|
-
* those registers it beside this.
|
|
170
|
+
* this registry for any kind it does not recognize.
|
|
187
171
|
*/
|
|
188
172
|
|
|
189
173
|
/** A compiled GL program. A paint kind gets one from
|
|
@@ -227,14 +211,14 @@ interface PaintBindContext {
|
|
|
227
211
|
* an axis. Nothing the paint draws there means anything, so `bind` should
|
|
228
212
|
* return `null` and draw nothing.
|
|
229
213
|
*/
|
|
230
|
-
spaceInverse(units:
|
|
214
|
+
spaceInverse(units: PaintUnits | undefined): GlMat3 | null;
|
|
231
215
|
/**
|
|
232
216
|
* The transform from the space `units` names to device pixels of the target
|
|
233
217
|
* this draw lands in — screen pixels times the pixel ratio on screen, output
|
|
234
218
|
* pixels in a headless render. It is what a paint baking a texture measures
|
|
235
219
|
* its resolution against. `null` where `spaceInverse` is.
|
|
236
220
|
*/
|
|
237
|
-
spaceToDevice(units:
|
|
221
|
+
spaceToDevice(units: PaintUnits | undefined): GlMat3 | null;
|
|
238
222
|
/** The largest texture side this context accepts, `MAX_TEXTURE_SIZE`. */
|
|
239
223
|
readonly maxTextureSize: number;
|
|
240
224
|
/** The lifetime GPU state made for this draw belongs to. Stable across the
|
|
@@ -275,11 +259,11 @@ interface PaintDrawKind {
|
|
|
275
259
|
* `v_world` through `spaceInverse`, not from where `a_position` happens to
|
|
276
260
|
* lie.
|
|
277
261
|
*/
|
|
278
|
-
bind?(ctx: PaintBindContext, fill:
|
|
262
|
+
bind?(ctx: PaintBindContext, fill: Paint): PaintProgram | null;
|
|
279
263
|
/** Bounds frame → the frame the node is painted in. */
|
|
280
|
-
inPoseFrame?(fill:
|
|
264
|
+
inPoseFrame?(fill: Paint, box: FillPoseBox): Paint;
|
|
281
265
|
/** The inverse. Required whenever `inPoseFrame` is supplied. */
|
|
282
|
-
toBoundsFrame?(fill:
|
|
266
|
+
toBoundsFrame?(fill: Paint, box: FillPoseBox): Paint;
|
|
283
267
|
/**
|
|
284
268
|
* Shader programs `bind` looks up through `ctx.program`, keyed by program
|
|
285
269
|
* id; registering the kind registers them. Declaring them here rather than
|
|
@@ -327,4 +311,4 @@ declare function warmPaintKinds(kinds?: readonly string[]): Promise<void>;
|
|
|
327
311
|
/** @internal Test helper — do not call from product code. */
|
|
328
312
|
declare function _resetPaintDrawKindsForTests(): void;
|
|
329
313
|
|
|
330
|
-
export { type FillPoseBox as F, type PaintBindContext as P, type ShaderProgramHandle as S, _resetPaintDrawKindsForTests as _, type ShaderUniform as a, ShaderProgram as b, type PaintDrawKind as c, type PaintDrawKindLoader as d, type PaintProgram as e, type PaintResources as f, type ProgramSource as g, ShaderCompileError as h,
|
|
314
|
+
export { type FillPoseBox as F, type PaintBindContext as P, type ShaderProgramHandle as S, type UniformArray as U, _resetPaintDrawKindsForTests as _, type ShaderUniform as a, ShaderProgram as b, type PaintDrawKind as c, type PaintDrawKindLoader as d, type PaintProgram as e, type PaintResources as f, type ProgramSource as g, ShaderCompileError as h, type ShaderStage as i, fillInPoseFrame as j, fillToBoundsFrame as k, getPaintDrawKind as l, getProgramSource as m, isPaintKindKnown as n, programSourceRegistry as o, paintDrawKindRegistry as p, registerPaintDrawKindLoader as q, registerPaintDrawKind as r, registerProgram as s, _resetProgramRegistryForTests as t, warmPaintKinds as w };
|
|
@@ -56,20 +56,16 @@ declare function drawTileShapes(oc: CanvasRenderingContext2D, geometry: TileGeom
|
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
58
|
* Built-in pattern factories — generic line/dot/scatter textures. Each
|
|
59
|
-
* returns a `TextureHandle` you can wrap in a `
|
|
60
|
-
* `{
|
|
61
|
-
*
|
|
62
|
-
* Imported from a subpath so consumers that ship their own catalog can
|
|
63
|
-
* avoid the bundle cost.
|
|
59
|
+
* returns a `TextureHandle` you can wrap in a `Paint` of
|
|
60
|
+
* `{ kind: 'texture', texture }`. Imported from a subpath so consumers that
|
|
61
|
+
* ship their own catalog can avoid the bundle cost.
|
|
64
62
|
*
|
|
65
|
-
* All factories require an explicit `color
|
|
66
|
-
* defaults are baked in — the caller's design system is the source of truth
|
|
67
|
-
* for palette decisions.
|
|
63
|
+
* All factories require an explicit `color`; no defaults are baked in.
|
|
68
64
|
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
65
|
+
* Prefer a tile paint (`{ kind: 'hatch', color }`) over calling these directly —
|
|
66
|
+
* it serializes, a `TextureHandle` does not.
|
|
67
|
+
*
|
|
68
|
+
* @packageDocumentation
|
|
73
69
|
*/
|
|
74
70
|
|
|
75
71
|
/** Diagonal hatch (forward slash). */
|
|
@@ -98,10 +94,8 @@ interface DotsParams {
|
|
|
98
94
|
declare function dots(params: DotsParams): TextureHandle | null;
|
|
99
95
|
/**
|
|
100
96
|
* Random scatter of small ellipses on top of a solid background. Driven by a
|
|
101
|
-
* seeded PRNG so the same `seed` produces a deterministic tile.
|
|
102
|
-
*
|
|
103
|
-
* Note: the `bg` field is optional — omit it for a transparent background
|
|
104
|
-
* (the scatter shapes draw directly onto whatever sits beneath the overlay).
|
|
97
|
+
* seeded PRNG so the same `seed` produces a deterministic tile. Omit `bg` for
|
|
98
|
+
* a transparent background.
|
|
105
99
|
*/
|
|
106
100
|
interface ChunksParams {
|
|
107
101
|
color: string;
|
package/dist/patterns-builtin.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { createTilePattern } from './chunk-
|
|
2
|
-
import { tileGeometry, drawTileShapes, tileSize } from './chunk-
|
|
3
|
-
export { drawTileShapes, tileGeometry, tileSize } from './chunk-
|
|
1
|
+
import { createTilePattern } from './chunk-43YL7HZV.js';
|
|
2
|
+
import { tileGeometry, drawTileShapes, tileSize } from './chunk-Q4SIHV7Q.js';
|
|
3
|
+
export { drawTileShapes, tileGeometry, tileSize } from './chunk-Q4SIHV7Q.js';
|
|
4
4
|
|
|
5
5
|
// src/patterns/patterns-builtin.ts
|
|
6
6
|
function build(spec) {
|
|
@@ -11,16 +11,16 @@ function build(spec) {
|
|
|
11
11
|
});
|
|
12
12
|
}
|
|
13
13
|
function hatch(params) {
|
|
14
|
-
return build({
|
|
14
|
+
return build({ kind: "hatch", ...params });
|
|
15
15
|
}
|
|
16
16
|
function crosshatch(params) {
|
|
17
|
-
return build({
|
|
17
|
+
return build({ kind: "crosshatch", ...params });
|
|
18
18
|
}
|
|
19
19
|
function dots(params) {
|
|
20
|
-
return build({
|
|
20
|
+
return build({ kind: "dots", ...params });
|
|
21
21
|
}
|
|
22
22
|
function chunks(params) {
|
|
23
|
-
return build({
|
|
23
|
+
return build({ kind: "chunks", ...params });
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
export { chunks, crosshatch, dots, hatch };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/patterns/patterns-builtin.ts"],"names":[],"mappings":";;;;;
|
|
1
|
+
{"version":3,"sources":["../src/patterns/patterns-builtin.ts"],"names":[],"mappings":";;;;;AAsBA,SAAS,MAAM,IAAA,EAA6C;AAC1D,EAAA,MAAM,QAAA,GAAW,aAAa,IAAI,CAAA;AAClC,EAAA,OAAO,iBAAA,CAAkB;AAAA,IACvB,IAAA,EAAM,SAAS,IAAI,CAAA;AAAA,IACnB,IAAA,EAAM,CAAC,EAAA,KAAO,cAAA,CAAe,IAAI,QAAQ;AAAA,GAC1C,CAAA;AACH;AAUO,SAAS,MAAM,MAAA,EAA2C;AAC/D,EAAA,OAAO,MAAM,EAAE,IAAA,EAAM,OAAA,EAAS,GAAG,QAAQ,CAAA;AAC3C;AAUO,SAAS,WAAW,MAAA,EAAgD;AACzE,EAAA,OAAO,MAAM,EAAE,IAAA,EAAM,YAAA,EAAc,GAAG,QAAQ,CAAA;AAChD;AAUO,SAAS,KAAK,MAAA,EAA0C;AAC7D,EAAA,OAAO,MAAM,EAAE,IAAA,EAAM,MAAA,EAAQ,GAAG,QAAQ,CAAA;AAC1C;AAkBO,SAAS,OAAO,MAAA,EAA4C;AACjE,EAAA,OAAO,MAAM,EAAE,IAAA,EAAM,QAAA,EAAU,GAAG,QAAQ,CAAA;AAC5C","file":"patterns-builtin.js","sourcesContent":["/**\n * Built-in pattern factories — generic line/dot/scatter textures. Each\n * returns a `TextureHandle` you can wrap in a `Paint` of\n * `{ kind: 'texture', texture }`. Imported from a subpath so consumers that\n * ship their own catalog can avoid the bundle cost.\n *\n * All factories require an explicit `color`; no defaults are baked in.\n *\n * Prefer a tile paint (`{ kind: 'hatch', color }`) over calling these directly —\n * it serializes, a `TextureHandle` does not.\n *\n * @packageDocumentation\n */\n\nexport { tileGeometry, tileSize, drawTileShapes } from './tileGeometry';\nexport type { TileShape, TileGeometry } from './tileGeometry';\n\nimport { createTilePattern } from './';\nimport { tileGeometry, drawTileShapes, tileSize } from './tileGeometry';\nimport type { TilePatternSpec } from '@weasel-js/paint';\nimport type { TextureHandle } from '../textures/registerTexture';\n\nfunction build(spec: TilePatternSpec): TextureHandle | null {\n const geometry = tileGeometry(spec);\n return createTilePattern({\n size: tileSize(spec),\n draw: (oc) => drawTileShapes(oc, geometry),\n });\n}\n\n/** Diagonal hatch (forward slash). */\nexport interface HatchParams {\n color: string;\n size?: number;\n lineWidth?: number;\n}\n\n/** Build a diagonal-hatch pattern texture. */\nexport function hatch(params: HatchParams): TextureHandle | null {\n return build({ kind: 'hatch', ...params });\n}\n\n/** Diagonal hatch in both directions. */\nexport interface CrosshatchParams {\n color: string;\n size?: number;\n lineWidth?: number;\n}\n\n/** Build a crosshatch pattern texture. */\nexport function crosshatch(params: CrosshatchParams): TextureHandle | null {\n return build({ kind: 'crosshatch', ...params });\n}\n\n/** Regular grid of filled dots. */\nexport interface DotsParams {\n color: string;\n size?: number;\n radius?: number;\n}\n\n/** Build a dot-grid pattern texture. */\nexport function dots(params: DotsParams): TextureHandle | null {\n return build({ kind: 'dots', ...params });\n}\n\n/**\n * Random scatter of small ellipses on top of a solid background. Driven by a\n * seeded PRNG so the same `seed` produces a deterministic tile. Omit `bg` for\n * a transparent background.\n */\nexport interface ChunksParams {\n color: string;\n bg?: string;\n size?: number;\n /** Coverage: `chunks * chunkSize²` per `size²` tile. Range ~[0, 1]. */\n density?: number;\n chunkSize?: number;\n seed?: number;\n}\n\n/** Build a seeded random-scatter pattern texture. */\nexport function chunks(params: ChunksParams): TextureHandle | null {\n return build({ kind: 'chunks', ...params });\n}\n"]}
|
package/dist/test-seams.d.ts
CHANGED
|
@@ -1,21 +1,15 @@
|
|
|
1
1
|
import { Path } from '@weasel-js/geom';
|
|
2
2
|
import { Mesh } from '@weasel-js/geom/tessellate';
|
|
3
|
-
export { F as FLOATS_PER_VERTEX, _ as __setImageLoaderForTests, k as _resetImageCacheForTests, l as _resetPatternSpecCacheForTests, m as _resetTextureRegistryForTests } from './imageCache-
|
|
4
|
-
export { _ as _resetPaintDrawKindsForTests,
|
|
3
|
+
export { F as FLOATS_PER_VERTEX, _ as __setImageLoaderForTests, k as _resetImageCacheForTests, l as _resetPatternSpecCacheForTests, m as _resetTextureRegistryForTests } from './imageCache-DucOxC7l.js';
|
|
4
|
+
export { _ as _resetPaintDrawKindsForTests, t as _resetProgramRegistryForTests } from './paintKinds-BmPAT51k.js';
|
|
5
5
|
import '@weasel-js/paint';
|
|
6
6
|
import '@weasel-js/registry';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* A rect path the batch stages as a quad never reaches this cache. One used
|
|
14
|
-
* as a clip, or filled with a pattern or vertex colors, does.
|
|
15
|
-
*
|
|
16
|
-
* Also bypassed whenever `DrawContext.flattenTolerance` is set: `fillMeshHandle`
|
|
17
|
-
* (see `../draw`) routes custom-tolerance fills through a fresh transient
|
|
18
|
-
* tessellation instead of this cache.
|
|
9
|
+
* The tessellated Mesh for `path`, cached by Path identity — different Path
|
|
10
|
+
* objects with the same coords are distinct cache entries. A mesh built under
|
|
11
|
+
* another default open-fill rule is rebuilt. Bypassed whenever
|
|
12
|
+
* `DrawContext.flattenTolerance` is set.
|
|
19
13
|
*/
|
|
20
14
|
declare function getMesh(path: Path): Mesh;
|
|
21
15
|
/** Test helper. Do not call from product code. */
|
|
@@ -39,66 +33,31 @@ declare function _resetCacheForTests(): void;
|
|
|
39
33
|
declare function _resetMarkerCommandCacheForTests(): void;
|
|
40
34
|
|
|
41
35
|
/**
|
|
42
|
-
* Tessellated glyph outlines, in em space.
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* transform that follows. It is also the half that does not vary — em space
|
|
47
|
-
* is unit-scale and baseline-relative, so the same glyph of the same face is
|
|
48
|
-
* the same triangles at every size, position and zoom. Tessellate once, keep
|
|
49
|
-
* it, and pay only a scale-and-translate per occurrence.
|
|
50
|
-
*
|
|
51
|
-
* That is why the renderer transforms cached vertices on the CPU and appends
|
|
52
|
-
* them into one shared buffer rather than giving each glyph its own model
|
|
53
|
-
* matrix: a per-glyph matrix would mean a draw call per glyph, throwing away
|
|
54
|
-
* the batching the atlas tier already has, in exchange for saving an
|
|
55
|
-
* arithmetic pass over a few hundred vertices.
|
|
56
|
-
*
|
|
57
|
-
* ### Flatten tolerance
|
|
36
|
+
* Tessellated glyph outlines, in em space. Em space is unit-scale and
|
|
37
|
+
* baseline-relative, so the same glyph of the same face is the same triangles
|
|
38
|
+
* at every size, position, and zoom: tessellate once and pay only a
|
|
39
|
+
* scale-and-translate per occurrence.
|
|
58
40
|
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* have to reach ~4000 screen pixels tall before the flattening error covered
|
|
63
|
-
* a single pixel — well past any size a document is read at, and far enough
|
|
64
|
-
* past the tier's own threshold that the two never interact.
|
|
41
|
+
* Flatten tolerance: an error of `T` em shows up as `T × fontSize × viewScale`
|
|
42
|
+
* screen pixels. At 1/4096 em, a glyph would have to reach ~4000 screen pixels
|
|
43
|
+
* tall before the flattening error covered a single pixel.
|
|
65
44
|
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
* A plain `Map` with a cap and wholesale eviction. Glyph tessellations are
|
|
69
|
-
* small (a few hundred vertices) and the working set is a charset, not a
|
|
70
|
-
* document — the cap exists so a page that cycles through many faces cannot
|
|
71
|
-
* grow without limit, not because pressure is expected. Wholesale rather than
|
|
72
|
-
* LRU because the refill cost is bounded by what is actually on screen, and
|
|
73
|
-
* an LRU's bookkeeping would cost more than the misses it avoids.
|
|
45
|
+
* Eviction is wholesale rather than LRU because the refill cost is bounded by
|
|
46
|
+
* what is actually on screen.
|
|
74
47
|
*/
|
|
75
48
|
|
|
76
49
|
/** Test helper. Do not call from product code. */
|
|
77
50
|
declare function _resetOutlineMeshCacheForTests(): void;
|
|
78
51
|
|
|
79
52
|
/**
|
|
80
|
-
* Tessellated glyph *outlines' strokes*, in em space
|
|
81
|
-
*
|
|
82
|
-
* The sibling of `outlineMeshCache`, and cached for the same reason: parsing
|
|
83
|
-
* `d` and tessellating a ribbon costs far more than the per-instance
|
|
84
|
-
* transform that follows, and em space is size- and position-independent, so
|
|
85
|
-
* one tessellation serves every occurrence of that glyph.
|
|
86
|
-
*
|
|
87
|
-
* ### Why the width is in the key
|
|
53
|
+
* Tessellated glyph *outlines' strokes*, in em space: the sibling of
|
|
54
|
+
* `outlineMeshCache`.
|
|
88
55
|
*
|
|
89
|
-
* A
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
* animates stroke width or font size will miss every frame. That is the same
|
|
95
|
-
* bargain `tessellate` makes everywhere else, and it is the reason the width
|
|
96
|
-
* is quantized before it reaches the key: neighbouring sub-quantum widths
|
|
97
|
-
* would otherwise each get their own entry and evict each other.
|
|
98
|
-
*
|
|
99
|
-
* Joins, caps, miter limit, alignment and dash all change the ribbon too, so
|
|
100
|
-
* they are in the key as well — assembled by the caller, which already has
|
|
101
|
-
* the `Stroke` in hand.
|
|
56
|
+
* A stroke's em-space mesh depends on `strokeWidth / fontSize`, so the width is
|
|
57
|
+
* in the key, and a document that animates stroke width or font size will miss
|
|
58
|
+
* every frame. The width is quantized before it reaches the key: neighboring
|
|
59
|
+
* sub-quantum widths would otherwise each get their own entry and evict each
|
|
60
|
+
* other.
|
|
102
61
|
*/
|
|
103
62
|
|
|
104
63
|
/** Test helper. Do not call from product code. */
|
|
@@ -110,8 +69,6 @@ declare function _resetOutlineStrokeMeshCacheForTests(): void;
|
|
|
110
69
|
* The sibling of `cache.ts` (fills) and `outlineStrokeMeshCache.ts` (glyph
|
|
111
70
|
* ribbons in em space), and it inherits their contract: identity, not content.
|
|
112
71
|
* A `Path` rebuilt with equal coords is a distinct entry.
|
|
113
|
-
*
|
|
114
|
-
* Design: `docs/superpowers/specs/2026-08-15-stroke-ribbon-cache-design.md`.
|
|
115
72
|
*/
|
|
116
73
|
|
|
117
74
|
/** Test helper. Do not call from product code. */
|
package/dist/test-seams.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { FLOATS_PER_VERTEX, __setImageLoaderForTests, _resetCacheForTests, _resetImageCacheForTests, _resetMarkerCommandCacheForTests, _resetOutlineMeshCacheForTests, _resetOutlineStrokeMeshCacheForTests, _resetPaintDrawKindsForTests, _resetPatternSpecCacheForTests, _resetProgramRegistryForTests, _resetStrokeMeshCacheForTests, getMesh } from './chunk-
|
|
2
|
-
export { _resetTextureRegistryForTests } from './chunk-
|
|
1
|
+
export { FLOATS_PER_VERTEX, __setImageLoaderForTests, _resetCacheForTests, _resetImageCacheForTests, _resetMarkerCommandCacheForTests, _resetOutlineMeshCacheForTests, _resetOutlineStrokeMeshCacheForTests, _resetPaintDrawKindsForTests, _resetPatternSpecCacheForTests, _resetProgramRegistryForTests, _resetStrokeMeshCacheForTests, getMesh } from './chunk-K7XCWVQH.js';
|
|
2
|
+
export { _resetTextureRegistryForTests } from './chunk-Q4SIHV7Q.js';
|
|
3
3
|
//# sourceMappingURL=test-seams.js.map
|
|
4
4
|
//# sourceMappingURL=test-seams.js.map
|
|
@@ -4,11 +4,8 @@
|
|
|
4
4
|
* Records every method call as { name, args, result } so tests can assert
|
|
5
5
|
* against the call sequence. Returns synthetic handles for object creation
|
|
6
6
|
* (createShader/Program/Buffer/etc.); reports truthy for *Parameter status
|
|
7
|
-
* queries (i.e. assumes shader/program compilation succeeded).
|
|
8
|
-
*
|
|
9
|
-
* NOT a renderer correctness check — pixel-level correctness is verified
|
|
10
|
-
* end-to-end by the smoke test in dev/smoke.spec.ts. This recorder catches
|
|
11
|
-
* "did the dispatcher emit the right call sequence" bugs only.
|
|
7
|
+
* queries (i.e. assumes shader/program compilation succeeded). NOT a renderer
|
|
8
|
+
* correctness check: it sees the call sequence, never a pixel.
|
|
12
9
|
*/
|
|
13
10
|
interface GLCall {
|
|
14
11
|
readonly name: string;
|
|
@@ -21,11 +21,14 @@ var GL_CONSTANTS = {
|
|
|
21
21
|
STENCIL_TEST: 2960,
|
|
22
22
|
CULL_FACE: 2884,
|
|
23
23
|
UNPACK_FLIP_Y_WEBGL: 37440,
|
|
24
|
+
UNPACK_PREMULTIPLY_ALPHA_WEBGL: 37441,
|
|
24
25
|
SCISSOR_TEST: 3089,
|
|
25
26
|
MAX_TEXTURE_SIZE: 3379,
|
|
26
27
|
// Blend factors
|
|
27
28
|
SRC_ALPHA: 770,
|
|
28
29
|
ONE_MINUS_SRC_ALPHA: 771,
|
|
30
|
+
DST_ALPHA: 772,
|
|
31
|
+
ONE_MINUS_DST_ALPHA: 773,
|
|
29
32
|
ZERO: 0,
|
|
30
33
|
ONE: 1,
|
|
31
34
|
// Buffer bits
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/testing/glRecorder.ts"],"names":[],"mappings":"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","file":"glRecorder.js","sourcesContent":["/**\n * Proxy-based recording WebGL2 context for unit tests.\n *\n * Records every method call as { name, args, result } so tests can assert\n * against the call sequence. Returns synthetic handles for object creation\n * (createShader/Program/Buffer/etc.); reports truthy for *Parameter status\n * queries (i.e. assumes shader/program compilation succeeded).\n *\n * NOT a renderer correctness check — pixel-level correctness is verified\n * end-to-end by the smoke test in dev/smoke.spec.ts. This recorder catches\n * \"did the dispatcher emit the right call sequence\" bugs only.\n */\n\nexport interface GLCall {\n readonly name: string;\n readonly args: readonly unknown[];\n readonly result: unknown;\n}\n\n/** What the recorded context reports about itself. */\nexport interface GLRecorderOptions {\n /** Reported as `gl.drawingBufferWidth`. Default 800. */\n drawingBufferWidth?: number;\n /** Reported as `gl.drawingBufferHeight`. Default 600. */\n drawingBufferHeight?: number;\n /** Returned by `gl.getContextAttributes()`. Default `{ stencil: true }`. */\n contextAttributes?: Partial<WebGLContextAttributes>;\n}\n\n/** A stand-in context and the calls made on it, oldest first. */\nexport interface GLRecorder {\n readonly gl: WebGL2RenderingContext;\n readonly calls: GLCall[];\n reset(): void;\n}\n\nconst GL_CONSTANTS: Readonly<Record<string, number>> = {\n // Shader / program\n VERTEX_SHADER: 0x8B31,\n FRAGMENT_SHADER: 0x8B30,\n COMPILE_STATUS: 0x8B81,\n LINK_STATUS: 0x8B82,\n // Buffer targets\n ARRAY_BUFFER: 0x8892,\n ELEMENT_ARRAY_BUFFER: 0x8893,\n STATIC_DRAW: 0x88E4,\n // Types\n FLOAT: 0x1406,\n UNSIGNED_INT: 0x1405,\n // Primitives\n TRIANGLES: 0x0004,\n // Errors / state\n NO_ERROR: 0,\n DEPTH_TEST: 0x0B71,\n BLEND: 0x0BE2,\n STENCIL_TEST: 0x0B90,\n CULL_FACE: 0x0B44,\n UNPACK_FLIP_Y_WEBGL: 0x9240,\n SCISSOR_TEST: 0x0C11,\n MAX_TEXTURE_SIZE: 0x0D33,\n // Blend factors\n SRC_ALPHA: 0x0302,\n ONE_MINUS_SRC_ALPHA: 0x0303,\n ZERO: 0,\n ONE: 1,\n // Buffer bits\n COLOR_BUFFER_BIT: 0x00004000,\n STENCIL_BUFFER_BIT: 0x00000400,\n DEPTH_BUFFER_BIT: 0x00000100,\n // Stencil ops\n KEEP: 0x1E00,\n REPLACE: 0x1E01,\n INVERT: 0x150A,\n // Compare funcs\n ALWAYS: 0x0207,\n NEVER: 0x0200,\n EQUAL: 0x0202,\n NOTEQUAL: 0x0205,\n // Color masks\n FRONT_AND_BACK: 0x0408,\n // Misc constants used in step 1\n COLOR_WRITEMASK: 0x0C23,\n // Textures (step 3)\n TEXTURE_2D: 0x0DE1,\n TEXTURE0: 0x84C0,\n RGBA: 0x1908,\n UNSIGNED_BYTE: 0x1401,\n TEXTURE_MIN_FILTER: 0x2801,\n TEXTURE_MAG_FILTER: 0x2800,\n LINEAR: 0x2601,\n NEAREST: 0x2600,\n TEXTURE_WRAP_S: 0x2802,\n TEXTURE_WRAP_T: 0x2803,\n CLAMP_TO_EDGE: 0x812F,\n // Wrap modes (step 4)\n REPEAT: 0x2901,\n MIRRORED_REPEAT: 0x8370,\n // Mipmap minification filter\n LINEAR_MIPMAP_LINEAR: 0x2703,\n // Framebuffers and renderbuffers (offscreen effect passes). Every one of\n // these has to be here before a test names it: an absent ALL-CAPS property\n // reads as 0 through the proxy, so `expect(args[0]).toBe(gl.FRAMEBUFFER)`\n // would compare 0 against 0 and pass over a renderer that bound nothing.\n FRAMEBUFFER: 0x8D40,\n RENDERBUFFER: 0x8D41,\n FRAMEBUFFER_BINDING: 0x8CA6,\n RENDERBUFFER_BINDING: 0x8CA7,\n COLOR_ATTACHMENT0: 0x8CE0,\n DEPTH_STENCIL_ATTACHMENT: 0x821A,\n DEPTH24_STENCIL8: 0x88F0,\n RGBA8: 0x8058,\n FRAMEBUFFER_COMPLETE: 0x8CD5,\n};\n\nfunction syntheticHandle(name: string, seq: number): { __id: string } {\n return { __id: `${name}_${seq}` };\n}\n\n/**\n * Copy typed-array arguments as they are recorded.\n *\n * A caller is entitled to reuse the array it uploads from — the text ring\n * packs every draw through one scratch buffer — so storing the reference\n * records a value that later frames overwrite. A test reading it back then\n * compares two frames and sees the same numbers twice, which passes.\n */\nfunction snapshot(value: unknown): unknown {\n return ArrayBuffer.isView(value) && !(value instanceof DataView)\n ? (value as unknown as { slice(): unknown }).slice()\n : value;\n}\n\n/** A WebGL2 stand-in that records every call and draws nothing. */\nexport function makeGLRecorder(options: GLRecorderOptions = {}): GLRecorder {\n const calls: GLCall[] = [];\n const {\n drawingBufferWidth = 800,\n drawingBufferHeight = 600,\n contextAttributes = { stencil: true },\n } = options;\n\n // Read as data, not called. The Proxy below hands back a recording function\n // for every lowercase property, so without this `drawingBufferHeight` is a\n // function and every y-flip computed from it is NaN — silently.\n const DATA_PROPERTIES: Readonly<Record<string, unknown>> = {\n drawingBufferWidth,\n drawingBufferHeight,\n };\n\n const handler = (name: string) => (...args: unknown[]) => {\n let result: unknown = undefined;\n switch (name) {\n case 'createShader':\n case 'createProgram':\n case 'createBuffer':\n case 'createVertexArray':\n case 'createTexture':\n case 'createFramebuffer':\n case 'createRenderbuffer':\n result = syntheticHandle(name, calls.length);\n break;\n case 'getUniformLocation':\n case 'getAttribLocation':\n result = calls.length;\n break;\n case 'getShaderParameter':\n case 'getProgramParameter':\n result = true;\n break;\n case 'getShaderInfoLog':\n case 'getProgramInfoLog':\n result = '';\n break;\n case 'getContextAttributes':\n result = { ...contextAttributes };\n break;\n case 'getError':\n result = 0;\n break;\n default:\n result = undefined;\n }\n calls.push({ name, args: args.map(snapshot), result });\n return result;\n };\n\n const gl = new Proxy(\n {},\n {\n get(_, prop: string | symbol) {\n if (typeof prop !== 'string') return undefined;\n if (prop in DATA_PROPERTIES) return DATA_PROPERTIES[prop];\n if (prop in GL_CONSTANTS) return GL_CONSTANTS[prop];\n if (/^[A-Z_0-9]+$/.test(prop)) return 0; // unknown all-caps constant → 0\n return handler(prop);\n },\n },\n ) as unknown as WebGL2RenderingContext;\n\n return {\n gl,\n calls,\n reset() {\n calls.length = 0;\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/testing/glRecorder.ts"],"names":[],"mappings":"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","file":"glRecorder.js","sourcesContent":["/**\n * Proxy-based recording WebGL2 context for unit tests.\n *\n * Records every method call as { name, args, result } so tests can assert\n * against the call sequence. Returns synthetic handles for object creation\n * (createShader/Program/Buffer/etc.); reports truthy for *Parameter status\n * queries (i.e. assumes shader/program compilation succeeded). NOT a renderer\n * correctness check: it sees the call sequence, never a pixel.\n */\n\nexport interface GLCall {\n readonly name: string;\n readonly args: readonly unknown[];\n readonly result: unknown;\n}\n\n/** What the recorded context reports about itself. */\nexport interface GLRecorderOptions {\n /** Reported as `gl.drawingBufferWidth`. Default 800. */\n drawingBufferWidth?: number;\n /** Reported as `gl.drawingBufferHeight`. Default 600. */\n drawingBufferHeight?: number;\n /** Returned by `gl.getContextAttributes()`. Default `{ stencil: true }`. */\n contextAttributes?: Partial<WebGLContextAttributes>;\n}\n\n/** A stand-in context and the calls made on it, oldest first. */\nexport interface GLRecorder {\n readonly gl: WebGL2RenderingContext;\n readonly calls: GLCall[];\n reset(): void;\n}\n\nconst GL_CONSTANTS: Readonly<Record<string, number>> = {\n // Shader / program\n VERTEX_SHADER: 0x8B31,\n FRAGMENT_SHADER: 0x8B30,\n COMPILE_STATUS: 0x8B81,\n LINK_STATUS: 0x8B82,\n // Buffer targets\n ARRAY_BUFFER: 0x8892,\n ELEMENT_ARRAY_BUFFER: 0x8893,\n STATIC_DRAW: 0x88E4,\n // Types\n FLOAT: 0x1406,\n UNSIGNED_INT: 0x1405,\n // Primitives\n TRIANGLES: 0x0004,\n // Errors / state\n NO_ERROR: 0,\n DEPTH_TEST: 0x0B71,\n BLEND: 0x0BE2,\n STENCIL_TEST: 0x0B90,\n CULL_FACE: 0x0B44,\n UNPACK_FLIP_Y_WEBGL: 0x9240,\n UNPACK_PREMULTIPLY_ALPHA_WEBGL: 0x9241,\n SCISSOR_TEST: 0x0C11,\n MAX_TEXTURE_SIZE: 0x0D33,\n // Blend factors\n SRC_ALPHA: 0x0302,\n ONE_MINUS_SRC_ALPHA: 0x0303,\n DST_ALPHA: 0x0304,\n ONE_MINUS_DST_ALPHA: 0x0305,\n ZERO: 0,\n ONE: 1,\n // Buffer bits\n COLOR_BUFFER_BIT: 0x00004000,\n STENCIL_BUFFER_BIT: 0x00000400,\n DEPTH_BUFFER_BIT: 0x00000100,\n // Stencil ops\n KEEP: 0x1E00,\n REPLACE: 0x1E01,\n INVERT: 0x150A,\n // Compare funcs\n ALWAYS: 0x0207,\n NEVER: 0x0200,\n EQUAL: 0x0202,\n NOTEQUAL: 0x0205,\n // Color masks\n FRONT_AND_BACK: 0x0408,\n // Misc constants used in step 1\n COLOR_WRITEMASK: 0x0C23,\n // Textures (step 3)\n TEXTURE_2D: 0x0DE1,\n TEXTURE0: 0x84C0,\n RGBA: 0x1908,\n UNSIGNED_BYTE: 0x1401,\n TEXTURE_MIN_FILTER: 0x2801,\n TEXTURE_MAG_FILTER: 0x2800,\n LINEAR: 0x2601,\n NEAREST: 0x2600,\n TEXTURE_WRAP_S: 0x2802,\n TEXTURE_WRAP_T: 0x2803,\n CLAMP_TO_EDGE: 0x812F,\n // Wrap modes (step 4)\n REPEAT: 0x2901,\n MIRRORED_REPEAT: 0x8370,\n // Mipmap minification filter\n LINEAR_MIPMAP_LINEAR: 0x2703,\n // Framebuffers and renderbuffers (offscreen effect passes). Every one of\n // these has to be here before a test names it: an absent ALL-CAPS property\n // reads as 0 through the proxy, so `expect(args[0]).toBe(gl.FRAMEBUFFER)`\n // would compare 0 against 0 and pass over a renderer that bound nothing.\n FRAMEBUFFER: 0x8D40,\n RENDERBUFFER: 0x8D41,\n FRAMEBUFFER_BINDING: 0x8CA6,\n RENDERBUFFER_BINDING: 0x8CA7,\n COLOR_ATTACHMENT0: 0x8CE0,\n DEPTH_STENCIL_ATTACHMENT: 0x821A,\n DEPTH24_STENCIL8: 0x88F0,\n RGBA8: 0x8058,\n FRAMEBUFFER_COMPLETE: 0x8CD5,\n};\n\nfunction syntheticHandle(name: string, seq: number): { __id: string } {\n return { __id: `${name}_${seq}` };\n}\n\n/**\n * Copy typed-array arguments as they are recorded.\n *\n * A caller is entitled to reuse the array it uploads from — the text ring\n * packs every draw through one scratch buffer — so storing the reference\n * records a value that later frames overwrite. A test reading it back then\n * compares two frames and sees the same numbers twice, which passes.\n */\nfunction snapshot(value: unknown): unknown {\n return ArrayBuffer.isView(value) && !(value instanceof DataView)\n ? (value as unknown as { slice(): unknown }).slice()\n : value;\n}\n\n/** A WebGL2 stand-in that records every call and draws nothing. */\nexport function makeGLRecorder(options: GLRecorderOptions = {}): GLRecorder {\n const calls: GLCall[] = [];\n const {\n drawingBufferWidth = 800,\n drawingBufferHeight = 600,\n contextAttributes = { stencil: true },\n } = options;\n\n // Read as data, not called. The Proxy below hands back a recording function\n // for every lowercase property, so without this `drawingBufferHeight` is a\n // function and every y-flip computed from it is NaN — silently.\n const DATA_PROPERTIES: Readonly<Record<string, unknown>> = {\n drawingBufferWidth,\n drawingBufferHeight,\n };\n\n const handler = (name: string) => (...args: unknown[]) => {\n let result: unknown = undefined;\n switch (name) {\n case 'createShader':\n case 'createProgram':\n case 'createBuffer':\n case 'createVertexArray':\n case 'createTexture':\n case 'createFramebuffer':\n case 'createRenderbuffer':\n result = syntheticHandle(name, calls.length);\n break;\n case 'getUniformLocation':\n case 'getAttribLocation':\n result = calls.length;\n break;\n case 'getShaderParameter':\n case 'getProgramParameter':\n result = true;\n break;\n case 'getShaderInfoLog':\n case 'getProgramInfoLog':\n result = '';\n break;\n case 'getContextAttributes':\n result = { ...contextAttributes };\n break;\n case 'getError':\n result = 0;\n break;\n default:\n result = undefined;\n }\n calls.push({ name, args: args.map(snapshot), result });\n return result;\n };\n\n const gl = new Proxy(\n {},\n {\n get(_, prop: string | symbol) {\n if (typeof prop !== 'string') return undefined;\n if (prop in DATA_PROPERTIES) return DATA_PROPERTIES[prop];\n if (prop in GL_CONSTANTS) return GL_CONSTANTS[prop];\n if (/^[A-Z_0-9]+$/.test(prop)) return 0; // unknown all-caps constant → 0\n return handler(prop);\n },\n },\n ) as unknown as WebGL2RenderingContext;\n\n return {\n gl,\n calls,\n reset() {\n calls.length = 0;\n },\n };\n}\n"]}
|
|
@@ -5,12 +5,10 @@ import { GLRecorder } from './glRecorder.js';
|
|
|
5
5
|
* floats-per-vertex taken from the `vertexAttribPointer` recorded for that same
|
|
6
6
|
* buffer object.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* rather than a function over one frame's calls. Ask it for `uploads()` after
|
|
13
|
-
* each frame and it carries the strides across `recorder.reset()`.
|
|
8
|
+
* A ring slot configures its attributes once, so a frame after the first
|
|
9
|
+
* records no pointer calls at all — hence a tracker that remembers what it has
|
|
10
|
+
* learned. Ask it for `uploads()` after each frame and it carries the strides
|
|
11
|
+
* across `recorder.reset()`.
|
|
14
12
|
*/
|
|
15
13
|
|
|
16
14
|
/** One vertex buffer upload. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/testing/vertexUploads.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"sources":["../../src/testing/vertexUploads.ts"],"names":[],"mappings":";AAaA,IAAM,YAAA,GAAe,KAAA;AAqBrB,SAAS,QAAQ,IAAA,EAAmC;AAClD,EAAA,MAAM,IAAA,GAAO,IAAA,CAAK,IAAA,KAAS,YAAA,GAAe,IAAA,CAAK,KAAK,CAAC,CAAA,GAAI,IAAA,CAAK,IAAA,CAAK,CAAC,CAAA;AACpE,EAAA,IAAI,EAAE,IAAA,YAAgB,YAAA,CAAA,EAAe,OAAO,IAAA;AAC5C,EAAA,IAAI,IAAA,CAAK,IAAA,KAAS,eAAA,EAAiB,OAAO,IAAA;AAC1C,EAAA,MAAM,SAAA,GAAY,OAAO,IAAA,CAAK,IAAA,CAAK,CAAC,MAAM,QAAA,GAAW,IAAA,CAAK,IAAA,CAAK,CAAC,CAAA,GAAI,CAAA;AACpE,EAAA,MAAM,MAAA,GAAS,OAAO,IAAA,CAAK,IAAA,CAAK,CAAC,CAAA,KAAM,QAAA,GAAW,IAAA,CAAK,IAAA,CAAK,CAAC,CAAA,GAAI,IAAA,CAAK,MAAA,GAAS,SAAA;AAC/E,EAAA,OAAO,IAAA,CAAK,QAAA,CAAS,SAAA,EAAW,SAAA,GAAY,MAAM,CAAA;AACpD;AAGO,SAAS,kBAAkB,QAAA,EAAqC;AAErE,EAAA,MAAM,OAAA,uBAAc,GAAA,EAAqB;AAEzC,EAAA,OAAO;AAAA,IACL,OAAA,GAA0B;AACxB,MAAA,MAAM,MAAsB,EAAC;AAC7B,MAAA,MAAM,UAAqD,EAAC;AAC5D,MAAA,IAAI,KAAA,GAAiB,IAAA;AAIrB,MAAA,KAAA,MAAW,IAAA,IAAQ,SAAS,KAAA,EAAO;AACjC,QAAA,IAAI,IAAA,CAAK,SAAS,YAAA,EAAc;AAC9B,UAAA,IAAI,IAAA,CAAK,KAAK,CAAC,CAAA,KAAM,cAAc,KAAA,GAAQ,IAAA,CAAK,KAAK,CAAC,CAAA;AACtD,UAAA;AAAA,QACF;AACA,QAAA,IAAI,IAAA,CAAK,SAAS,qBAAA,EAAuB;AAEvC,UAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,IAAA,CAAK,CAAC,CAAA;AACzB,UAAA,OAAA,CAAQ,IAAI,KAAA,EAAO,KAAA,KAAU,CAAA,GAAI,CAAA,GAAI,QAAQ,CAAC,CAAA;AAC9C,UAAA;AAAA,QACF;AACA,QAAA,IAAI,IAAA,CAAK,IAAA,KAAS,YAAA,IAAgB,IAAA,CAAK,SAAS,eAAA,EAAiB;AACjE,QAAA,MAAM,IAAA,GAAO,QAAQ,IAAI,CAAA;AACzB,QAAA,IAAI,MAAM,OAAA,CAAQ,IAAA,CAAK,EAAE,MAAA,EAAQ,KAAA,EAAO,MAAM,CAAA;AAAA,MAChD;AAEA,MAAA,KAAA,MAAW,EAAE,MAAA,EAAQ,IAAA,EAAK,IAAK,OAAA,EAAS;AACtC,QAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,GAAA,CAAI,MAAM,CAAA;AACjC,QAAA,IAAI,WAAW,MAAA,EAAW;AACxB,UAAA,MAAM,IAAI,KAAA;AAAA,YACR;AAAA,WAGF;AAAA,QACF;AACA,QAAA,GAAA,CAAI,IAAA,CAAK,EAAE,MAAA,EAAQ,IAAA,EAAM,CAAA;AAAA,MAC3B;AACA,MAAA,OAAO,GAAA;AAAA,IACT;AAAA,GACF;AACF","file":"vertexUploads.js","sourcesContent":["/**\n * Read vertex geometry back out of a `GLRecorder`, with each upload's\n * floats-per-vertex taken from the `vertexAttribPointer` recorded for that same\n * buffer object.\n *\n * A ring slot configures its attributes once, so a frame after the first\n * records no pointer calls at all — hence a tracker that remembers what it has\n * learned. Ask it for `uploads()` after each frame and it carries the strides\n * across `recorder.reset()`.\n */\n\nimport type { GLCall, GLRecorder } from './glRecorder';\n\nconst ARRAY_BUFFER = 0x8892;\n\n/** One vertex buffer upload. */\nexport interface VertexUpload {\n /** Floats per vertex. */\n stride: number;\n data: Float32Array;\n}\n\n/** Reads vertex uploads out of a recorder, remembering each buffer's stride. */\nexport interface UploadTracker {\n /** Every vertex upload recorded since the last `recorder.reset()`. */\n uploads(): VertexUpload[];\n}\n\n/**\n * The floats an upload actually sent. `bufferSubData(target, offset, src,\n * srcOffset, length)` hands GL a *window* into a scratch array the batch\n * reuses across frames, so reading the recorded argument whole compares live\n * vertices against stale ones.\n */\nfunction payload(call: GLCall): Float32Array | null {\n const data = call.name === 'bufferData' ? call.args[1] : call.args[2];\n if (!(data instanceof Float32Array)) return null;\n if (call.name !== 'bufferSubData') return data;\n const srcOffset = typeof call.args[3] === 'number' ? call.args[3] : 0;\n const length = typeof call.args[4] === 'number' ? call.args[4] : data.length - srcOffset;\n return data.subarray(srcOffset, srcOffset + length);\n}\n\n/** A tracker over `recorder`; keep one across frames. */\nexport function makeUploadTracker(recorder: GLRecorder): UploadTracker {\n // Keyed on the recorder's synthetic buffer handle, which is a stable object.\n const strides = new Map<unknown, number>();\n\n return {\n uploads(): VertexUpload[] {\n const out: VertexUpload[] = [];\n const pending: { buffer: unknown; data: Float32Array }[] = [];\n let bound: unknown = null;\n\n // Two passes: a buffer's attribute layout can be recorded either before\n // its first upload (the ring) or after it (every other draw path).\n for (const call of recorder.calls) {\n if (call.name === 'bindBuffer') {\n if (call.args[0] === ARRAY_BUFFER) bound = call.args[1];\n continue;\n }\n if (call.name === 'vertexAttribPointer') {\n // Byte stride; 0 means tightly packed, which for a_position is a vec2.\n const bytes = call.args[4] as number;\n strides.set(bound, bytes === 0 ? 2 : bytes / 4);\n continue;\n }\n if (call.name !== 'bufferData' && call.name !== 'bufferSubData') continue;\n const data = payload(call);\n if (data) pending.push({ buffer: bound, data });\n }\n\n for (const { buffer, data } of pending) {\n const stride = strides.get(buffer);\n if (stride === undefined) {\n throw new Error(\n 'vertexUploads: a vertex buffer was written with no vertexAttribPointer ' +\n 'ever recorded for it — call uploads() on the frame that creates the ' +\n 'buffer, before resetting the recorder.',\n );\n }\n out.push({ stride, data });\n }\n return out;\n },\n };\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weasel-js/renderer",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.1",
|
|
4
4
|
"description": "A WebGL2 renderer for draw commands: batched path fills and strokes, MSDF and outline text, images and sprites, gradients, patterns, registered shader programs, and full-screen effects. No scene graph, no React.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -36,14 +36,14 @@
|
|
|
36
36
|
"./package.json": "./package.json"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@weasel-js/geom": "1.
|
|
40
|
-
"@weasel-js/paint": "1.
|
|
41
|
-
"@weasel-js/registry": "1.
|
|
42
|
-
"@weasel-js/stroke": "1.
|
|
43
|
-
"@weasel-js/text": "1.
|
|
39
|
+
"@weasel-js/geom": "1.10.1",
|
|
40
|
+
"@weasel-js/paint": "1.10.1",
|
|
41
|
+
"@weasel-js/registry": "1.10.1",
|
|
42
|
+
"@weasel-js/stroke": "1.10.1",
|
|
43
|
+
"@weasel-js/text": "1.10.1"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
|
-
"@weasel-js/font": "1.
|
|
46
|
+
"@weasel-js/font": "1.10.1"
|
|
47
47
|
},
|
|
48
48
|
"author": "orochi235",
|
|
49
49
|
"homepage": "https://orochi235.github.io/weasel/",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/patterns/index.ts"],"names":[],"mappings":";;;AAiCO,SAAS,kBAAkB,IAAA,EAA6C;AAC7E,EAAA,MAAM,MAAA,GAAS,cAAc,IAAI,CAAA;AACjC,EAAA,OAAO,MAAA,GAAS,eAAA,CAAgB,MAAM,CAAA,GAAI,IAAA;AAC5C","file":"chunk-2VOBNWBN.js","sourcesContent":["/**\n * Tile-pattern primitive — a small helper for building repeating texture\n * tiles from a draw callback. The result plugs into a `FillStyle` of\n * `{ fill: 'pattern', pattern }` (see `@weasel-js/paint`).\n *\n * GL-backed: the tile is rendered to an `OffscreenCanvas` (with a 2D fallback\n * to a regular `<canvas>` for environments without `OffscreenCanvas`),\n * converted to an `ImageBitmap`, and registered with `registerTexture()`.\n * The returned `TextureHandle` is what `FillStyle.pattern` accepts.\n *\n * For ready-to-use named patterns (hatch, crosshatch, dots, chunks),\n * see the `@weasel-js/renderer/patterns-builtin` subpath.\n */\n\nimport { registerTexture } from '../textures/registerTexture';\nimport type { TextureHandle } from '../textures/registerTexture';\nimport { rasterizeTile } from './rasterizeTile';\n\n/** Options for `createTilePattern`. */\nexport interface TilePatternOpts {\n /** Edge length (in pixels) of the square offscreen tile. */\n size: number;\n /** FillStyle one tile at integer-pixel coordinates `(0, 0) .. (size, size)`. */\n draw: (oc: CanvasRenderingContext2D, size: number) => void;\n}\n\n/**\n * Render a single tile to an offscreen canvas and register it as a GL\n * texture, returning a `TextureHandle` you can wrap in a `FillStyle` of\n * `{ fill: 'pattern', pattern }`. Returns `null` only when an\n * `OffscreenCanvas`/2D context can't be acquired (very rare; same fallback\n * semantics as the prior `CanvasPattern`-based implementation).\n */\nexport function createTilePattern(opts: TilePatternOpts): TextureHandle | null {\n const bitmap = rasterizeTile(opts);\n return bitmap ? registerTexture(bitmap) : null;\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/textures/registerTexture.ts","../src/patterns/tileGeometry.ts","../src/patterns/rasterizeTile.ts"],"names":[],"mappings":"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","file":"chunk-ORBXHIRB.js","sourcesContent":["/**\n * registerTexture — accepts an image source, assigns an opaque id, stores in\n * a module-level registry. Actual GL upload happens lazily at draw time in\n * drawShader() via GLTextureCache.upload (which is idempotent).\n *\n * Lifecycle: a texture lives until `unregisterTexture`. Each renderer frees\n * its own GL copy at the top of its next frame.\n *\n * Convention §9: this registry stores image data only — no per-renderer state.\n * Each WeaselRenderer's GLTextureCache does its own dedup by id and source.\n */\n\nimport type { TextureHandle } from '@weasel-js/paint';\nimport { createReflectable, type Reflection } from '@weasel-js/registry';\n\nexport type { TextureHandle };\n\n/** A registered texture's image source, uploaded to GL lazily at first draw. */\nexport interface TextureEntry {\n source: HTMLImageElement | ImageBitmap;\n}\n\nlet counter = 0;\nconst registry = createReflectable<TextureEntry>();\n\n/** Every registered texture, keyed by its handle id. */\nexport const textureRegistry: Reflection<TextureEntry> = registry.reflection;\n\n/** @internal Test helper — do not call from product code. */\nexport function _resetTextureRegistryForTests(): void {\n registry.clear();\n counter = 0;\n}\n\n/**\n * Whoever mints the ids under one prefix and can rebuild any of them.\n * @internal\n */\nexport interface TextureIdOwner {\n /** Register `id` again if it can be rebuilt; do nothing otherwise. */\n revive(id: string): void;\n /** `id` was looked up and found: something is about to draw it. */\n touched?(id: string): void;\n}\n\nconst owners: { prefix: string; owner: TextureIdOwner }[] = [];\n\n/**\n * Claim every texture id starting with `prefix`. Returns the release.\n * @internal\n */\nexport function ownTextureIds(prefix: string, owner: TextureIdOwner): () => void {\n const claim = { prefix, owner };\n owners.push(claim);\n return () => {\n const i = owners.indexOf(claim);\n if (i !== -1) owners.splice(i, 1);\n };\n}\n\nfunction ownerOf(id: string): TextureIdOwner | undefined {\n for (let i = 0; i < owners.length; i++) {\n if (id.startsWith(owners[i].prefix)) return owners[i].owner;\n }\n return undefined;\n}\n\n/**\n * The entry for `id`, as a draw looks it up. An owned id that was\n * unregistered is rebuilt by its owner first, so a handle held past its\n * texture's eviction still draws.\n */\nexport function getTexture(id: string): TextureEntry | null {\n const owner = owners.length > 0 ? ownerOf(id) : undefined;\n const hit = registry.get(id);\n if (hit) {\n owner?.touched?.(id);\n return hit;\n }\n if (!owner) return null;\n owner.revive(id);\n return registry.get(id) ?? null;\n}\n\n/**\n * Register an image for use as a shader texture uniform.\n *\n * Returns a TextureHandle whose `id` can be passed as a ShaderUniform value.\n * The handle is valid for the lifetime of the renderer it will be used with.\n *\n * @param image The image to register. HTMLImageElement must be fully loaded.\n * @returns Opaque TextureHandle.\n */\nexport function registerTexture(\n image: HTMLImageElement | ImageBitmap,\n): TextureHandle {\n const id = `tex_${++counter}`;\n registry.set(id, { source: image });\n return { id };\n}\n\n/**\n * Register an image under an id its caller chose.\n * @internal\n */\nexport function registerTextureAs(\n id: string,\n image: HTMLImageElement | ImageBitmap,\n): TextureHandle {\n registry.set(id, { source: image });\n return { id };\n}\n\n/**\n * Drop a registered texture. Every renderer that uploaded it frees its GL\n * copy at the top of its next frame; a draw command still holding the handle\n * then draws nothing and warns. The image itself is the caller's to release.\n *\n * @returns Whether the handle was registered.\n */\nexport function unregisterTexture(handle: TextureHandle): boolean {\n return registry.delete(handle.id);\n}\n","/**\n * The shapes a built-in tile is made of, as data.\n *\n * One description, two renderers: `patterns-builtin` strokes/fills these to\n * an `OffscreenCanvas` to build the GL texture, and `@weasel-js/svg` maps\n * them to `<pattern>` children. Duplicating the tile drawing in the\n * serializer would let the raster and vector forms drift apart silently.\n */\n\nimport type { TilePatternSpec } from '@weasel-js/paint';\n\n/** One primitive within a pattern tile, in tile-local coordinates. */\nexport type TileShape =\n | { kind: 'line'; x1: number; y1: number; x2: number; y2: number; width: number; color: string }\n | { kind: 'circle'; cx: number; cy: number; r: number; color: string }\n | { kind: 'ellipse'; cx: number; cy: number; rx: number; ry: number; rotation: number; color: string }\n | { kind: 'rect'; x: number; y: number; width: number; height: number; color: string };\n\n/** A built-in tile's shapes plus the edge length they're drawn within. */\nexport interface TileGeometry {\n size: number;\n shapes: TileShape[];\n}\n\n/** Defaults per tile kind, matching each factory's signature. */\nexport function tileSize(spec: TilePatternSpec): number {\n if (spec.size !== undefined) return spec.size;\n switch (spec.tile) {\n case 'hatch': return 5;\n case 'crosshatch': return 6;\n case 'dots': return 6;\n case 'chunks': return 48;\n default: return 8;\n }\n}\n\n/** Seeded PRNG (mulberry32) — `chunks` places its scatter with this, so the\n * same `seed` yields the same tile in both the raster and vector paths. */\nfunction mulberry32(seed: number): () => number {\n let s = seed | 0;\n return () => {\n s = (s + 0x6D2B79F5) | 0;\n let t = Math.imul(s ^ (s >>> 15), 1 | s);\n t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n };\n}\n\n/** Resolve a pattern spec to the shapes that make up one tile. Consumers that\n * want to draw a pattern swatch themselves — a picker, an SVG export — read\n * this rather than rasterizing a texture. */\nexport function tileGeometry(spec: TilePatternSpec): TileGeometry {\n const s = tileSize(spec);\n const { color } = spec;\n\n switch (spec.tile) {\n case 'hatch': {\n const width = spec.lineWidth ?? 1;\n return { size: s, shapes: forwardDiagonals(s, width, color) };\n }\n case 'crosshatch': {\n const width = spec.lineWidth ?? 0.8;\n return {\n size: s,\n shapes: [...forwardDiagonals(s, width, color), ...backwardDiagonals(s, width, color)],\n };\n }\n case 'dots': {\n const r = spec.radius ?? 1;\n return { size: s, shapes: [{ kind: 'circle', cx: s / 2, cy: s / 2, r, color }] };\n }\n case 'chunks': {\n const density = spec.density ?? 0.35;\n const chunkSize = spec.chunkSize ?? 3;\n const rand = mulberry32(spec.seed ?? 42);\n const count = Math.round((s * s * density) / (chunkSize * chunkSize));\n const shapes: TileShape[] = [];\n if (spec.bg !== undefined) {\n shapes.push({ kind: 'rect', x: 0, y: 0, width: s, height: s, color: spec.bg });\n }\n for (let i = 0; i < count; i++) {\n const cx = rand() * s;\n const cy = rand() * s;\n const w = chunkSize * (0.5 + rand());\n const h = chunkSize * (0.5 + rand());\n const rotation = rand() * Math.PI;\n shapes.push({ kind: 'ellipse', cx, cy, rx: w / 2, ry: h / 2, rotation, color });\n }\n return { size: s, shapes };\n }\n default:\n return { size: s, shapes: [] };\n }\n}\n\n/** The forward (`/`) diagonal, plus the two corner stubs that make it tile\n * seamlessly across the edges. */\nfunction forwardDiagonals(s: number, width: number, color: string): TileShape[] {\n return [\n { kind: 'line', x1: -1, y1: s + 1, x2: s + 1, y2: -1, width, color },\n { kind: 'line', x1: -1, y1: 1, x2: 1, y2: -1, width, color },\n { kind: 'line', x1: s - 1, y1: s + 1, x2: s + 1, y2: s - 1, width, color },\n ];\n}\n\n/** The backward (`\\`) diagonal and its two stubs. */\nfunction backwardDiagonals(s: number, width: number, color: string): TileShape[] {\n return [\n { kind: 'line', x1: -1, y1: -1, x2: s + 1, y2: s + 1, width, color },\n { kind: 'line', x1: s - 1, y1: -1, x2: s + 1, y2: 1, width, color },\n { kind: 'line', x1: -1, y1: s - 1, x2: 1, y2: s + 1, width, color },\n ];\n}\n\n/** Draw a tile's shapes to a 2D context — the raster half of the pair. */\nexport function drawTileShapes(oc: CanvasRenderingContext2D, geometry: TileGeometry): void {\n for (const shape of geometry.shapes) {\n if (shape.kind === 'line') {\n oc.strokeStyle = shape.color;\n oc.lineWidth = shape.width;\n oc.beginPath();\n oc.moveTo(shape.x1, shape.y1);\n oc.lineTo(shape.x2, shape.y2);\n oc.stroke();\n continue;\n }\n oc.fillStyle = shape.color;\n if (shape.kind === 'rect') {\n oc.fillRect(shape.x, shape.y, shape.width, shape.height);\n continue;\n }\n oc.beginPath();\n if (shape.kind === 'circle') {\n oc.arc(shape.cx, shape.cy, shape.r, 0, Math.PI * 2);\n } else {\n oc.ellipse(shape.cx, shape.cy, shape.rx, shape.ry, shape.rotation, 0, Math.PI * 2);\n }\n oc.fill();\n }\n}\n","import type { TilePatternOpts } from './';\n\n/** Draw one tile and hand back its pixels, or `null` where no\n * `OffscreenCanvas` 2D context can be had. */\nexport function rasterizeTile(opts: TilePatternOpts): ImageBitmap | null {\n // Prefer OffscreenCanvas; fall back to <canvas> for environments without it.\n let off: OffscreenCanvas | HTMLCanvasElement;\n try {\n off = new OffscreenCanvas(opts.size, opts.size);\n } catch {\n if (typeof document === 'undefined') return null;\n const c = document.createElement('canvas');\n c.width = opts.size;\n c.height = opts.size;\n off = c;\n }\n const oc = off.getContext('2d') as CanvasRenderingContext2D | null;\n if (!oc) return null;\n opts.draw(oc, opts.size);\n\n // Only an OffscreenCanvas can hand over a bitmap synchronously.\n if (typeof (off as OffscreenCanvas).transferToImageBitmap !== 'function') return null;\n return (off as OffscreenCanvas).transferToImageBitmap();\n}\n"]}
|