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
|
@@ -0,0 +1,892 @@
|
|
|
1
|
+
import { i as ShapeSchema, o as SpriteSchema } from "./pipeline-DE3a1Pnk.mjs";
|
|
2
|
+
import { $ as RendererConfig, $n as SEGMENT_FLOATS, A as SpatialGridResult, B as FrameDebugListener, Bn as ARC_FLOATS, C as ABuffer, D as SLOT_BYTES, F as CullResult, G as LayerSummary, Gn as CURVE_FLOATS, H as FRAME_RING_CAPACITY, Hn as CURVE_CAP_BUTT, I as CulledRange, It as DrawCommand, K as summarize, L as cullCommands, Lt as UberPack, M as buildSpatialGrid, N as queryRanges, O as SpatialGrid, P as CullCommand, R as worldFrustumAabb, Rt as nextByteCapacity, S as createOITPipelines, St as GlyphPack, T as HEAD_BYTES, U as FrameRing, Un as CURVE_CAP_ROUND, V as RendererDebug, W as FrameSummary, Wn as CURVE_CAP_SQUARE, _r as Color, at as convertRect, b as OITPipelineDeps, er as SHAPE_BYTES, et as DebugSize, fn as Group, it as captureSpaceMap, j as SpatialRange, k as SpatialGridOptions, ln as LayerSpace, nr as SHAPE_ELLIPSE, nt as RectXYWH, or as TRIANGLE_FLOATS, r as Renderer2D, rr as SHAPE_RECT, rt as SpaceMap, s as createTestRenderer, tr as SHAPE_CIRCLE, tt as DebugSpace, un as ProjectOptions, vt as Layer, x as OITPipelines, xn as AssembledShader } from "./renderer-DzZqd1bY.mjs";
|
|
3
|
+
import { N as FrameRect, S as Mat3, n as CameraState, t as Bounds2D } from "./camera-view-DHmMiKvP.mjs";
|
|
4
|
+
import { _ as resolveRoot, g as GPUOwner } from "./text-font-D7GGDtTK.mjs";
|
|
5
|
+
import { D as SpatialRenderer2D, E as SpatialPointerEvent, S as Scheduler, T as SpatialFrameTiming, b as SceneRoot, g as Rect, i as DirtyRegion, m as PointerRouter, r as DirtyChannel, t as Damage, v as SceneNode } from "./spatial-Bd3Ay8I2.mjs";
|
|
6
|
+
import { StorageFlag, TgpuBindGroup, TgpuBuffer, TgpuRoot, d } from "typegpu";
|
|
7
|
+
|
|
8
|
+
//#region src/shared/dynamic-buffer.d.ts
|
|
9
|
+
declare function nextPow2(value: number): number;
|
|
10
|
+
//#endregion
|
|
11
|
+
//#region src/gpu/spatial-hash.d.ts
|
|
12
|
+
declare const SPATIAL_HASH_WORKGROUP_SIZE = 64;
|
|
13
|
+
declare const SPATIAL_HASH_PARAMS_BYTES = 32;
|
|
14
|
+
declare const SPATIAL_HASH_PARAMS_WGSL = "\nstruct SpatialHashParams {\n rangeStart: u32,\n rangeCount: u32,\n bucketMask: u32,\n bucketCount: u32,\n invCellSize: f32,\n weight: f32,\n _pad0: f32,\n _pad1: f32,\n};\n\nconst EMPTY_SLOT: u32 = 0xffffffffu;\nconst DEAD_CELL_COORD: i32 = 2147483647;\n\nfn hashCell(cell: vec2i, mask: u32) -> u32 {\n let x = bitcast<u32>(cell.x);\n let y = bitcast<u32>(cell.y);\n var h = x * 0x9e3779b9u;\n h = (h << 6u) ^ (h >> 2u) ^ (y * 0x85ebca6bu);\n return h & mask;\n}\n\nfn worldCell(pos: vec2f, invCellSize: f32) -> vec2i {\n return vec2i(floor(pos * invCellSize));\n}\n";
|
|
15
|
+
declare const CLEAR_SPATIAL_HASH_WGSL = "\nstruct SpatialHashParams {\n rangeStart: u32,\n rangeCount: u32,\n bucketMask: u32,\n bucketCount: u32,\n invCellSize: f32,\n weight: f32,\n _pad0: f32,\n _pad1: f32,\n};\n\nconst EMPTY_SLOT: u32 = 0xffffffffu;\nconst DEAD_CELL_COORD: i32 = 2147483647;\n\nfn hashCell(cell: vec2i, mask: u32) -> u32 {\n let x = bitcast<u32>(cell.x);\n let y = bitcast<u32>(cell.y);\n var h = x * 0x9e3779b9u;\n h = (h << 6u) ^ (h >> 2u) ^ (y * 0x85ebca6bu);\n return h & mask;\n}\n\nfn worldCell(pos: vec2f, invCellSize: f32) -> vec2i {\n return vec2i(floor(pos * invCellSize));\n}\n\n@group(0) @binding(0) var<storage, read_write> heads: array<atomic<u32>>;\n@group(0) @binding(1) var<uniform> params: SpatialHashParams;\n\n@compute @workgroup_size(64)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let i = gid.x;\n if (i >= params.bucketCount) {\n return;\n }\n atomicStore(&heads[i], EMPTY_SLOT);\n}\n";
|
|
16
|
+
declare const BUILD_SPATIAL_HASH_WGSL = "\nstruct SpatialHashParams {\n rangeStart: u32,\n rangeCount: u32,\n bucketMask: u32,\n bucketCount: u32,\n invCellSize: f32,\n weight: f32,\n _pad0: f32,\n _pad1: f32,\n};\n\nconst EMPTY_SLOT: u32 = 0xffffffffu;\nconst DEAD_CELL_COORD: i32 = 2147483647;\n\nfn hashCell(cell: vec2i, mask: u32) -> u32 {\n let x = bitcast<u32>(cell.x);\n let y = bitcast<u32>(cell.y);\n var h = x * 0x9e3779b9u;\n h = (h << 6u) ^ (h >> 2u) ^ (y * 0x85ebca6bu);\n return h & mask;\n}\n\nfn worldCell(pos: vec2f, invCellSize: f32) -> vec2i {\n return vec2i(floor(pos * invCellSize));\n}\n\n@group(0) @binding(0) var<storage, read_write> heads: array<atomic<u32>>;\n@group(0) @binding(1) var<storage, read_write> next: array<u32>;\n@group(0) @binding(2) var<storage, read_write> cells: array<vec2i>;\n@group(0) @binding(3) var<storage, read> nodeIds: array<u32>;\n@group(0) @binding(4) var<storage, read> positions: array<vec4f>;\n@group(0) @binding(5) var<storage, read> alive: array<u32>;\n@group(0) @binding(6) var<uniform> params: SpatialHashParams;\n\n@compute @workgroup_size(64)\nfn main(@builtin(global_invocation_id) gid: vec3u) {\n let role = gid.x;\n if (role >= params.rangeCount) {\n return;\n }\n\n let entry = params.rangeStart + role;\n let nodeId = nodeIds[entry];\n next[entry] = EMPTY_SLOT;\n\n if (alive[nodeId] == 0u) {\n cells[entry] = vec2i(DEAD_CELL_COORD);\n return;\n }\n\n let cell = worldCell(positions[nodeId].xy, params.invCellSize);\n cells[entry] = cell;\n let bucket = hashCell(cell, params.bucketMask);\n let prev = atomicExchange(&heads[bucket], entry);\n next[entry] = prev;\n}\n";
|
|
17
|
+
//#endregion
|
|
18
|
+
//#region src/pack/region-index.d.ts
|
|
19
|
+
/** A byte range within the `UberPack` buffer that must be re-uploaded. */
|
|
20
|
+
interface DirtyRange {
|
|
21
|
+
/** Byte offset of the dirty span within the `UberPack` buffer. */
|
|
22
|
+
byteOffset: number;
|
|
23
|
+
/** Byte length of the dirty span. */
|
|
24
|
+
byteLength: number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Maps group identities to their byte spans within the `UberPack` buffer, and
|
|
28
|
+
* maintains an accumulated set of dirty byte ranges for the current frame.
|
|
29
|
+
*
|
|
30
|
+
* Default region granularity = one `Group` object. Callers that do not use
|
|
31
|
+
* explicit group tracking can pass a single sentinel object as `groupId`.
|
|
32
|
+
*
|
|
33
|
+
* Usage per frame:
|
|
34
|
+
* ```ts
|
|
35
|
+
* regions.reset();
|
|
36
|
+
* for (const [group, items] of scene.byGroup()) {
|
|
37
|
+
* regions.beginRegion(group, pack.byteLength);
|
|
38
|
+
* for (const item of items) pack.append(...);
|
|
39
|
+
* regions.endRegion(group, pack.byteLength);
|
|
40
|
+
* }
|
|
41
|
+
* // At mutation time:
|
|
42
|
+
* regions.markDirty(hoveredGroup);
|
|
43
|
+
* // Before upload:
|
|
44
|
+
* const ranges = regions.coalesced();
|
|
45
|
+
* regions.clearDirty();
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
declare class RegionIndex {
|
|
49
|
+
/** group identity → byte span in the current pack. */
|
|
50
|
+
private _spans;
|
|
51
|
+
/** Accumulated dirty ranges for this frame (may overlap / be out of order). */
|
|
52
|
+
private _dirty;
|
|
53
|
+
/**
|
|
54
|
+
* Record the byte offset at which a group's instances BEGIN being packed.
|
|
55
|
+
* `byteOffset` is the value of `UberPack.byteLength` BEFORE the first
|
|
56
|
+
* `append` call for this group.
|
|
57
|
+
*/
|
|
58
|
+
beginRegion(groupId: object, byteOffset: number): void;
|
|
59
|
+
/**
|
|
60
|
+
* Record the byte offset at which a group's instances END being packed.
|
|
61
|
+
* `byteOffset` is the value of `UberPack.byteLength` AFTER the last
|
|
62
|
+
* `append` call for this group.
|
|
63
|
+
*/
|
|
64
|
+
endRegion(groupId: object, byteOffset: number): void;
|
|
65
|
+
/**
|
|
66
|
+
* Reset all span records for a new frame. Call before re-packing. Does NOT
|
|
67
|
+
* clear dirty ranges (use `clearDirty` for that).
|
|
68
|
+
*/
|
|
69
|
+
reset(): void;
|
|
70
|
+
/**
|
|
71
|
+
* Mark a group's byte span as dirty. If the group has no recorded span
|
|
72
|
+
* (e.g. not packed this frame), this is a no-op.
|
|
73
|
+
*/
|
|
74
|
+
markDirty(groupId: object): void;
|
|
75
|
+
/**
|
|
76
|
+
* Return the minimal, sorted, non-overlapping set of dirty byte ranges.
|
|
77
|
+
*
|
|
78
|
+
* - Output is sorted ascending by `byteOffset`.
|
|
79
|
+
* - Adjacent or overlapping input ranges are merged into a single output range.
|
|
80
|
+
* - Non-destructive: does not mutate the internal `_dirty` list.
|
|
81
|
+
* - Returns an empty array when no ranges are dirty.
|
|
82
|
+
*/
|
|
83
|
+
coalesced(): DirtyRange[];
|
|
84
|
+
/**
|
|
85
|
+
* Clear accumulated dirty ranges. Call after the GPU upload is complete.
|
|
86
|
+
* Does NOT reset span records — use `reset()` for that.
|
|
87
|
+
*/
|
|
88
|
+
clearDirty(): void;
|
|
89
|
+
/**
|
|
90
|
+
* Return the recorded byte span for a group, or `undefined` if the group
|
|
91
|
+
* was not packed this frame. For testing / debugging only.
|
|
92
|
+
*/
|
|
93
|
+
spanOf(groupId: object): {
|
|
94
|
+
start: number;
|
|
95
|
+
end: number;
|
|
96
|
+
} | undefined;
|
|
97
|
+
}
|
|
98
|
+
//#endregion
|
|
99
|
+
//#region src/pack/repack-v1.d.ts
|
|
100
|
+
/**
|
|
101
|
+
* Repack `count` v1-stride segment records (`SEGMENT_FLOATS` floats each) into a
|
|
102
|
+
* freshly-allocated v3 instance buffer (`count * INSTANCE_FLOATS` floats).
|
|
103
|
+
* Byte-identical to looping `Layer.pushSegment` over the same records.
|
|
104
|
+
*/
|
|
105
|
+
declare function repackSegments(src: ArrayLike<number>, count: number): Float32Array;
|
|
106
|
+
/**
|
|
107
|
+
* Repack `count` v1-stride curve records (`CURVE_FLOATS` floats each) into a
|
|
108
|
+
* freshly-allocated v3 instance buffer. Byte-identical to looping
|
|
109
|
+
* `Layer.pushCurve` over the same records.
|
|
110
|
+
*/
|
|
111
|
+
declare function repackCurves(src: ArrayLike<number>, count: number): Float32Array;
|
|
112
|
+
/**
|
|
113
|
+
* Repack `count` v1-stride arc records (`ARC_FLOATS` floats each) into a
|
|
114
|
+
* freshly-allocated v3 instance buffer. Byte-identical to looping
|
|
115
|
+
* `Layer.pushArc` over the same records.
|
|
116
|
+
*/
|
|
117
|
+
declare function repackArcs(src: ArrayLike<number>, count: number): Float32Array;
|
|
118
|
+
//#endregion
|
|
119
|
+
//#region src/target/blit.d.ts
|
|
120
|
+
/**
|
|
121
|
+
* Copy the entire persistent backbuffer onto the swap-chain texture.
|
|
122
|
+
*
|
|
123
|
+
* Both textures share the same format, size, and sample count (single-sample),
|
|
124
|
+
* so this is a raw texel copy — no sampler, blend, or premultiply round-trip.
|
|
125
|
+
*
|
|
126
|
+
* Always copies the full `width × height`. Never pass a sub-rect: a partial
|
|
127
|
+
* copy leaves pixels outside the rect cleared on the presented physical buffer
|
|
128
|
+
* (see the module header). Width/height are in DEVICE pixels (the backbuffer /
|
|
129
|
+
* swap-chain texel dimensions).
|
|
130
|
+
*
|
|
131
|
+
* @param encoder Command encoder for the current frame (the copy is recorded
|
|
132
|
+
* after `pass.end()` and before `device.queue.submit`).
|
|
133
|
+
* @param src Persistent backbuffer texture (`ctx.backbuffer`).
|
|
134
|
+
* @param dst Swap-chain texture (`ctx.gpuContext.getCurrentTexture()`).
|
|
135
|
+
* @param width Device-pixel width of both textures (`ctx.width`).
|
|
136
|
+
* @param height Device-pixel height of both textures (`ctx.height`).
|
|
137
|
+
*/
|
|
138
|
+
declare function blitBackbuffer(encoder: GPUCommandEncoder, src: GPUTexture, dst: GPUTexture, width: number, height: number): void;
|
|
139
|
+
//#endregion
|
|
140
|
+
//#region src/tiers/retained.d.ts
|
|
141
|
+
/**
|
|
142
|
+
* Metadata for a single UberPack's stable GPU buffer. Stored in the
|
|
143
|
+
* WeakMap keyed on the pack itself.
|
|
144
|
+
*/
|
|
145
|
+
interface RetainedRecord {
|
|
146
|
+
/** Stable GPU buffer — NOT re-uploaded when damage is clear. */
|
|
147
|
+
gpuBuffer: GPUBuffer;
|
|
148
|
+
/** Byte capacity of gpuBuffer. */
|
|
149
|
+
gpuCapacityBytes: number;
|
|
150
|
+
/** Frames elapsed since the start of the current shrink window. */
|
|
151
|
+
framesSinceCheck: number;
|
|
152
|
+
/** Peak byte usage observed within the current shrink window. */
|
|
153
|
+
windowPeakBytes: number;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* WeakMap-keyed retained tier: manages stable GPU instance buffers for
|
|
157
|
+
* `UberPack` instances whose paint and data damage bits are both clear.
|
|
158
|
+
*
|
|
159
|
+
* Lifecycle per frame:
|
|
160
|
+
* ```ts
|
|
161
|
+
* if (!tier.has(pack)) {
|
|
162
|
+
* tier.build(pack); // damage present — reallocate/upload
|
|
163
|
+
* }
|
|
164
|
+
* tier.tickShrink(pack); // always call at frame end
|
|
165
|
+
* const rec = tier.get(pack)!; // use rec.gpuBuffer for draw calls
|
|
166
|
+
* ```
|
|
167
|
+
*/
|
|
168
|
+
declare class RetainedTier {
|
|
169
|
+
private readonly root;
|
|
170
|
+
private readonly records;
|
|
171
|
+
constructor(root: TgpuRoot);
|
|
172
|
+
/**
|
|
173
|
+
* Returns `true` iff a retained record exists for `pack` (damage-miss path:
|
|
174
|
+
* caller skips `build`). The RetainedTier itself never suppresses a build
|
|
175
|
+
* — that decision belongs to the caller.
|
|
176
|
+
*/
|
|
177
|
+
has(pack: UberPack): boolean;
|
|
178
|
+
/**
|
|
179
|
+
* Retrieve the retained record for `pack` without triggering a build.
|
|
180
|
+
* Returns `undefined` if `has(pack)` is false.
|
|
181
|
+
*/
|
|
182
|
+
get(pack: UberPack): RetainedRecord | undefined;
|
|
183
|
+
/**
|
|
184
|
+
* Build or rebuild the GPU buffer for `pack`. Call only when paint/data
|
|
185
|
+
* damage is present.
|
|
186
|
+
*
|
|
187
|
+
* - Reuses the existing buffer if it is large enough (range write only).
|
|
188
|
+
* - Reallocates with the pow2-then-1.25× policy when the buffer is too small.
|
|
189
|
+
*/
|
|
190
|
+
build(pack: UberPack): RetainedRecord;
|
|
191
|
+
/**
|
|
192
|
+
* Advance the shrink window for `pack`. Call once per frame per retained
|
|
193
|
+
* pack (regardless of whether `build` was called this frame).
|
|
194
|
+
*
|
|
195
|
+
* After `SHRINK_WINDOW_FRAMES` consecutive frames where the peak byte usage
|
|
196
|
+
* is below `gpuCapacityBytes / SHRINK_PEAK_RATIO`, the GPU buffer is
|
|
197
|
+
* reallocated to a smaller size.
|
|
198
|
+
*/
|
|
199
|
+
tickShrink(pack: UberPack): void;
|
|
200
|
+
/**
|
|
201
|
+
* Destroy the GPU buffer for `pack` and remove its record. Call when the
|
|
202
|
+
* layer is uncached or destroyed.
|
|
203
|
+
*/
|
|
204
|
+
evict(pack: UberPack): void;
|
|
205
|
+
/**
|
|
206
|
+
* Drop all retained state. This method is intentionally a no-op: the WeakMap
|
|
207
|
+
* holds no strong references and cannot iterate to destroy GPU buffers — callers
|
|
208
|
+
* MUST call `evict(pack)` for every registered pack before calling `destroy()`.
|
|
209
|
+
* After this call the `RetainedTier` is unusable.
|
|
210
|
+
*
|
|
211
|
+
* Contract: all GPU buffers are the owner's responsibility. `destroy()` does
|
|
212
|
+
* not destroy them; failing to evict before drop leaks GPU memory.
|
|
213
|
+
*/
|
|
214
|
+
destroy(): void;
|
|
215
|
+
}
|
|
216
|
+
//#endregion
|
|
217
|
+
//#region src/tiers/tile-replay.d.ts
|
|
218
|
+
/** Screen-device-px AABB used by {@link TileGrid.assign}. Mirrors the v3
|
|
219
|
+
* `WorldAABB` shape (`kinds/kind.ts`) but in already-projected screen pixels. */
|
|
220
|
+
interface ScreenAABB {
|
|
221
|
+
minX: number;
|
|
222
|
+
minY: number;
|
|
223
|
+
maxX: number;
|
|
224
|
+
maxY: number;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Row-major grid of command buckets over screen device-px space. The grid is
|
|
228
|
+
* built once per layout: every command is assigned to the tiles its dilated
|
|
229
|
+
* AABB touches, and a dirty-rect query returns the union of the touched tiles'
|
|
230
|
+
* buckets.
|
|
231
|
+
*/
|
|
232
|
+
declare class TileGrid {
|
|
233
|
+
/** Tile edge length in device px (== {@link RendererConfig.tileSize}). */
|
|
234
|
+
readonly cellSize: number;
|
|
235
|
+
readonly cols: number;
|
|
236
|
+
readonly rows: number;
|
|
237
|
+
/** Flat row-major array: `buckets[tileId]` = cmdIndex list (insertion order). */
|
|
238
|
+
private readonly buckets;
|
|
239
|
+
constructor(canvasW: number, canvasH: number, cellSize: number);
|
|
240
|
+
/** Row-major tile id for a `(col, row)` cell. */
|
|
241
|
+
tileId(col: number, row: number): number;
|
|
242
|
+
/**
|
|
243
|
+
* Rasterize an AABB (device px, half-open `[min, max)`) to the tile ids it
|
|
244
|
+
* touches. The AABB is dilated by +1px on every side before rasterizing so a
|
|
245
|
+
* shape's analytic anti-aliased fringe (which spills ≤1px past the geometric
|
|
246
|
+
* edge) is conservatively included — a shape sitting on a tile seam lands in
|
|
247
|
+
* both tiles. Out-of-range cells are clamped to the grid; a fully off-grid
|
|
248
|
+
* AABB yields an empty list.
|
|
249
|
+
*/
|
|
250
|
+
tilesForAabb(minX: number, minY: number, maxX: number, maxY: number): number[];
|
|
251
|
+
/**
|
|
252
|
+
* Rasterize a {@link FrameRect} (CSS px) to a tile-id set. The rect is first
|
|
253
|
+
* padded by 1px — the SAME `padFrameRect(rect, 1)` the T22 clear-quad step
|
|
254
|
+
* applies — then converted to device px via `dpr`, then handed to
|
|
255
|
+
* {@link tilesForAabb} (which adds its own +1px AA dilation). The query rect
|
|
256
|
+
* thus covers at least the pixels the per-rect erase will actually repaint.
|
|
257
|
+
*/
|
|
258
|
+
tilesForRect(rect: FrameRect, dpr: number): Set<number>;
|
|
259
|
+
/**
|
|
260
|
+
* Assign a command (by its index in the renderer's command array) to every
|
|
261
|
+
* tile its screen AABB intersects. The same +1px AA dilation as
|
|
262
|
+
* {@link tilesForAabb} applies, so a command straddling a tile boundary is
|
|
263
|
+
* dropped into every touched bucket — the dirty-rect query then issues it
|
|
264
|
+
* exactly once per region pass (via {@link commandsForTiles} dedup).
|
|
265
|
+
*/
|
|
266
|
+
assign(cmdIndex: number, aabb: ScreenAABB): void;
|
|
267
|
+
/**
|
|
268
|
+
* Union of command indices across a set of tile ids, deduplicated and sorted
|
|
269
|
+
* ascending. Ascending = original scene/submission order, so the caller's
|
|
270
|
+
* opaque-reverse sweep (`for i = n-1 … 0`) and transparent-forward sweep match
|
|
271
|
+
* v1's painter stacking. A command in two dirty tiles appears once.
|
|
272
|
+
*/
|
|
273
|
+
commandsForTiles(tileIds: Set<number>): number[];
|
|
274
|
+
/** Total tile count (`cols × rows`). */
|
|
275
|
+
get totalTiles(): number;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Full-frame fallback threshold. When the dirty-tile fraction exceeds this,
|
|
279
|
+
* {@link dirtyTileSet} returns `null` so the caller skips bucket replay and runs
|
|
280
|
+
* a single full-frame pass instead — thousands of scissor switches across most
|
|
281
|
+
* of the canvas cost more than one unscissored re-render.
|
|
282
|
+
*/
|
|
283
|
+
declare const FULL_FRAME_TILE_FRACTION = 0.5;
|
|
284
|
+
/**
|
|
285
|
+
* Map damage regions to the set of tile ids to replay. Unions the tiles each
|
|
286
|
+
* region touches (via {@link TileGrid.tilesForRect}). Returns `null` — NOT an
|
|
287
|
+
* empty set — when the dirty fraction strictly exceeds
|
|
288
|
+
* {@link FULL_FRAME_TILE_FRACTION}, signalling "fall back to a full frame". An
|
|
289
|
+
* empty/zero-area region set returns an empty `Set` (nothing to replay), which
|
|
290
|
+
* the caller distinguishes from `null`.
|
|
291
|
+
*/
|
|
292
|
+
declare function dirtyTileSet(grid: TileGrid, regions: readonly FrameRect[], dpr: number): Set<number> | null;
|
|
293
|
+
//#endregion
|
|
294
|
+
//#region src/damage/spine.d.ts
|
|
295
|
+
interface FrameSpineOptions {
|
|
296
|
+
/**
|
|
297
|
+
* Scheduler implementation. Defaults to RAFScheduler (production). Pass a
|
|
298
|
+
* ManualScheduler (or createTestSpine) for deterministic unit tests.
|
|
299
|
+
*/
|
|
300
|
+
scheduler?: Scheduler;
|
|
301
|
+
/**
|
|
302
|
+
* Called each flush with the coalesced dirty regions. Fires only when at
|
|
303
|
+
* least one non-empty mark() was accumulated since the last flush.
|
|
304
|
+
*/
|
|
305
|
+
onFlush: (regions: DirtyRegion) => void;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Frame-loop primitive for v3. Owns a DirtyChannel<DirtyRegion> wired to a
|
|
309
|
+
* Scheduler. Callers mark damage via mark(); the channel coalesces and
|
|
310
|
+
* schedules a single flush that invokes onFlush with the union of all marked
|
|
311
|
+
* regions. Empty flushes (no mark since last pump) are suppressed by the
|
|
312
|
+
* DirtyChannel itself.
|
|
313
|
+
*
|
|
314
|
+
* T61's GpuSceneRoot owns one FrameSpine and delegates damage marking to it.
|
|
315
|
+
*/
|
|
316
|
+
declare class FrameSpine {
|
|
317
|
+
#private;
|
|
318
|
+
readonly channel: DirtyChannel<DirtyRegion>;
|
|
319
|
+
readonly scheduler: Scheduler;
|
|
320
|
+
constructor({
|
|
321
|
+
scheduler,
|
|
322
|
+
onFlush
|
|
323
|
+
}: FrameSpineOptions);
|
|
324
|
+
/**
|
|
325
|
+
* Accumulate damage and schedule a flush. An empty regions array is a
|
|
326
|
+
* fast-path no-op — nothing is scheduled.
|
|
327
|
+
*/
|
|
328
|
+
mark(regions: DirtyRegion): void;
|
|
329
|
+
/**
|
|
330
|
+
* Dispose the spine. Removes the subscriber and cancels any pending
|
|
331
|
+
* scheduled flush.
|
|
332
|
+
*/
|
|
333
|
+
dispose(): void;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Creates a FrameSpine backed by a ManualScheduler. Call pump() to drain
|
|
337
|
+
* any pending flush synchronously.
|
|
338
|
+
*
|
|
339
|
+
* @example
|
|
340
|
+
* const { spine, pump } = createTestSpine((r) => calls.push(r));
|
|
341
|
+
* spine.mark([{ rect: { x: 0, y: 0, w: 10, h: 10 }, kind: "paint" }]);
|
|
342
|
+
* pump(); // fires onFlush once
|
|
343
|
+
*/
|
|
344
|
+
declare function createTestSpine(onFlush: (r: DirtyRegion) => void): {
|
|
345
|
+
spine: FrameSpine;
|
|
346
|
+
pump: () => void;
|
|
347
|
+
};
|
|
348
|
+
//#endregion
|
|
349
|
+
//#region src/pipelines/glyph-pipelines.d.ts
|
|
350
|
+
/**
|
|
351
|
+
* Per-bake glyph parameters uniform. 16 bytes (one vec4f slot):
|
|
352
|
+
* pxRange — atlas distance range in texels (`GlyphAtlas.pxRange`).
|
|
353
|
+
* atlasSize — atlas side length in texels (`GlyphAtlas.atlasSize`).
|
|
354
|
+
* _pad0, _pad1 — reserved (keep the uniform 16-byte aligned).
|
|
355
|
+
*
|
|
356
|
+
* Both values are constant for one atlas, so one upload per bake is enough.
|
|
357
|
+
*/
|
|
358
|
+
declare const GLYPH_PARAMS_FLOATS = 4;
|
|
359
|
+
declare const GLYPH_PARAMS_BYTES: number;
|
|
360
|
+
/**
|
|
361
|
+
* Bind-group layouts + render pipeline for the MSDF glyph bake pass.
|
|
362
|
+
*
|
|
363
|
+
* `cameraLayout` / `instanceLayout` are structurally identical to
|
|
364
|
+
* `Pipelines.cameraLayout` / `.instanceLayout`, but they are built HERE
|
|
365
|
+
* (own objects) so the glyph pipeline does not depend on `pipelines.ts` plumbing
|
|
366
|
+
* the bake never receives. The bake builds ONE camera bind group + ONE instances
|
|
367
|
+
* bind group against the bake-shape layouts and reuses them for the glyph draw —
|
|
368
|
+
* see the binding-compatibility note in `tiers/bake.ts`.
|
|
369
|
+
*/
|
|
370
|
+
interface GlyphPipelineSet {
|
|
371
|
+
/** Transparent (alpha-blended, depth-test-only) MSDF glyph pipeline. */
|
|
372
|
+
pipeline: GPURenderPipeline;
|
|
373
|
+
/** `@group(0)` camera uniform layout (Camera { vpCol0, vpCol1, vpCol2 }). */
|
|
374
|
+
cameraLayout: GPUBindGroupLayout;
|
|
375
|
+
/** `@group(1)` read-only instance storage layout. */
|
|
376
|
+
instanceLayout: GPUBindGroupLayout;
|
|
377
|
+
/** `@group(2)` atlas texture + sampler + glyph-params uniform layout. */
|
|
378
|
+
atlasLayout: GPUBindGroupLayout;
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Build the MSDF glyph bake pipeline + its three bind-group layouts.
|
|
382
|
+
*
|
|
383
|
+
* @param device The GPU device (from `root.device`).
|
|
384
|
+
* @param format Bake color-target format (the canvas/ctx format).
|
|
385
|
+
* @param sampleCount MSAA sample count — `config.msaaBakes` (1 | 4). MUST match
|
|
386
|
+
* the shape bake pipelines and the bake pass's color/depth attachments.
|
|
387
|
+
*/
|
|
388
|
+
declare function createGlyphPipeline(device: GPUDevice, format: GPUTextureFormat, sampleCount: number): GlyphPipelineSet;
|
|
389
|
+
//#endregion
|
|
390
|
+
//#region src/tiers/bake.d.ts
|
|
391
|
+
/**
|
|
392
|
+
* Write a 3x3 view-projection `Mat3` plus the viewport/dpr tail into the
|
|
393
|
+
* 10-float Camera uniform layout. `viewportDevW`/`viewportDevH` are the render
|
|
394
|
+
* target's DEVICE-pixel extent; `dpr` the device pixel ratio. They default to 0
|
|
395
|
+
* for the bake path (anchored text re-projects live, not from a bake), where
|
|
396
|
+
* only the matrix prefix is meaningful.
|
|
397
|
+
*/
|
|
398
|
+
declare function packCamera(vp: Mat3, out: Float32Array, viewportDevW?: number, viewportDevH?: number, dpr?: number): void;
|
|
399
|
+
/** Minimal canvas-size source — width/height in device pixels. */
|
|
400
|
+
interface BakeSizeSource {
|
|
401
|
+
readonly width: number;
|
|
402
|
+
readonly height: number;
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Compute the bake-time view projection for a layer.
|
|
406
|
+
*
|
|
407
|
+
* - `world` : `projection(w, h) x scaling(dpr) x viewFromCamera(camera,
|
|
408
|
+
* w/dpr, h/dpr)` (CSS-px camera baseline) — the camera is FROZEN at the
|
|
409
|
+
* instant the bake is requested (the matrix is computed from the camera
|
|
410
|
+
* snapshot passed in; subsequent camera mutation does not alter the
|
|
411
|
+
* returned matrix).
|
|
412
|
+
* - `ui` : `projection(w / dpr, h / dpr)` (CSS-px coordinate space).
|
|
413
|
+
*
|
|
414
|
+
* `cacheSize` overrides the target dimensions (matches v1's `layer.cacheSize`);
|
|
415
|
+
* when absent the canvas `ctx` size is used.
|
|
416
|
+
*/
|
|
417
|
+
declare function bakeViewProjection(space: LayerSpace, ctx: BakeSizeSource, camera: CameraState, dpr: number, cacheSize?: {
|
|
418
|
+
width: number;
|
|
419
|
+
height: number;
|
|
420
|
+
}): Mat3;
|
|
421
|
+
/**
|
|
422
|
+
* Allocate the single-sample bake target texture. The MSAA pass resolves into
|
|
423
|
+
* this; it is also `TEXTURE_BINDING` so the composited result can be sampled
|
|
424
|
+
* back as a sprite by the layer that owns the bake. Caller must `destroy()` it.
|
|
425
|
+
*/
|
|
426
|
+
declare function createBakeTexture(device: GPUDevice, format: GPUTextureFormat, width: number, height: number): GPUTexture;
|
|
427
|
+
/**
|
|
428
|
+
* The pipelines a bake draws with. Built once per (format, sampleCount) and
|
|
429
|
+
* reused across bakes. ALL pipelines share `sampleCount` and `DEPTH_FORMAT` so
|
|
430
|
+
* one MSAA color + one MSAA depth attachment serve every draw in the pass.
|
|
431
|
+
*/
|
|
432
|
+
interface BakePipelineSet {
|
|
433
|
+
/** Opaque shape pass (depth write on, src-replace). MSAA. */
|
|
434
|
+
shapeOpaque: GPURenderPipeline;
|
|
435
|
+
/** Transparent shape pass (depth test only, premultiplied src-over). MSAA. */
|
|
436
|
+
shapeTransparent: GPURenderPipeline;
|
|
437
|
+
/** MSDF glyph pipeline + its bind-group layouts. MSAA. */
|
|
438
|
+
glyph: GlyphPipelineSet;
|
|
439
|
+
/** `@group(0)` camera layout shared by shape + glyph passes. */
|
|
440
|
+
cameraLayout: GPUBindGroupLayout;
|
|
441
|
+
/** `@group(1)` instance-storage layout shared by shape + glyph passes. */
|
|
442
|
+
instanceLayout: GPUBindGroupLayout;
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Build the bake pipeline set for a `(format, sampleCount)` pair.
|
|
446
|
+
*
|
|
447
|
+
* The shape pipelines reuse `assembleShader`'s `vertex` + `opaque` / `transparent`
|
|
448
|
+
* WGSL VERBATIM — the same source the main (single-sample) pipelines use — but
|
|
449
|
+
* are created with `multisample: { count: sampleCount }`. Their `@group(0)` and
|
|
450
|
+
* `@group(1)` layouts are structurally identical to `Pipelines.cameraLayout`
|
|
451
|
+
* / `.instanceLayout` (and to the glyph pipeline's), so ONE camera bind group +
|
|
452
|
+
* ONE instances bind group bind against every draw in the bake pass.
|
|
453
|
+
*
|
|
454
|
+
* @param sampleCount `config.msaaBakes` (1 | 4).
|
|
455
|
+
*/
|
|
456
|
+
declare function createBakePipelines(device: GPUDevice, format: GPUTextureFormat, shader: AssembledShader, sampleCount: number): BakePipelineSet;
|
|
457
|
+
/** Inputs the bake draws into the target. */
|
|
458
|
+
interface BakeInput {
|
|
459
|
+
/** Shape instances (UberPack). May be empty (text-only bake). */
|
|
460
|
+
shapes: UberPack;
|
|
461
|
+
/**
|
|
462
|
+
* Optional glyph instances (GlyphPack). When present and non-empty, drawn
|
|
463
|
+
* after the shapes, inside the SAME render pass.
|
|
464
|
+
*/
|
|
465
|
+
glyphs?: GlyphPack | null;
|
|
466
|
+
/**
|
|
467
|
+
* MSDF atlas the glyphs reference: provides the texture/sampler and the
|
|
468
|
+
* `pxRange` / `atlasSize` for the screen-px AA. Required iff `glyphs` is
|
|
469
|
+
* non-empty.
|
|
470
|
+
*/
|
|
471
|
+
atlas?: BakeAtlas | null;
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* The atlas facts the glyph pass needs. A `GlyphAtlas` (`text/text-font.ts`)
|
|
475
|
+
* satisfies this — `atlas.texture.view` is the bindable `GPUTextureView`.
|
|
476
|
+
*/
|
|
477
|
+
interface BakeAtlas {
|
|
478
|
+
/** Atlas texture (its `.view` is bound to `@group(2) @binding(0)`). */
|
|
479
|
+
readonly texture: {
|
|
480
|
+
readonly view: GPUTextureView;
|
|
481
|
+
};
|
|
482
|
+
/** Distance range in atlas texels. */
|
|
483
|
+
readonly pxRange: number;
|
|
484
|
+
/** Atlas side length in texels (square). */
|
|
485
|
+
readonly atlasSize: number;
|
|
486
|
+
}
|
|
487
|
+
/** Per-call bake options. */
|
|
488
|
+
interface BakeOptions {
|
|
489
|
+
/** Caller-owned single-sample resolve target (from `createBakeTexture`). */
|
|
490
|
+
target: GPUTexture;
|
|
491
|
+
/** Target width in pixels. */
|
|
492
|
+
width: number;
|
|
493
|
+
/** Target height in pixels. */
|
|
494
|
+
height: number;
|
|
495
|
+
/** Frozen view-projection (from `bakeViewProjection`). */
|
|
496
|
+
viewProjection: Mat3;
|
|
497
|
+
/** Clear color for the bake (typically transparent). */
|
|
498
|
+
clearColor: Color;
|
|
499
|
+
/**
|
|
500
|
+
* Sampler for the glyph atlas (filtering). Only needed when glyphs are drawn.
|
|
501
|
+
* The renderer owns/caches this; the bake binds it for one pass.
|
|
502
|
+
*/
|
|
503
|
+
glyphSampler?: GPUSampler | null;
|
|
504
|
+
/**
|
|
505
|
+
* Optional crop rect in DEVICE pixels (the layer's `clipRect` resolved by the
|
|
506
|
+
* caller). When set, the bake pass is scissored to it so the snapshot matches
|
|
507
|
+
* the clipped live render — geometry outside the rect stays the cleared
|
|
508
|
+
* (transparent) background and never composites. `null`/absent = no crop.
|
|
509
|
+
*/
|
|
510
|
+
clip?: {
|
|
511
|
+
x: number;
|
|
512
|
+
y: number;
|
|
513
|
+
width: number;
|
|
514
|
+
height: number;
|
|
515
|
+
} | null;
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Run a single-submit RTT bake pass: `config.msaaBakes`x MSAA -> resolve into
|
|
519
|
+
* `opts.target`. Shapes (opaque reverse, transparent forward) then glyphs
|
|
520
|
+
* (transparent, scene order) are drawn into ONE render pass so the baked
|
|
521
|
+
* snapshot is complete — no live text re-emission needed on top.
|
|
522
|
+
*
|
|
523
|
+
* Scratch MSAA color + depth textures and all per-bake GPU buffers are created
|
|
524
|
+
* and destroyed within this call. The camera uniform / instances buffers /
|
|
525
|
+
* glyph-params uniform are uploaded fresh each bake (a bake is a rare,
|
|
526
|
+
* settle-time event — not a per-frame cost).
|
|
527
|
+
*/
|
|
528
|
+
declare function runBakePass(device: GPUDevice, config: RendererConfig, input: BakeInput, pipelines: BakePipelineSet, opts: BakeOptions): void;
|
|
529
|
+
//#endregion
|
|
530
|
+
//#region src/damage/scene-root.d.ts
|
|
531
|
+
interface PaintCtx {
|
|
532
|
+
/** The in-scope scene layer that descendant nodes paint into. */
|
|
533
|
+
readonly layer: Layer;
|
|
534
|
+
/** Per-frame inputs for the `project` hook (camera / group / dpr / viewport). */
|
|
535
|
+
readonly projectOpts: ProjectOptions;
|
|
536
|
+
}
|
|
537
|
+
declare abstract class GpuSceneNode extends SceneNode {
|
|
538
|
+
/**
|
|
539
|
+
* Emit this node's geometry into `ctx.layer`. Called with the live frame
|
|
540
|
+
* context — NO `gpuRoot()` re-walk. Implementations push shapes via the
|
|
541
|
+
* `Layer.pushXxx` API and may read `ctx.projectOpts` for screen-pixel math.
|
|
542
|
+
*/
|
|
543
|
+
abstract drawSelf(ctx: PaintCtx): void;
|
|
544
|
+
/**
|
|
545
|
+
* Paint this node's WHOLE subtree (self + every descendant), threading the
|
|
546
|
+
* SAME `ctx`. This is the unculled entry used when a caller wants to force a
|
|
547
|
+
* subtree repaint regardless of damage. The damage-tracked frame path does NOT
|
|
548
|
+
* call this — it uses {@link GpuSceneRoot}'s recursive cull, which tests every
|
|
549
|
+
* node's bounds against the damage regions and prunes missed subtrees. Keeping
|
|
550
|
+
* the cull out of `paint` means a hit subtree still walks fully, while the cull
|
|
551
|
+
* decides which subtrees to enter.
|
|
552
|
+
*/
|
|
553
|
+
paint(ctx: unknown): void;
|
|
554
|
+
/**
|
|
555
|
+
* Project this node's bounds to a screen-pixel `FrameRect` via the T21
|
|
556
|
+
* `project` hook. Call this BEFORE `markDamaged` when a node authors in a
|
|
557
|
+
* non-`ui` space so the emitted damage rect already lives in the screen-pixel
|
|
558
|
+
* space the cull + scissor share. (For a plain `ui` scene graph the bounds are
|
|
559
|
+
* already CSS px and `markDamaged` may be called directly.)
|
|
560
|
+
*
|
|
561
|
+
* `space` defaults to `"ui"` — the natural space for a CSS-pixel scene graph
|
|
562
|
+
* whose `bounds` are authored in screen pixels.
|
|
563
|
+
*/
|
|
564
|
+
protected projectBounds(ctx: PaintCtx, space?: "world" | "ui"): FrameRect;
|
|
565
|
+
/** Mark a visual-only change: repaint, no data/layout rebuild. */
|
|
566
|
+
invalidatePaint(rect?: Rect): void;
|
|
567
|
+
/** Mark a layout change (bounds/children moved). Triggers doLayout + paint. */
|
|
568
|
+
invalidateLayout(rect?: Rect): void;
|
|
569
|
+
/** Mark a data change (inputs replaced). Triggers rebuildData → layout → paint. */
|
|
570
|
+
invalidateData(rect?: Rect): void;
|
|
571
|
+
}
|
|
572
|
+
interface GpuSceneRootOptions {
|
|
573
|
+
/**
|
|
574
|
+
* Scheduler for the FrameSpine (and the base SceneRoot's inert channel).
|
|
575
|
+
* Defaults to a RAFScheduler in production; pass a ManualScheduler (or use a
|
|
576
|
+
* test spine) for deterministic unit tests.
|
|
577
|
+
*/
|
|
578
|
+
scheduler?: Scheduler;
|
|
579
|
+
/**
|
|
580
|
+
* Repaint every node on any damage and ignore the per-node cull. Default
|
|
581
|
+
* `false` — the recursive cull is the whole point. Set `true` only as a
|
|
582
|
+
* cost-comparison baseline or when the scene layer is cleared+re-emitted whole
|
|
583
|
+
* each frame and culled-out nodes would otherwise vanish.
|
|
584
|
+
*/
|
|
585
|
+
fullFrame?: boolean;
|
|
586
|
+
/** Optional per-frame CPU timing hook (layout vs paint, painted-node count). */
|
|
587
|
+
onFrameTiming?: (timing: SpatialFrameTiming) => void;
|
|
588
|
+
/**
|
|
589
|
+
* Per-frame `project` inputs (camera / group / dpr / viewport). A thunk so the
|
|
590
|
+
* caller can update the camera/dpr/viewport between frames without rebuilding
|
|
591
|
+
* the root. The returned options seed `PaintCtx.projectOpts` and the `viewKey`
|
|
592
|
+
* fold. Required so the screen-pixel project space matches the renderer.
|
|
593
|
+
*/
|
|
594
|
+
projectOpts: () => ProjectOptions;
|
|
595
|
+
/**
|
|
596
|
+
* Active group transforms folded into the `viewKey`. A thunk re-read each
|
|
597
|
+
* frame: whenever ANY returned group's compounded transform changes, the
|
|
598
|
+
* folded key changes and the renderer forces a full frame (no ghosting). Pass
|
|
599
|
+
* the groups whose moves should invalidate the partial path.
|
|
600
|
+
*/
|
|
601
|
+
groups?: () => readonly Group[];
|
|
602
|
+
/**
|
|
603
|
+
* Extra data-domain key folded into the `viewKey` after the group transforms.
|
|
604
|
+
* Change it whenever a non-transform input (a plot's data domain, a palette)
|
|
605
|
+
* changes the whole-canvas mapping. A thunk re-read each frame.
|
|
606
|
+
*/
|
|
607
|
+
dataKey?: () => string;
|
|
608
|
+
}
|
|
609
|
+
declare class GpuSceneRoot extends SceneRoot implements SpatialRenderer2D {
|
|
610
|
+
/** The scene layer that descendant nodes paint into. */
|
|
611
|
+
readonly layer: Layer;
|
|
612
|
+
/** The underlying v3 WebGPU renderer. */
|
|
613
|
+
readonly gpu: Renderer2D;
|
|
614
|
+
/** Reverse-z pointer routing with per-pointerId capture/drag/hover. */
|
|
615
|
+
readonly pointer: PointerRouter;
|
|
616
|
+
/**
|
|
617
|
+
* When `true`, repaint every node on any damage and ignore the per-node cull
|
|
618
|
+
* (set from {@link GpuSceneRootOptions.fullFrame}, default `false`).
|
|
619
|
+
* Passed through to `endFrame()` so the renderer does a full repaint.
|
|
620
|
+
*/
|
|
621
|
+
fullFrame: boolean;
|
|
622
|
+
private readonly _spine;
|
|
623
|
+
private readonly _projectOpts;
|
|
624
|
+
private readonly _groups?;
|
|
625
|
+
private readonly _dataKey?;
|
|
626
|
+
/** Our own copy of the timing hook — the base keeps `onFrameTiming` private,
|
|
627
|
+
* and we override `_renderFrame`, so we read it directly. */
|
|
628
|
+
private readonly _onFrameTiming?;
|
|
629
|
+
/** Damage regions handed to the in-flight `beginFrame`; consumed by `endFrame`. */
|
|
630
|
+
private _currentRegions;
|
|
631
|
+
constructor(gpu: Renderer2D, layer: Layer, options: GpuSceneRootOptions);
|
|
632
|
+
/** Stash the frame's damage regions and clear the scene layer. */
|
|
633
|
+
beginFrame(regions: readonly Rect[]): void;
|
|
634
|
+
/**
|
|
635
|
+
* Submit one v3 GPU frame. Each damage `Rect` maps to ONE `Bounds2D` region
|
|
636
|
+
* (1:1 — never unioned), so the renderer's per-rect scissor path repaints each
|
|
637
|
+
* disjoint rect independently. `viewKey` folds the active group transforms +
|
|
638
|
+
* data key so a group move forces a full frame.
|
|
639
|
+
*/
|
|
640
|
+
endFrame(): void;
|
|
641
|
+
/**
|
|
642
|
+
* Called by `SceneNode.markDamaged` (structural-type contract:
|
|
643
|
+
* `typeof root._emitDamage === "function"`). Routes the damage into the
|
|
644
|
+
* FrameSpine instead of the base SceneRoot's own channel, so a single
|
|
645
|
+
* coalesced flush per frame drives `_renderFrameV3`.
|
|
646
|
+
*/
|
|
647
|
+
_emitDamage(damage: Damage): void;
|
|
648
|
+
/** Force a full repaint of the whole root bounds on the next scheduled frame. */
|
|
649
|
+
requestPaint(): void;
|
|
650
|
+
/**
|
|
651
|
+
* Mark a paint-damage region directly on the root (CSS px). Useful when an
|
|
652
|
+
* external surface (a resize, a host overlay) damages a screen rect that is
|
|
653
|
+
* not owned by a single node. Routes through the spine like any node damage.
|
|
654
|
+
*/
|
|
655
|
+
requestPaintRegion(rect: Rect): void;
|
|
656
|
+
/**
|
|
657
|
+
* Run one frame from a coalesced dirty set. Mirrors the base SceneRoot
|
|
658
|
+
* pipeline (rebuildData on data-damage, doLayout on non-paint damage) but
|
|
659
|
+
* replaces the one-level `_paintCulled` with the recursive cull and uses our
|
|
660
|
+
* own `beginFrame`/`endFrame`. `fullFrame` repaints the whole bounds.
|
|
661
|
+
*/
|
|
662
|
+
private _renderFrameV3;
|
|
663
|
+
/**
|
|
664
|
+
* Recursive damage cull — the v3 fix for v1's one-level cull. For each child
|
|
665
|
+
* of `node`: if its bounds miss EVERY region, prune the entire subtree (no
|
|
666
|
+
* `drawSelf`, no recursion into ITS children). On a hit, paint the child via
|
|
667
|
+
* `drawSelf(ctx)` and recurse — so the SAME per-region test applies at every
|
|
668
|
+
* depth, not just the root's direct children. A leaf deep in a large container
|
|
669
|
+
* therefore repaints only the nodes whose AABB actually touches a damage rect,
|
|
670
|
+
* and the cost scales with the damaged area, not the scene size.
|
|
671
|
+
*
|
|
672
|
+
* Pruning a missed subtree is sound: a node's bounds are expected to bound its
|
|
673
|
+
* own painted geometry (and, when it `clipsOverflow`, its descendants too), so
|
|
674
|
+
* a subtree whose AABB misses every damage rect contributes nothing inside any
|
|
675
|
+
* rect. (Nodes that paint outside their declared bounds must widen `bounds`.)
|
|
676
|
+
*
|
|
677
|
+
* Note: this does NOT call `GpuSceneNode.paint` (which would recurse
|
|
678
|
+
* unconditionally and defeat the deep cull) — it owns the descent itself.
|
|
679
|
+
*/
|
|
680
|
+
private _paintCulledRecursive;
|
|
681
|
+
/**
|
|
682
|
+
* Composite `viewKey`: fold every active group's compounded transform, then
|
|
683
|
+
* the data key, into one string. Handed to `render(..., { viewKey })`; a
|
|
684
|
+
* change flips the renderer's view fingerprint and forces a full frame.
|
|
685
|
+
*/
|
|
686
|
+
private _viewKey;
|
|
687
|
+
/**
|
|
688
|
+
* Dispatch a pointer event through the {@link PointerRouter}: `down` hit-tests
|
|
689
|
+
* (reverse-z, last-child-wins, depth-first — the inherited `SceneRoot.hitTest`)
|
|
690
|
+
* and captures by `pointerId`; `move`/`up`/`cancel` route to the captured node
|
|
691
|
+
* (or, for an uncaptured `move`, re-hit). Returns the receiving node or null.
|
|
692
|
+
*/
|
|
693
|
+
dispatchPointer(e: SpatialPointerEvent): SceneNode | null;
|
|
694
|
+
/** Dispose the frame spine (removes the subscriber, cancels any pending flush). */
|
|
695
|
+
dispose(): void;
|
|
696
|
+
}
|
|
697
|
+
/** Convert a spatial `Rect` to the renderer's `Bounds2D` damage-region shape. */
|
|
698
|
+
declare function rectToBounds(r: Rect): Bounds2D;
|
|
699
|
+
//#endregion
|
|
700
|
+
//#region src/pipelines/composite-pipelines.d.ts
|
|
701
|
+
/**
|
|
702
|
+
* Composite-quad uniform: destination NDC rect + source UV rect. 8 floats / 32
|
|
703
|
+
* bytes (two vec4f):
|
|
704
|
+
* rectNdc = (ndcMinX, ndcMinY, ndcMaxX, ndcMaxY)
|
|
705
|
+
* rectUv = (uvMinX, uvMinY, uvMaxX, uvMaxY)
|
|
706
|
+
*/
|
|
707
|
+
declare const COMPOSITE_UNIFORM_FLOATS = 8;
|
|
708
|
+
declare const COMPOSITE_UNIFORM_BYTES: number;
|
|
709
|
+
/** Bind-group layout + pipeline for the textured-quad composite. */
|
|
710
|
+
interface CompositePipelineSet {
|
|
711
|
+
/** Premultiplied-over textured-quad pipeline (no depth, single-sample). */
|
|
712
|
+
pipeline: GPURenderPipeline;
|
|
713
|
+
/**
|
|
714
|
+
* In-MAIN-PASS composite variant (T-ZBAKE). Identical shader / bind-group
|
|
715
|
+
* layout / topology / target blend as {@link pipeline}, but it declares the
|
|
716
|
+
* main pass's depth attachment (WebGPU requires a pipeline's `depthStencil` to
|
|
717
|
+
* match the pass it runs in) while neither writing nor testing depth
|
|
718
|
+
* (`depthWriteEnabled:false`, `depthCompare:"always"`). A bake is a translucent
|
|
719
|
+
* IMAGE — writing depth would let it occlude live geometry through its own
|
|
720
|
+
* transparent texels — so depth is intentionally inert. Drawn at the START of
|
|
721
|
+
* the main pass (full frame) or right after the per-rect erase (partial frame)
|
|
722
|
+
* so the LEADING z-run of bakes lands UNDER all live geometry.
|
|
723
|
+
*/
|
|
724
|
+
pipelineInPass: GPURenderPipeline;
|
|
725
|
+
/**
|
|
726
|
+
* `@group(0)` layout: the composite uniform (vertex+fragment) + the sampled
|
|
727
|
+
* texture + a filtering sampler.
|
|
728
|
+
*/
|
|
729
|
+
layout: GPUBindGroupLayout;
|
|
730
|
+
}
|
|
731
|
+
/**
|
|
732
|
+
* Build the textured-quad composite pipeline + its bind-group layout.
|
|
733
|
+
*
|
|
734
|
+
* @param device WebGPU device.
|
|
735
|
+
* @param format Color-target format (the canvas/backbuffer format).
|
|
736
|
+
*/
|
|
737
|
+
declare function createCompositePipeline(device: GPUDevice, format: GPUTextureFormat): CompositePipelineSet;
|
|
738
|
+
/**
|
|
739
|
+
* Fill the composite uniform for a full-screen bake (the cacheLayer case): the
|
|
740
|
+
* destination covers the whole NDC viewport and the source samples the entire
|
|
741
|
+
* texture, with the Y-UV flipped so a texture rendered in NDC (+y up) reads back
|
|
742
|
+
* upright in UV (v=0 at top).
|
|
743
|
+
*/
|
|
744
|
+
declare function packFullScreenComposite(out: Float32Array): void;
|
|
745
|
+
//#endregion
|
|
746
|
+
//#region src/pipelines/sprite-pipelines.d.ts
|
|
747
|
+
/** A built sprite pipeline pair (opaque + transparent variants share layouts). */
|
|
748
|
+
interface SpritePipelineSet {
|
|
749
|
+
/** Opaque variant: depth-write on, src-replace, alpha-discard at 0.5. */
|
|
750
|
+
opaque: GPURenderPipeline;
|
|
751
|
+
/** Transparent variant: depth-test only, premultiplied ALPHA_BLEND. */
|
|
752
|
+
transparent: GPURenderPipeline;
|
|
753
|
+
/** `@group(0)` camera layout — 6-float Camera, binds the v3 camera slots. */
|
|
754
|
+
cameraLayout: GPUBindGroupLayout;
|
|
755
|
+
/** `@group(1)` read-only-storage layout over the external sprite buffer. */
|
|
756
|
+
bufferLayout: GPUBindGroupLayout;
|
|
757
|
+
/** `@group(2)` texture + sampler layout. */
|
|
758
|
+
textureLayout: GPUBindGroupLayout;
|
|
759
|
+
/** Per-draw `InteropDraw { z }` uniform layout (`@group(N)`). */
|
|
760
|
+
drawLayout: GPUBindGroupLayout;
|
|
761
|
+
/** Group index of the {@link drawLayout} — 3 for sprites. */
|
|
762
|
+
drawGroup: number;
|
|
763
|
+
}
|
|
764
|
+
/**
|
|
765
|
+
* Built sprite OIT emit pipeline. Appends premultiplied `(rgba, depth)` fragments
|
|
766
|
+
* into the shared A-buffer at `@group(3)` instead of alpha-blending to the color
|
|
767
|
+
* target. The existing opaque/transparent sprite pipelines stay; Phase 4 routes
|
|
768
|
+
* transparent sprites to this pipeline when OIT is enabled.
|
|
769
|
+
*/
|
|
770
|
+
interface SpriteOitEmitPipeline {
|
|
771
|
+
/** The single pipeline: `writeMask:0`, depth-test-only, OIT A-buffer append. */
|
|
772
|
+
pipeline: GPURenderPipeline;
|
|
773
|
+
/** `@group(0)` camera layout — 6-float Camera, binds v3 camera slots. */
|
|
774
|
+
cameraLayout: GPUBindGroupLayout;
|
|
775
|
+
/** `@group(1)` read-only-storage layout over the `SpriteSchema` buffer. */
|
|
776
|
+
bufferLayout: GPUBindGroupLayout;
|
|
777
|
+
/** `@group(2)` texture + sampler layout (one texture per draw command). */
|
|
778
|
+
textureLayout: GPUBindGroupLayout;
|
|
779
|
+
/**
|
|
780
|
+
* `@group(3)` A-buffer layout (build-style: heads + slots `storage`, viewport
|
|
781
|
+
* `uniform`). Created via {@link ABuffer.buildLayoutAt}(3).
|
|
782
|
+
*/
|
|
783
|
+
abufferLayout: GPUBindGroupLayout;
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* WGSL for the sprite OIT emit pipeline. The VS reads per-instance `sp.order`
|
|
787
|
+
* (float offset 15, stamped by Phase 1 {@link SpritePack.stampOrder}) for
|
|
788
|
+
* `out.pos.z` so sprites z-interleave with shapes/glyphs in true push order.
|
|
789
|
+
* The FS computes premultiplied color, calls `oitAppend`, and returns `vec4f(0.0)`
|
|
790
|
+
* into a `writeMask:0` target.
|
|
791
|
+
*
|
|
792
|
+
* @internal — exported for unit testing only.
|
|
793
|
+
*/
|
|
794
|
+
declare function spriteOitEmitWgsl(): string;
|
|
795
|
+
/**
|
|
796
|
+
* Build the SPRITE OIT emit pipeline. Samples the per-command texture at
|
|
797
|
+
* `@group(2)` and appends premultiplied fragments into the shared A-buffer at
|
|
798
|
+
* `@group(3)`. Writes no color (`writeMask:0`); depth-tests but does not write
|
|
799
|
+
* (`depthWriteEnabled:false`).
|
|
800
|
+
*
|
|
801
|
+
* Bind-group convention (per design.md):
|
|
802
|
+
* `@group(0)` — 6-float Camera (v3 camera slots)
|
|
803
|
+
* `@group(1)` — read-only-storage `SpriteSchema` array
|
|
804
|
+
* `@group(2)` — texture + sampler (one per draw command)
|
|
805
|
+
* `@group(3)` — A-buffer (via `abuffer.buildLayoutAt(3)` / `buildBindGroupFor()`)
|
|
806
|
+
*
|
|
807
|
+
* @param device WebGPU device.
|
|
808
|
+
* @param format Color attachment format (must match the render pass; `writeMask:0`
|
|
809
|
+
* means nothing is written, but the target format must still be declared).
|
|
810
|
+
* @param abuffer {@link ABuffer} instance — provides `buildLayoutAt(3)` for the
|
|
811
|
+
* pipeline layout and `buildBindGroupFor()` for per-pass bind.
|
|
812
|
+
* @param sampleCount Multi-sample count for the main render pass (1 for live OIT —
|
|
813
|
+
* NOT the bake MSAA count; see design.md §Risks "MSAA mismatch").
|
|
814
|
+
*/
|
|
815
|
+
declare function createSpriteOitEmitPipeline(device: GPUDevice, format: GPUTextureFormat, abuffer: ABuffer, sampleCount?: number): SpriteOitEmitPipeline;
|
|
816
|
+
/**
|
|
817
|
+
* Build the SPRITE pipeline pair (v1 64 B `SpriteSchema` external buffer
|
|
818
|
+
* + a texture). `@group(0)` = 6-float Camera (v3 slots), `@group(1)` =
|
|
819
|
+
* read-only-storage `SpriteSchema` array, `@group(2)` = texture + sampler,
|
|
820
|
+
* `@group(3)` = `InteropDraw { z }`. Drives the `Layer.pushSprite` path
|
|
821
|
+
* (`_encodeLayerSprites`) — textured quads accumulated CPU-side into a per-layer
|
|
822
|
+
* `SpritePack` (e.g. plot's heatmap geom drawing a compute texture).
|
|
823
|
+
*/
|
|
824
|
+
declare function createSpritePipelines(device: GPUDevice, format: GPUTextureFormat): SpritePipelineSet;
|
|
825
|
+
//#endregion
|
|
826
|
+
//#region src/pipelines/instance-pipelines.d.ts
|
|
827
|
+
/** Per-buffer draw uniform: a single near-z, 16 B (one vec4f slot). Shared
|
|
828
|
+
* wire format with `sprite-pipelines.ts` (same `InteropDraw` WGSL struct). */
|
|
829
|
+
declare const INSTANCE_DRAW_FLOATS = 4;
|
|
830
|
+
declare const INSTANCE_DRAW_BYTES: number;
|
|
831
|
+
/** A built external-instance pipeline pair (opaque + transparent share layouts). */
|
|
832
|
+
interface InstancePipelineSet {
|
|
833
|
+
/** Opaque variant: depth-write on, src-replace, alpha-discard at 0.5. */
|
|
834
|
+
opaque: GPURenderPipeline;
|
|
835
|
+
/** Transparent variant: depth-test only, premultiplied ALPHA_BLEND. */
|
|
836
|
+
transparent: GPURenderPipeline;
|
|
837
|
+
/** `@group(0)` camera layout — 6-float Camera, binds the v3 camera slots. */
|
|
838
|
+
cameraLayout: GPUBindGroupLayout;
|
|
839
|
+
/** `@group(1)` read-only-storage layout over the external `Instance` buffer. */
|
|
840
|
+
bufferLayout: GPUBindGroupLayout;
|
|
841
|
+
/** Per-buffer `InteropDraw { z }` uniform layout (`@group(2)`). */
|
|
842
|
+
drawLayout: GPUBindGroupLayout;
|
|
843
|
+
/** Group index of {@link drawLayout} — always 2 for instances. */
|
|
844
|
+
drawGroup: number;
|
|
845
|
+
}
|
|
846
|
+
/**
|
|
847
|
+
* Build the EXTERNAL-INSTANCE pipeline pair (v3 64 B `Instance` external buffer).
|
|
848
|
+
*
|
|
849
|
+
* `@group(0)` = 6-float Camera (binds v3 camera slots), `@group(1)` =
|
|
850
|
+
* read-only-storage `array<InstanceStruct>`, `@group(2)` = `InteropDraw { z }`.
|
|
851
|
+
* Opaque + transparent variants share all three layouts.
|
|
852
|
+
*/
|
|
853
|
+
declare function createInstancePipelines(device: GPUDevice, format: GPUTextureFormat): InstancePipelineSet;
|
|
854
|
+
/** A built external-instance OIT-emit pipeline. Appends premultiplied `(rgba,
|
|
855
|
+
* depth)` into the shared A-buffer at `@group(3)` instead of blending. */
|
|
856
|
+
interface InstanceOitEmitPipeline {
|
|
857
|
+
/** The single pipeline: `writeMask:0`, depth-test-only, OIT A-buffer append. */
|
|
858
|
+
pipeline: GPURenderPipeline;
|
|
859
|
+
/** `@group(0)` camera layout — 6-float Camera, binds v3 camera slots. */
|
|
860
|
+
cameraLayout: GPUBindGroupLayout;
|
|
861
|
+
/** `@group(1)` read-only-storage layout over the external `Instance` buffer. */
|
|
862
|
+
bufferLayout: GPUBindGroupLayout;
|
|
863
|
+
/** `@group(2)` per-buffer `InteropDraw { z }` uniform layout. */
|
|
864
|
+
drawLayout: GPUBindGroupLayout;
|
|
865
|
+
/** `@group(3)` A-buffer layout (via {@link ABuffer.buildLayoutAt}(3)). */
|
|
866
|
+
abufferLayout: GPUBindGroupLayout;
|
|
867
|
+
}
|
|
868
|
+
/**
|
|
869
|
+
* WGSL for the external-instance OIT-emit pipeline. Same SDF switch as the
|
|
870
|
+
* opaque/transparent variants; the FS calls `oitAppend` with the premultiplied
|
|
871
|
+
* `outColor` and the per-buffer near-z (`in.pos.z`, written from `drawParams.z`
|
|
872
|
+
* in the vertex stage) so external instances composite through the unchanged
|
|
873
|
+
* resolve pass.
|
|
874
|
+
*
|
|
875
|
+
* @internal — exported for unit testing only.
|
|
876
|
+
*/
|
|
877
|
+
declare function instanceOitEmitWgsl(): string;
|
|
878
|
+
/**
|
|
879
|
+
* Build the EXTERNAL-INSTANCE OIT-emit pipeline. Shares the vertex stage with
|
|
880
|
+
* {@link createInstancePipelines} (so the geometry expansion + near-z stamp are
|
|
881
|
+
* identical) and appends premultiplied fragments into the shared A-buffer at
|
|
882
|
+
* `@group(3)`. Writes no color (`writeMask:0`); depth-tests but does not write.
|
|
883
|
+
*
|
|
884
|
+
* Bind-group convention:
|
|
885
|
+
* `@group(0)` — 6-float Camera (v3 camera slots)
|
|
886
|
+
* `@group(1)` — read-only-storage `array<InstanceStruct>`
|
|
887
|
+
* `@group(2)` — `InteropDraw { z }` per-buffer near-z
|
|
888
|
+
* `@group(3)` — A-buffer (via `abuffer.buildLayoutAt(3)` / `buildBindGroupFor()`)
|
|
889
|
+
*/
|
|
890
|
+
declare function createInstanceOitEmitPipeline(device: GPUDevice, format: GPUTextureFormat, abuffer: ABuffer, sampleCount?: number): InstanceOitEmitPipeline;
|
|
891
|
+
//#endregion
|
|
892
|
+
export { ABuffer, ARC_FLOATS, BUILD_SPATIAL_HASH_WGSL, type BakeAtlas, type BakeInput, type BakeOptions, type BakePipelineSet, type BakeSizeSource, CLEAR_SPATIAL_HASH_WGSL, COMPOSITE_UNIFORM_BYTES, COMPOSITE_UNIFORM_FLOATS, CURVE_CAP_BUTT, CURVE_CAP_ROUND, CURVE_CAP_SQUARE, CURVE_FLOATS, type CompositePipelineSet, type CullCommand, type CullResult, type CulledRange, type DebugSize, type DebugSpace, type DirtyRange, type DirtyRegion, type DrawCommand, FRAME_RING_CAPACITY, FULL_FRAME_TILE_FRACTION, type FrameDebugListener, FrameRing, FrameSpine, type FrameSpineOptions, type FrameSummary, GLYPH_PARAMS_BYTES, GLYPH_PARAMS_FLOATS, type GPUOwner, type GlyphPipelineSet, GpuSceneNode, GpuSceneRoot, type GpuSceneRootOptions, HEAD_BYTES, INSTANCE_DRAW_BYTES, INSTANCE_DRAW_FLOATS, type InstanceOitEmitPipeline, type InstancePipelineSet, type LayerSummary, type OITPipelineDeps, type OITPipelines, type PaintCtx, type RectXYWH, RegionIndex, RendererDebug, type RetainedRecord, RetainedTier, SEGMENT_FLOATS, SHAPE_BYTES, SHAPE_CIRCLE, SHAPE_ELLIPSE, SHAPE_RECT, SLOT_BYTES, SPATIAL_HASH_PARAMS_BYTES, SPATIAL_HASH_PARAMS_WGSL, SPATIAL_HASH_WORKGROUP_SIZE, type ScreenAABB, ShapeSchema, type SpaceMap, type SpatialGrid, type SpatialGridOptions, type SpatialGridResult, type SpatialRange, type SpriteOitEmitPipeline, type SpritePipelineSet, SpriteSchema, TRIANGLE_FLOATS, TileGrid, UberPack, bakeViewProjection, blitBackbuffer, buildSpatialGrid, captureSpaceMap, convertRect, createBakePipelines, createBakeTexture, createCompositePipeline, createGlyphPipeline, createInstanceOitEmitPipeline, createInstancePipelines, createOITPipelines, createSpriteOitEmitPipeline, createSpritePipelines, createTestRenderer, createTestSpine, cullCommands, dirtyTileSet, instanceOitEmitWgsl, nextByteCapacity, nextPow2, packCamera, packFullScreenComposite, queryRanges, rectToBounds, repackArcs, repackCurves, repackSegments, resolveRoot, runBakePass, spriteOitEmitWgsl, summarize, worldFrustumAabb };
|