vireglass 2.1.0 → 2.3.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.
Files changed (46) hide show
  1. package/README.md +41 -1
  2. package/dist/{chunk-3PGYF2NK.js → chunk-CTTZNZFV.js} +3 -3
  3. package/dist/{chunk-3PGYF2NK.js.map → chunk-CTTZNZFV.js.map} +1 -1
  4. package/dist/{chunk-FL5UZPY5.js → chunk-H5QTBF67.js} +4 -4
  5. package/dist/{chunk-FL5UZPY5.js.map → chunk-H5QTBF67.js.map} +1 -1
  6. package/dist/{chunk-TTOKBARR.js → chunk-MMPK6DCK.js} +10 -3
  7. package/dist/chunk-MMPK6DCK.js.map +1 -0
  8. package/dist/{chunk-BZC2WHCK.js → chunk-PM2SWCSF.js} +4 -4
  9. package/dist/{chunk-BZC2WHCK.js.map → chunk-PM2SWCSF.js.map} +1 -1
  10. package/dist/{chunk-P2ZOODXD.js → chunk-SLRPHDV7.js} +3 -3
  11. package/dist/{chunk-P2ZOODXD.js.map → chunk-SLRPHDV7.js.map} +1 -1
  12. package/dist/dom.cjs +48 -20
  13. package/dist/dom.cjs.map +1 -1
  14. package/dist/dom.d.cts +15 -3
  15. package/dist/dom.d.ts +15 -3
  16. package/dist/dom.js +44 -23
  17. package/dist/dom.js.map +1 -1
  18. package/dist/index.cjs +1 -2
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.d.cts +4 -0
  21. package/dist/index.d.ts +4 -0
  22. package/dist/index.js +5 -5
  23. package/dist/law.cjs +8 -1
  24. package/dist/law.cjs.map +1 -1
  25. package/dist/law.d.cts +7 -0
  26. package/dist/law.d.ts +7 -0
  27. package/dist/law.js +1 -1
  28. package/dist/reference.js +2 -2
  29. package/dist/web/index.cjs +262 -153
  30. package/dist/web/index.cjs.map +1 -1
  31. package/dist/web/index.d.cts +88 -2
  32. package/dist/web/index.d.ts +88 -2
  33. package/dist/web/index.js +257 -156
  34. package/dist/web/index.js.map +1 -1
  35. package/docs/law.md +2 -2
  36. package/package.json +13 -9
  37. package/src/dom/body.ts +24 -9
  38. package/src/dom/index.ts +44 -10
  39. package/src/dom/probe.ts +27 -3
  40. package/src/law.ts +7 -0
  41. package/src/targets/glsl.ts +4 -0
  42. package/src/web/gl.ts +57 -4
  43. package/src/web/gpu-timer.ts +111 -0
  44. package/src/web/index.ts +1 -0
  45. package/src/web/renderer.ts +305 -218
  46. package/dist/chunk-TTOKBARR.js.map +0 -1
@@ -43,6 +43,42 @@ type VireGlassSceneDrawer = (ctx: CanvasRenderingContext2D, width: number, heigh
43
43
  * on a phone, and an element of the same size looked completely different on the lab bench
44
44
  * than on a device. */
45
45
  density: number) => void;
