insomni 0.2.0-alpha.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.
- package/LICENSE.md +674 -0
- package/README.md +234 -0
- package/dist/advanced.d.mts +76 -0
- package/dist/advanced.mjs +81 -0
- package/dist/assemble-BT3CXbSx.mjs +1574 -0
- package/dist/camera-view-DHmMiKvP.d.mts +326 -0
- package/dist/frame-mHNdKRpF.mjs +135 -0
- package/dist/index-CmMZCMJT.d.mts +39 -0
- package/dist/index-DkJfpntS.d.mts +2417 -0
- package/dist/index.d.mts +5 -0
- package/dist/index.mjs +6612 -0
- package/dist/internal.d.mts +892 -0
- package/dist/internal.mjs +566 -0
- package/dist/logger-DSyBF3Y_.mjs +15 -0
- package/dist/particles.d.mts +816 -0
- package/dist/particles.mjs +4804 -0
- package/dist/pipeline-BWCAZTKx.mjs +470 -0
- package/dist/pipeline-DE3a1Pnk.d.mts +115 -0
- package/dist/reactivity-B7I0pvzm.mjs +191 -0
- package/dist/reactivity.d.mts +2 -0
- package/dist/reactivity.mjs +2 -0
- package/dist/renderer-DzZqd1bY.d.mts +4566 -0
- package/dist/root-CHradZKM.mjs +30 -0
- package/dist/shape-DfZP9Jdk.mjs +349 -0
- package/dist/space-CeDnj6eu.mjs +11240 -0
- package/dist/spatial-Bd3Ay8I2.d.mts +85 -0
- package/dist/spatial-hash-C1crBjTo.mjs +77 -0
- package/dist/spatial.d.mts +2 -0
- package/dist/spatial.mjs +121 -0
- package/dist/text-font-D7GGDtTK.d.mts +185 -0
- package/dist/text-ttf.d.mts +91 -0
- package/dist/text-ttf.mjs +298 -0
- package/dist/texture-dABoqFoP.mjs +131 -0
- package/dist/viewport.d.mts +2 -0
- package/dist/viewport.mjs +274 -0
- package/package.json +69 -0
package/README.md
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# insomni
|
|
2
|
+
|
|
3
|
+
`insomni` is an opinionated 2D WebGPU renderer. It is optimized for data-heavy, interactive scenes — geometry is packed CPU-side into flat float arrays and uploaded to the GPU each frame. SDF-based primitives cover the common cases; complex polygons fall back to `earcut` triangulation.
|
|
4
|
+
|
|
5
|
+
> **Alpha.** The core rendering loop is stable but some APIs carry known limitations (see [Alpha limitations](#alpha-limitations) below).
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
npm install insomni
|
|
11
|
+
# or
|
|
12
|
+
pnpm add insomni
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
WebGPU must be available in the environment. Chrome 113+ and Safari 18+ ship it behind no flag.
|
|
16
|
+
|
|
17
|
+
## Quick start
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { initGPU, createRenderer, createLayer, rgba } from "insomni";
|
|
21
|
+
|
|
22
|
+
// 1. Acquire the GPU device.
|
|
23
|
+
const gpu = await initGPU();
|
|
24
|
+
|
|
25
|
+
// 2. Build a renderer wired to a canvas element.
|
|
26
|
+
const canvas = document.querySelector("canvas") as HTMLCanvasElement;
|
|
27
|
+
const renderer = createRenderer(gpu, canvas);
|
|
28
|
+
renderer.setBackground(rgba(0.05, 0.06, 0.09, 1));
|
|
29
|
+
|
|
30
|
+
// 3. Create a layer and push some primitives.
|
|
31
|
+
const scene = createLayer(); // "world" space by default
|
|
32
|
+
|
|
33
|
+
scene.pushRect({
|
|
34
|
+
x: 40,
|
|
35
|
+
y: 36,
|
|
36
|
+
width: 240,
|
|
37
|
+
height: 128,
|
|
38
|
+
fill: rgba(0.13, 0.15, 0.2, 1),
|
|
39
|
+
cornerRadius: 16,
|
|
40
|
+
});
|
|
41
|
+
scene.pushCircle({ cx: 160, cy: 100, radius: 24, fill: rgba(0.36, 0.7, 1, 1) });
|
|
42
|
+
scene.pushLine({ x1: 40, y1: 170, x2: 280, y2: 170, color: rgba(1, 1, 1, 0.2), width: 1 });
|
|
43
|
+
|
|
44
|
+
// 4. Render each frame.
|
|
45
|
+
function frame(now: DOMHighResTimeStamp) {
|
|
46
|
+
renderer.render([scene]);
|
|
47
|
+
requestAnimationFrame(frame);
|
|
48
|
+
}
|
|
49
|
+
requestAnimationFrame(frame);
|
|
50
|
+
|
|
51
|
+
// 5. Handle resize.
|
|
52
|
+
window.addEventListener("resize", () => {
|
|
53
|
+
const dpr = window.devicePixelRatio;
|
|
54
|
+
renderer.setDpr(dpr);
|
|
55
|
+
renderer.resize(canvas.clientWidth, canvas.clientHeight);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// 6. Tear down.
|
|
59
|
+
function dispose() {
|
|
60
|
+
renderer.destroy();
|
|
61
|
+
gpu.destroy();
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Coordinate spaces
|
|
66
|
+
|
|
67
|
+
Every `Layer` lives in one of two coordinate spaces, set once at construction:
|
|
68
|
+
|
|
69
|
+
| Space | Meaning |
|
|
70
|
+
| ------------------- | ----------------------------------------------------------------------------------------------------------------- |
|
|
71
|
+
| `"world"` (default) | Camera-transformed world coordinates. Pan, zoom, and `fitCameraToBounds` all apply. |
|
|
72
|
+
| `"ui"` | CSS pixel coordinates fixed to the canvas. Unaffected by the camera. Useful for HUD overlays, axes, and tooltips. |
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
const hud = createLayer({ space: "ui" });
|
|
76
|
+
hud.pushRect({ x: 8, y: 8, width: 120, height: 32, fill: rgba(0, 0, 0, 0.6), cornerRadius: 6 });
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Layer and Group model
|
|
80
|
+
|
|
81
|
+
**`Layer`** is the universal drawable. It owns a CPU-side pack of shape commands and an optional coordinate space. You create one with `createLayer()`, populate it with `push*` calls, and hand it to `renderer.render([...layers])`. Layer order in the array sets draw order — earlier layers are painted first.
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
const background = createLayer();
|
|
85
|
+
const foreground = createLayer();
|
|
86
|
+
// background is drawn first, foreground on top:
|
|
87
|
+
renderer.render([background, foreground]);
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**`Group`** is a lightweight transform container. Any primitive whose `group` field references a `Group` instance is transformed by that group's `transform` matrix at draw time — no CPU repacking needed. Mutating `group.transform` and re-rendering is enough to move, rotate, or scale an entire cluster of shapes as a rigid body.
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
import { createGroup, rotation } from "insomni";
|
|
94
|
+
|
|
95
|
+
const orbit = createGroup();
|
|
96
|
+
orbit.transform = rotation(Date.now() * 0.001);
|
|
97
|
+
|
|
98
|
+
layer.pushRect({ x: -20, y: -20, width: 40, height: 40, fill: rgba(1, 0.5, 0), group: orbit });
|
|
99
|
+
layer.pushCircle({ cx: 80, cy: 0, radius: 10, fill: rgba(0, 0.8, 1), group: orbit });
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Dynamic vs. static (cached) layers
|
|
103
|
+
|
|
104
|
+
By default every layer is **dynamic**: on each frame the CPU pack is re-uploaded to GPU. This is fine for most scenes.
|
|
105
|
+
|
|
106
|
+
For large, unchanging geometry (grids, axis decorations, terrain) you can bake a layer to a cached RTT texture:
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
// Bake the layer once — runs an MSAA render pass and freezes it into a texture.
|
|
110
|
+
renderer.cacheLayer(grid);
|
|
111
|
+
|
|
112
|
+
// Every subsequent render() composites the baked texture instead of re-drawing the geometry.
|
|
113
|
+
renderer.render([grid, dynamic]);
|
|
114
|
+
|
|
115
|
+
// Must be called before re-baking with new content.
|
|
116
|
+
renderer.uncacheLayer(grid);
|
|
117
|
+
grid.clear();
|
|
118
|
+
// ... rebuild grid ...
|
|
119
|
+
renderer.cacheLayer(grid);
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
`cacheLayer` requires `createRenderer` (not a bare `new Renderer2D`). The bake freezes the world-space camera; a subsequent pan or zoom will NOT re-bake automatically — call `uncacheLayer` + `cacheLayer` to refresh.
|
|
123
|
+
|
|
124
|
+
## OIT — Order-independent transparency
|
|
125
|
+
|
|
126
|
+
OIT is **on by default**. Text (MSDF glyphs) and transparent sprites z-interleave with shapes correctly regardless of draw order. The A-buffer composites depth-sorted fragments per pixel at the end of each frame.
|
|
127
|
+
|
|
128
|
+
When OIT is off (`config: { oit: false }`), glyphs and sprites fall back to a painter-order depth stamp — they will not float unconditionally on top, but they will not depth-sort against transparent geometry either.
|
|
129
|
+
|
|
130
|
+
You can pass a partial `RendererConfig` to `createRenderer`:
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
const renderer = createRenderer(gpu, canvas, {
|
|
134
|
+
config: { oit: false }, // disable OIT (saves ~16 MiB of A-buffer memory)
|
|
135
|
+
});
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Primitive push methods
|
|
139
|
+
|
|
140
|
+
All methods live on `Layer` and return `this` for chaining (except `pushText` which returns glyph metrics):
|
|
141
|
+
|
|
142
|
+
| Method | Shape |
|
|
143
|
+
| --------------------------- | ------------------------------------------- |
|
|
144
|
+
| `pushRect(shape)` | Axis-aligned rounded rectangle |
|
|
145
|
+
| `pushCircle(shape)` | Circle |
|
|
146
|
+
| `pushEllipse(shape)` | Ellipse |
|
|
147
|
+
| `pushLine(shape)` | Line segment (alias for `pushSegment`) |
|
|
148
|
+
| `pushSegment(shape)` | Line segment |
|
|
149
|
+
| `pushCurve(shape)` | Cubic Bézier curve |
|
|
150
|
+
| `pushArc(shape)` | Arc |
|
|
151
|
+
| `pushTriangle(shape)` | Single triangle |
|
|
152
|
+
| `pushPolygon(shape)` | Polygon (triangulated via earcut) |
|
|
153
|
+
| `pushStroke(shape)` | Analytic SDF stroke path (round joins only) |
|
|
154
|
+
| `pushSprite(sprite)` | Textured sprite (atlas region) |
|
|
155
|
+
| `pushText(shape)` | MSDF glyph text |
|
|
156
|
+
| `pushAnchoredString(shape)` | World-anchored, screen-sized text |
|
|
157
|
+
|
|
158
|
+
Call `layer.clear()` to reset the pack before rebuilding each frame.
|
|
159
|
+
|
|
160
|
+
## Camera and resize
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
// Fit the world camera to a bounding box each resize:
|
|
164
|
+
renderer.fitCameraToBounds({ minX: 0, minY: 0, maxX: 800, maxY: 600 }, { padding: 0.9 });
|
|
165
|
+
|
|
166
|
+
// Manual HiDPI setup:
|
|
167
|
+
renderer.setDpr(window.devicePixelRatio);
|
|
168
|
+
renderer.resize(canvas.clientWidth, canvas.clientHeight);
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Frame timing
|
|
172
|
+
|
|
173
|
+
Pass `onFrameTiming` in the renderer config to receive per-frame CPU/GPU timing info:
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
const renderer = createRenderer(gpu, canvas, {
|
|
177
|
+
config: {
|
|
178
|
+
onFrameTiming: (t) => console.log("frame", t),
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Device loss
|
|
184
|
+
|
|
185
|
+
`initGPU` exposes two hooks for GPU error handling:
|
|
186
|
+
|
|
187
|
+
```ts
|
|
188
|
+
const gpu = await initGPU({
|
|
189
|
+
onDeviceLost: (info) => {
|
|
190
|
+
// Notified when device is lost unexpectedly (GPU reset, driver crash, TDR).
|
|
191
|
+
// insomni does NOT auto-recreate. Tear down your renderer and call initGPU again.
|
|
192
|
+
console.error("GPU lost:", info.message);
|
|
193
|
+
},
|
|
194
|
+
onUncapturedError: (err) => {
|
|
195
|
+
// Notified for each uncaptured GPU error (validation, out-of-memory).
|
|
196
|
+
console.error("GPU error:", err);
|
|
197
|
+
},
|
|
198
|
+
logger: myLogger, // optional; defaults to console
|
|
199
|
+
});
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## SVG export
|
|
203
|
+
|
|
204
|
+
The SVG backend consumes the same `Layer[]` as `render()`:
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
import { createSVGRenderer } from "insomni";
|
|
208
|
+
|
|
209
|
+
const svg = createSVGRenderer({ width: renderer.width, height: renderer.height });
|
|
210
|
+
svg.setCamera(renderer.getCamera());
|
|
211
|
+
svg.setBackground(bg);
|
|
212
|
+
svg.render([scene]);
|
|
213
|
+
document.body.appendChild(svg.element());
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Alpha limitations
|
|
217
|
+
|
|
218
|
+
- **Device-loss recovery is notify-only.** `onDeviceLost` fires but insomni does not auto-recreate the device or rebuild GPU resources. Callers must tear down and call `initGPU` again.
|
|
219
|
+
- **OIT fragment budget is fixed at K=8 per pixel** (configurable up to K=16 via `config.oitFragmentsPerPixel`). Dense overlapping transparent layers will drop the deepest fragments.
|
|
220
|
+
- **Clipping is rectangular scissor only.** There is no stencil-based or path-based clip.
|
|
221
|
+
- **SDF stroke joins are round only.** Passing `join: "miter"` or `join: "bevel"` to `pushStroke` falls back to tessellated polylines.
|
|
222
|
+
- **`cacheLayer` freezes the world-space camera.** A camera move after baking does not re-bake automatically.
|
|
223
|
+
|
|
224
|
+
## Subpath exports
|
|
225
|
+
|
|
226
|
+
| Import path | Contents |
|
|
227
|
+
| -------------------- | ------------------------------------------------------ |
|
|
228
|
+
| `insomni` | Core renderer, Layer, Group, math, interactions |
|
|
229
|
+
| `insomni/text-ttf` | TTF/MSDF text with `loadFont()` (pulls in opentype.js) |
|
|
230
|
+
| `insomni/viewport` | `createViewport`, `CameraViewport`, pan-bound types |
|
|
231
|
+
| `insomni/internal` | Render-debug probes (unstable) |
|
|
232
|
+
| `insomni/reactivity` | Reactive primitives (unstable) |
|
|
233
|
+
| `insomni/spatial` | Spatial indexing helpers (unstable) |
|
|
234
|
+
| `insomni/particles` | Particle system (unstable) |
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { a as SpriteBufferLayout, c as TriangleBufferLayout, i as ShapeSchema, l as TriangleSchema, n as CameraSchema, o as SpriteSchema, r as ShapeBufferLayout, s as TextureSamplerLayout, t as CameraLayout } from "./pipeline-DE3a1Pnk.mjs";
|
|
2
|
+
import { Fn as INSTANCE_LAYOUT, In as deriveLayout, Ln as INSTANCE_BYTES, Rn as INSTANCE_FIELDS, Sn as assembleShader, bn as createPipelines, er as SHAPE_BYTES, i as RendererConstructorOptions, ir as SPRITE_FLOATS, nr as SHAPE_ELLIPSE, or as TRIANGLE_FLOATS, rr as SHAPE_RECT, tr as SHAPE_CIRCLE, xn as AssembledShader, yn as Pipelines, zn as INSTANCE_FLOATS } from "./renderer-DzZqd1bY.mjs";
|
|
3
|
+
import tgpu, { TgpuRoot, d } from "typegpu";
|
|
4
|
+
|
|
5
|
+
//#region src/core/f16.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* IEEE-754 half-precision (f16) packing helpers. Used to pack per-shape
|
|
8
|
+
* scalars into the instance vertex buffer — see `pack/uber-pack.ts`.
|
|
9
|
+
*
|
|
10
|
+
* Not suitable for large-magnitude world coordinates: f16 has ±65504 range
|
|
11
|
+
* and sub-pixel precision only up to ~1024.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Pack a 32-bit float into its 16-bit half-precision bit pattern.
|
|
15
|
+
* Rounds ties to even. NaN → canonical quiet NaN. Out-of-range → ±Inf.
|
|
16
|
+
*/
|
|
17
|
+
declare function packF16(value: number): number;
|
|
18
|
+
/** Pack (lo, hi) as a single little-endian u32 — `pack2x16float` equivalent. */
|
|
19
|
+
declare function pack2xF16(lo: number, hi: number): number;
|
|
20
|
+
/**
|
|
21
|
+
* Pack four components clamped to [0,1] as a little-endian unorm8x4 u32.
|
|
22
|
+
* Match to WGSL `unpack4x8unorm(value)` which yields `vec4f(r, g, b, a)` with
|
|
23
|
+
* component 0 in the low byte.
|
|
24
|
+
*/
|
|
25
|
+
declare function packUnorm8x4(r: number, g: number, b: number, a: number): number;
|
|
26
|
+
/** Unpack an IEEE-754 f16 bit pattern into a JS number. Inverse of `packF16`. */
|
|
27
|
+
declare function unpackF16(bits: number): number;
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region src/schema/emit-snippet.d.ts
|
|
30
|
+
/** Options for {@link writeRectInstance}. */
|
|
31
|
+
interface WriteRectInstanceOptions {
|
|
32
|
+
/**
|
|
33
|
+
* WGSL name of the `var<storage, read_write>` array the instance is written
|
|
34
|
+
* into. The array element type MUST be the uber `InstanceStruct` (16-float /
|
|
35
|
+
* 64 B) — i.e. the same struct `INSTANCE_STRUCT_WGSL` declares. Defaults to
|
|
36
|
+
* `"instances"`.
|
|
37
|
+
*/
|
|
38
|
+
array?: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* WGSL helper-function source for emitting a v3 rect `Instance` into a storage
|
|
42
|
+
* array, with a field mapping byte-identical to `kinds/rect.ts`'s `pack()`.
|
|
43
|
+
*
|
|
44
|
+
* Emits one function:
|
|
45
|
+
*
|
|
46
|
+
* ```wgsl
|
|
47
|
+
* fn writeRectInstance(
|
|
48
|
+
* index: u32, // element index into the storage array
|
|
49
|
+
* center: vec2f, // rect center (x + halfW, y + halfH)
|
|
50
|
+
* halfSize: vec2f, // (halfW, halfH) = (width, height) * 0.5
|
|
51
|
+
* fill: vec4f, // fill color, components in [0,1]
|
|
52
|
+
* stroke: vec4f, // stroke color, components in [0,1]
|
|
53
|
+
* strokeWidth: f32,
|
|
54
|
+
* cornerRadius: f32,
|
|
55
|
+
* rotation: f32, // radians; the CALLER normalizes to (-pi, pi] if desired
|
|
56
|
+
* order: f32, // explicit depth (never index-derived)
|
|
57
|
+
* )
|
|
58
|
+
* ```
|
|
59
|
+
*
|
|
60
|
+
* NOTE the param contract vs the CPU `RectRecord`: this helper takes `center` +
|
|
61
|
+
* `halfSize` (already halved) rather than top-left `x,y` + full `width,height`,
|
|
62
|
+
* because a compute kernel typically has the centroid in hand. The CPU packer
|
|
63
|
+
* does `center = (x + w/2, y + h/2)` / `half = (w/2, h/2)` itself — pass those
|
|
64
|
+
* precomputed values here and the resulting bytes match. The CPU packer also
|
|
65
|
+
* wraps `rotation` into `(-pi, pi]`; WGSL `pack2x16float` does not, so the
|
|
66
|
+
* caller should wrap rotation to match if it needs bit-exact parity for
|
|
67
|
+
* out-of-range angles (in-range angles are already identical).
|
|
68
|
+
*
|
|
69
|
+
* The emitted function reads the field names from {@link INSTANCE_LAYOUT}
|
|
70
|
+
* (`inst.geom0`, `inst.lane3`, …) so it cannot drift from the schema.
|
|
71
|
+
*
|
|
72
|
+
* @param opts.array Storage-array variable name. Default `"instances"`.
|
|
73
|
+
*/
|
|
74
|
+
declare function writeRectInstance(opts?: WriteRectInstanceOptions): string;
|
|
75
|
+
//#endregion
|
|
76
|
+
export { type AssembledShader, CameraLayout, CameraSchema, INSTANCE_BYTES, INSTANCE_FIELDS, INSTANCE_FLOATS, INSTANCE_LAYOUT, type Pipelines, type RendererConstructorOptions, SHAPE_BYTES, SHAPE_CIRCLE, SHAPE_ELLIPSE, SHAPE_RECT, SPRITE_FLOATS, ShapeBufferLayout, ShapeSchema, SpriteBufferLayout, SpriteSchema, TRIANGLE_FLOATS, TextureSamplerLayout, type TgpuRoot, TriangleBufferLayout, TriangleSchema, type WriteRectInstanceOptions, assembleShader, createPipelines, d, deriveLayout, pack2xF16, packF16, packUnorm8x4, tgpu, unpackF16, writeRectInstance };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { C as pack2xF16, E as unpackF16, L as INSTANCE_BYTES, R as INSTANCE_FIELDS, T as packUnorm8x4, b as INSTANCE_LAYOUT, d as SHAPE_BYTES, f as SHAPE_CIRCLE, g as TRIANGLE_FLOATS, h as SPRITE_FLOATS, i as createPipelines, m as SHAPE_RECT, p as SHAPE_ELLIPSE, r as assembleShader, w as packF16, x as deriveLayout, z as INSTANCE_FLOATS } from "./assemble-BT3CXbSx.mjs";
|
|
2
|
+
import { c as SpriteBufferLayout, d as TriangleBufferLayout, f as TriangleSchema, l as SpriteSchema, n as CameraLayout, o as ShapeBufferLayout, r as CameraSchema, s as ShapeSchema, u as TextureSamplerLayout } from "./pipeline-BWCAZTKx.mjs";
|
|
3
|
+
import tgpu, { d } from "typegpu";
|
|
4
|
+
//#region src/schema/emit-snippet.ts
|
|
5
|
+
/**
|
|
6
|
+
* WGSL helper-function source for emitting a v3 rect `Instance` into a storage
|
|
7
|
+
* array, with a field mapping byte-identical to `kinds/rect.ts`'s `pack()`.
|
|
8
|
+
*
|
|
9
|
+
* Emits one function:
|
|
10
|
+
*
|
|
11
|
+
* ```wgsl
|
|
12
|
+
* fn writeRectInstance(
|
|
13
|
+
* index: u32, // element index into the storage array
|
|
14
|
+
* center: vec2f, // rect center (x + halfW, y + halfH)
|
|
15
|
+
* halfSize: vec2f, // (halfW, halfH) = (width, height) * 0.5
|
|
16
|
+
* fill: vec4f, // fill color, components in [0,1]
|
|
17
|
+
* stroke: vec4f, // stroke color, components in [0,1]
|
|
18
|
+
* strokeWidth: f32,
|
|
19
|
+
* cornerRadius: f32,
|
|
20
|
+
* rotation: f32, // radians; the CALLER normalizes to (-pi, pi] if desired
|
|
21
|
+
* order: f32, // explicit depth (never index-derived)
|
|
22
|
+
* )
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* NOTE the param contract vs the CPU `RectRecord`: this helper takes `center` +
|
|
26
|
+
* `halfSize` (already halved) rather than top-left `x,y` + full `width,height`,
|
|
27
|
+
* because a compute kernel typically has the centroid in hand. The CPU packer
|
|
28
|
+
* does `center = (x + w/2, y + h/2)` / `half = (w/2, h/2)` itself — pass those
|
|
29
|
+
* precomputed values here and the resulting bytes match. The CPU packer also
|
|
30
|
+
* wraps `rotation` into `(-pi, pi]`; WGSL `pack2x16float` does not, so the
|
|
31
|
+
* caller should wrap rotation to match if it needs bit-exact parity for
|
|
32
|
+
* out-of-range angles (in-range angles are already identical).
|
|
33
|
+
*
|
|
34
|
+
* The emitted function reads the field names from {@link INSTANCE_LAYOUT}
|
|
35
|
+
* (`inst.geom0`, `inst.lane3`, …) so it cannot drift from the schema.
|
|
36
|
+
*
|
|
37
|
+
* @param opts.array Storage-array variable name. Default `"instances"`.
|
|
38
|
+
*/
|
|
39
|
+
function writeRectInstance(opts = {}) {
|
|
40
|
+
const arr = opts.array ?? "instances";
|
|
41
|
+
const O = INSTANCE_LAYOUT.offsets;
|
|
42
|
+
for (const name of [
|
|
43
|
+
"geom0",
|
|
44
|
+
"geom1",
|
|
45
|
+
"colorBits",
|
|
46
|
+
"typeFlag",
|
|
47
|
+
"order",
|
|
48
|
+
"lane0",
|
|
49
|
+
"lane1",
|
|
50
|
+
"lane2",
|
|
51
|
+
"lane3",
|
|
52
|
+
"lane4"
|
|
53
|
+
]) if (!(name in O)) throw new Error(`writeRectInstance: schema missing field "${name}"`);
|
|
54
|
+
return `
|
|
55
|
+
fn writeRectInstance(
|
|
56
|
+
index: u32,
|
|
57
|
+
center: vec2f,
|
|
58
|
+
halfSize: vec2f,
|
|
59
|
+
fill: vec4f,
|
|
60
|
+
stroke: vec4f,
|
|
61
|
+
strokeWidth: f32,
|
|
62
|
+
cornerRadius: f32,
|
|
63
|
+
rotation: f32,
|
|
64
|
+
order: f32,
|
|
65
|
+
) {
|
|
66
|
+
var inst: InstanceStruct;
|
|
67
|
+
inst.geom0 = vec4f(center, 0.0, 0.0);
|
|
68
|
+
inst.geom1 = vec4f(0.0);
|
|
69
|
+
inst.colorBits = pack4x8unorm(fill);
|
|
70
|
+
inst.typeFlag = 0u;
|
|
71
|
+
inst.order = order;
|
|
72
|
+
inst.lane0 = pack4x8unorm(stroke);
|
|
73
|
+
inst.lane1 = pack2x16float(vec2f(rotation, strokeWidth));
|
|
74
|
+
inst.lane2 = pack2x16float(vec2f(cornerRadius, 0.0));
|
|
75
|
+
inst.lane3 = pack2x16float(halfSize);
|
|
76
|
+
inst.lane4 = 0u;
|
|
77
|
+
${arr}[index] = inst;
|
|
78
|
+
}`;
|
|
79
|
+
}
|
|
80
|
+
//#endregion
|
|
81
|
+
export { CameraLayout, CameraSchema, INSTANCE_BYTES, INSTANCE_FIELDS, INSTANCE_FLOATS, INSTANCE_LAYOUT, SHAPE_BYTES, SHAPE_CIRCLE, SHAPE_ELLIPSE, SHAPE_RECT, SPRITE_FLOATS, ShapeBufferLayout, ShapeSchema, SpriteBufferLayout, SpriteSchema, TRIANGLE_FLOATS, TextureSamplerLayout, TriangleBufferLayout, TriangleSchema, assembleShader, createPipelines, d, deriveLayout, pack2xF16, packF16, packUnorm8x4, tgpu, unpackF16, writeRectInstance };
|