46
+ /** The target a `VireGlassBackdropPass` must finish its frame in — the FBO wraps `contentTexture`
47
+ * itself, so drawing into it needs no readback: the very texture the lens samples gets the pixels
48
+ * directly. */
49
+ type VireGlassBackdropTarget = {
50
+ /** Owned by the renderer — the FBO wrapping `contentTexture` itself, recreated on `resize` and
51
+ * freed in `destroy`. The pass must not delete it, and must not hold onto it past the call:
52
+ * the object may be gone or pointing at a different texture by the next `render()`. */
53
+ framebuffer: WebGLFramebuffer;
54
+ width: number;
55
+ height: number;
56
+ };
57
+ /**
58
+ * Alternative to `scene`: draws the backdrop with a GPU pass, in this same WebGL2 context, straight
59
+ * into the texture the lens samples — no offscreen 2D canvas, no `texSubImage2D` readback. The
60
+ * renderer doesn't know or care what's drawn; a fluid sim, a video frame, anything that ends up as
61
+ * pixels.
62
+ *
63
+ * ORIENTATION CONTRACT. `contentTexture` carries the same layout the `scene` path has always
64
+ * produced: uploaded from a 2D canvas without a Y flip, so texel row 0 (`v≈0`) is the scene's TOP
65
+ * row (see `BLIT_FRAGMENT_SOURCE` below) — and that row sits at `gl_FragCoord.y = 0` in this FBO,
66
+ * the window-coordinate BOTTOM, not the top. A pass that copies an ordinary top-row-first image
67
+ * (one loaded the normal way, via `texImage2D` from a canvas or an `<img>`) straight across with
68
+ * `texture(src, gl_FragCoord.xy / vec2(target.width, target.height))` reproduces it correctly,
69
+ * with no flip of its own. A pass that instead treats INCREASING y as its own "up" — the natural
70
+ * convention for a physical simulation — must flip before this final write, or its top ends up
71
+ * stored as the scene's bottom and appears upside down once the lens samples it.
72
+ *
73
+ * The pass may render through as many of its own FBOs first as it likes (a multi-step simulation)
74
+ * and leave the GL context in whatever state suits it; it must finish by drawing into
75
+ * `target.framebuffer` at `viewport(0, 0, target.width, target.height)`. The renderer puts back
76
+ * every piece of state its own remaining steps depend on right after — framebuffer, viewport,
77
+ * blend, scissor, depth/stencil/cull, the vertex array binding, the active texture unit, and the
78
+ * `UNPACK_*` pixel-store flags used to upload `iconMask`/`colorLayer` — so the pass is free to
79
+ * change any of it without arranging its own cleanup.
80
+ */
81
+ type VireGlassBackdropPass = (gl: WebGL2RenderingContext, target: VireGlassBackdropTarget) => void;
46
82
  /** One glass element in the frame. Each has its own material — that's how they're compared side by side. */
47
83
  type VireGlassPiece = {
48
84
  optics: VireGlassOptics;
@@ -83,7 +119,14 @@ type VireGlassRenderOptions = {
83
119
  density: number;
84
120
  debug: VireGlassDebugMode;
85
121
  pieces: readonly VireGlassPiece[];
86
- scene: VireGlassSceneDrawer;
122
+ /** CPU-side backdrop: draws into an offscreen 2D canvas, uploaded via `texSubImage2D`. Exactly
123
+ * one of `scene`/`backdrop` is required — `render()` throws if both or neither are given — see
124
+ * `backdrop` for the GPU alternative. */
125
+ scene?: VireGlassSceneDrawer;
126
+ /** GPU-side backdrop: draws straight into the texture the lens samples, in this context, no
127
+ * readback. Exactly one of `scene`/`backdrop` is required — see `VireGlassBackdropPass` for the
128
+ * orientation contract it must honor. */
129
+ backdrop?: VireGlassBackdropPass | null;
87
130
  /** Canvas offset under stationary elements, device-px. */
88
131
  offsetX?: number;
89
132
  offsetY?: number;
@@ -116,8 +159,51 @@ type VireGlassRenderResult = {
116
159
  type VireGlassRenderer = {
117
160
  resize(widthPx: number, heightPx: number): void;
118
161
  render(options: VireGlassRenderOptions): VireGlassRenderResult;
162
+ /** GPU time of the last `render()` call, milliseconds, via
163
+ * `EXT_disjoint_timer_query_webgl2`. `null` when the extension is unavailable or the sample
164
+ * came back disjoint — never an error either way. */
165
+ getLastGpuMs(): number | null;
119
166
  destroy(): void;
120
167
  };
121
168
  declare function createVireGlassRenderer(canvas: HTMLCanvasElement): VireGlassRenderer;
122
169
 
123
- export { PROBE_GRID_HEIGHT, PROBE_GRID_WIDTH, type Probe, type ProbeRect, type ProbeStats, type VireGlassPiece, type VireGlassRenderOptions, type VireGlassRenderResult, type VireGlassRenderer, type VireGlassSceneDrawer, createProbe, createVireGlassRenderer };
170
+ /** The same trick as in `scripts/check-glsl.mjs`: three vertices with no buffer, indexed by
171
+ * `gl_VertexID`. One vertex shader works for EVERY pass of the pipeline, because each fragment
172
+ * shader pulls its own coordinate from `gl_FragCoord`. */
173
+ declare const FULLSCREEN_TRIANGLE_VERTEX_SOURCE = "#version 300 es\nconst vec2 VG_POS[3] = vec2[3](vec2(-1.0, -1.0), vec2(3.0, -1.0), vec2(-1.0, 3.0));\nvoid main() {\n gl_Position = vec4(VG_POS[gl_VertexID], 0.0, 1.0);\n}\n";
174
+ declare function compileShader(gl: WebGL2RenderingContext, type: number, source: string): WebGLShader;
175
+ declare function createProgram(gl: WebGL2RenderingContext, vertexSource: string, fragmentSource: string): WebGLProgram;
176
+ /** Draws the fullscreen triangle with the current program. No attributes — the vertices are
177
+ * baked into the shader, so even a VAO isn't needed. */
178
+ declare function drawFullscreenTriangle(gl: WebGL2RenderingContext): void;
179
+ type TextureOptions = {
180
+ width: number;
181
+ height: number;
182
+ /** RGBA8 by default; the probe needs float when it needs more than 8 bits of precision. */
183
+ internalFormat?: number;
184
+ format?: number;
185
+ type?: number;
186
+ /** CLAMP_TO_EDGE by default. A simulation with a periodic domain (a tiling pattern, a torus)
187
+ * wants REPEAT on its own textures instead — applied to both S and T, there's no case here
188
+ * that wants them to differ. */
189
+ wrap?: number;
190
+ };
191
+ /**
192
+ * General-purpose texture allocator: LINEAR on MIN and MAG, no mipmaps, CLAMP_TO_EDGE by default.
193
+ * Used throughout `web/` for `contentTexture` and the probe's downsample buffer, and public so a
194
+ * `VireGlassBackdropPass` can build its own textures the same way. LINEAR rather than NEAREST
195
+ * because Skia's `.eval` is bilinear by default — NEAREST would break the disc gather into
196
+ * pixel-stepped bands (spec §4); a pass with different needs overrides `internalFormat`/`format`/
197
+ * `type`/`wrap` directly.
198
+ */
199
+ declare function createTexture(gl: WebGL2RenderingContext, options: TextureOptions): WebGLTexture;
200
+ type UniformValue = number | readonly number[];
201
+ /** One cache per program: `getUniformLocation` is a name lookup, and every piece re-sets every
202
+ * uniform every frame. */
203
+ declare function locationCache(gl: WebGL2RenderingContext, program: WebGLProgram): (name: string) => WebGLUniformLocation | null;
204
+ declare function setUniform(gl: WebGL2RenderingContext, loc: WebGLUniformLocation | null, value: UniformValue): void;
205
+ declare function createFramebuffer(gl: WebGL2RenderingContext, texture: WebGLTexture): WebGLFramebuffer;
206
+ /** The one texture unit any pipeline pass needs here — a bind with no extra slot bookkeeping. */
207
+ declare function bindTextureAt(gl: WebGL2RenderingContext, unit: number, texture: WebGLTexture, program: WebGLProgram, uniformName: string): void;
208
+
209
+ export { FULLSCREEN_TRIANGLE_VERTEX_SOURCE, PROBE_GRID_HEIGHT, PROBE_GRID_WIDTH, type Probe, type ProbeRect, type ProbeStats, type TextureOptions, type UniformValue, type VireGlassBackdropPass, type VireGlassBackdropTarget, type VireGlassPiece, type VireGlassRenderOptions, type VireGlassRenderResult, type VireGlassRenderer, type VireGlassSceneDrawer, bindTextureAt, compileShader, createFramebuffer, createProbe, createProgram, createTexture, createVireGlassRenderer, drawFullscreenTriangle, locationCache, setUniform };
@@ -43,6 +43,42 @@ type VireGlassSceneDrawer = (ctx: CanvasRenderingContext2D, width: number, heigh
43
43
  * on a phone, and an element of the same size looked completely different on the lab bench
44
44
  * than on a device. */
45
45
  density: number) => void;
46
+ /** The target a `VireGlassBackdropPass` must finish its frame in — the FBO wraps `contentTexture`
47
+ * itself, so drawing into it needs no readback: the very texture the lens samples gets the pixels
48
+ * directly. */
49
+ type VireGlassBackdropTarget = {
50
+ /** Owned by the renderer — the FBO wrapping `contentTexture` itself, recreated on `resize` and
51
+ * freed in `destroy`. The pass must not delete it, and must not hold onto it past the call:
52
+ * the object may be gone or pointing at a different texture by the next `render()`. */
53
+ framebuffer: WebGLFramebuffer;
54
+ width: number;
55
+ height: number;
56
+ };
57
+ /**
58
+ * Alternative to `scene`: draws the backdrop with a GPU pass, in this same WebGL2 context, straight
59
+ * into the texture the lens samples — no offscreen 2D canvas, no `texSubImage2D` readback. The
60
+ * renderer doesn't know or care what's drawn; a fluid sim, a video frame, anything that ends up as
61
+ * pixels.
62
+ *
63
+ * ORIENTATION CONTRACT. `contentTexture` carries the same layout the `scene` path has always
64
+ * produced: uploaded from a 2D canvas without a Y flip, so texel row 0 (`v≈0`) is the scene's TOP
65
+ * row (see `BLIT_FRAGMENT_SOURCE` below) — and that row sits at `gl_FragCoord.y = 0` in this FBO,
66
+ * the window-coordinate BOTTOM, not the top. A pass that copies an ordinary top-row-first image
67
+ * (one loaded the normal way, via `texImage2D` from a canvas or an `<img>`) straight across with
68
+ * `texture(src, gl_FragCoord.xy / vec2(target.width, target.height))` reproduces it correctly,
69
+ * with no flip of its own. A pass that instead treats INCREASING y as its own "up" — the natural
70
+ * convention for a physical simulation — must flip before this final write, or its top ends up
71
+ * stored as the scene's bottom and appears upside down once the lens samples it.
72
+ *
73
+ * The pass may render through as many of its own FBOs first as it likes (a multi-step simulation)
74
+ * and leave the GL context in whatever state suits it; it must finish by drawing into
75
+ * `target.framebuffer` at `viewport(0, 0, target.width, target.height)`. The renderer puts back
76
+ * every piece of state its own remaining steps depend on right after — framebuffer, viewport,
77
+ * blend, scissor, depth/stencil/cull, the vertex array binding, the active texture unit, and the
78
+ * `UNPACK_*` pixel-store flags used to upload `iconMask`/`colorLayer` — so the pass is free to
79
+ * change any of it without arranging its own cleanup.
80
+ */
81
+ type VireGlassBackdropPass = (gl: WebGL2RenderingContext, target: VireGlassBackdropTarget) => void;
46
82
  /** One glass element in the frame. Each has its own material — that's how they're compared side by side. */
47
83
  type VireGlassPiece = {
48
84
  optics: VireGlassOptics;
@@ -83,7 +119,14 @@ type VireGlassRenderOptions = {
83
119
  density: number;
84
120
  debug: VireGlassDebugMode;
85
121
  pieces: readonly VireGlassPiece[];
86
- scene: VireGlassSceneDrawer;
122
+ /** CPU-side backdrop: draws into an offscreen 2D canvas, uploaded via `texSubImage2D`. Exactly
123
+ * one of `scene`/`backdrop` is required — `render()` throws if both or neither are given — see
124
+ * `backdrop` for the GPU alternative. */
125
+ scene?: VireGlassSceneDrawer;
126
+ /** GPU-side backdrop: draws straight into the texture the lens samples, in this context, no
127
+ * readback. Exactly one of `scene`/`backdrop` is required — see `VireGlassBackdropPass` for the
128
+ * orientation contract it must honor. */
129
+ backdrop?: VireGlassBackdropPass | null;
87
130
  /** Canvas offset under stationary elements, device-px. */
88
131
  offsetX?: number;
89
132
  offsetY?: number;
@@ -116,8 +159,51 @@ type VireGlassRenderResult = {
116
159
  type VireGlassRenderer = {
117
160
  resize(widthPx: number, heightPx: number): void;
118
161
  render(options: VireGlassRenderOptions): VireGlassRenderResult;
162
+ /** GPU time of the last `render()` call, milliseconds, via
163
+ * `EXT_disjoint_timer_query_webgl2`. `null` when the extension is unavailable or the sample
164
+ * came back disjoint — never an error either way. */
165
+ getLastGpuMs(): number | null;
119
166
  destroy(): void;
120
167
  };
121
168
  declare function createVireGlassRenderer(canvas: HTMLCanvasElement): VireGlassRenderer;
122
169
 
123
- export { PROBE_GRID_HEIGHT, PROBE_GRID_WIDTH, type Probe, type ProbeRect, type ProbeStats, type VireGlassPiece, type VireGlassRenderOptions, type VireGlassRenderResult, type VireGlassRenderer, type VireGlassSceneDrawer, createProbe, createVireGlassRenderer };
170
+ /** The same trick as in `scripts/check-glsl.mjs`: three vertices with no buffer, indexed by
171
+ * `gl_VertexID`. One vertex shader works for EVERY pass of the pipeline, because each fragment
172
+ * shader pulls its own coordinate from `gl_FragCoord`. */
173
+ declare const FULLSCREEN_TRIANGLE_VERTEX_SOURCE = "#version 300 es\nconst vec2 VG_POS[3] = vec2[3](vec2(-1.0, -1.0), vec2(3.0, -1.0), vec2(-1.0, 3.0));\nvoid main() {\n gl_Position = vec4(VG_POS[gl_VertexID], 0.0, 1.0);\n}\n";
174
+ declare function compileShader(gl: WebGL2RenderingContext, type: number, source: string): WebGLShader;
175
+ declare function createProgram(gl: WebGL2RenderingContext, vertexSource: string, fragmentSource: string): WebGLProgram;
176
+ /** Draws the fullscreen triangle with the current program. No attributes — the vertices are
177
+ * baked into the shader, so even a VAO isn't needed. */
178
+ declare function drawFullscreenTriangle(gl: WebGL2RenderingContext): void;
179
+ type TextureOptions = {
180
+ width: number;
181
+ height: number;
182
+ /** RGBA8 by default; the probe needs float when it needs more than 8 bits of precision. */
183
+ internalFormat?: number;
184
+ format?: number;
185
+ type?: number;
186
+ /** CLAMP_TO_EDGE by default. A simulation with a periodic domain (a tiling pattern, a torus)
187
+ * wants REPEAT on its own textures instead — applied to both S and T, there's no case here
188
+ * that wants them to differ. */
189
+ wrap?: number;
190
+ };
191
+ /**
192
+ * General-purpose texture allocator: LINEAR on MIN and MAG, no mipmaps, CLAMP_TO_EDGE by default.
193
+ * Used throughout `web/` for `contentTexture` and the probe's downsample buffer, and public so a
194
+ * `VireGlassBackdropPass` can build its own textures the same way. LINEAR rather than NEAREST
195
+ * because Skia's `.eval` is bilinear by default — NEAREST would break the disc gather into
196
+ * pixel-stepped bands (spec §4); a pass with different needs overrides `internalFormat`/`format`/
197
+ * `type`/`wrap` directly.
198
+ */
199
+ declare function createTexture(gl: WebGL2RenderingContext, options: TextureOptions): WebGLTexture;
200
+ type UniformValue = number | readonly number[];
201
+ /** One cache per program: `getUniformLocation` is a name lookup, and every piece re-sets every
202
+ * uniform every frame. */
203
+ declare function locationCache(gl: WebGL2RenderingContext, program: WebGLProgram): (name: string) => WebGLUniformLocation | null;
204
+ declare function setUniform(gl: WebGL2RenderingContext, loc: WebGLUniformLocation | null, value: UniformValue): void;
205
+ declare function createFramebuffer(gl: WebGL2RenderingContext, texture: WebGLTexture): WebGLFramebuffer;
206
+ /** The one texture unit any pipeline pass needs here — a bind with no extra slot bookkeeping. */
207
+ declare function bindTextureAt(gl: WebGL2RenderingContext, unit: number, texture: WebGLTexture, program: WebGLProgram, uniformName: string): void;
208
+
209
+ export { FULLSCREEN_TRIANGLE_VERTEX_SOURCE, PROBE_GRID_HEIGHT, PROBE_GRID_WIDTH, type Probe, type ProbeRect, type ProbeStats, type TextureOptions, type UniformValue, type VireGlassBackdropPass, type VireGlassBackdropTarget, type VireGlassPiece, type VireGlassRenderOptions, type VireGlassRenderResult, type VireGlassRenderer, type VireGlassSceneDrawer, bindTextureAt, compileShader, createFramebuffer, createProbe, createProgram, createTexture, createVireGlassRenderer, drawFullscreenTriangle, locationCache, setUniform };