@taole/giftstage 0.1.32 → 0.1.34

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.
@@ -19,8 +19,26 @@ export declare class WebGL1Backend implements GPUBackend {
19
19
  constructor(canvas: HTMLCanvasElement, antialias?: boolean);
20
20
  init(): Promise<void>;
21
21
  private initPrograms;
22
- private getUniforms;
23
- beginFrame(): void;
22
+ private makeProgramEntry;
23
+ /**
24
+ * `u_transform` on `svga-batch-premul` defaults to identity; shape draws overwrite it and
25
+ * this re-upload restores it for the next plain batch draw. Uploaded unconditionally because
26
+ * renderers mutate their scratch transform arrays in place (reference never changes).
27
+ */
28
+ private applyIdentityTransform;
29
+ /** Sampler uniforms are constant per shader; set them once instead of every draw. */
30
+ private bindProgramSamplers;
31
+ /**
32
+ * `u_transform` always comes from a renderer-owned scratch `Float32Array` that gets
33
+ * overwritten in place per draw, so its reference never changes — only its content does.
34
+ * Skipping the upload based on reference equality would silently apply a stale transform,
35
+ * so unlike `u_projection` (whose cached arrays are replaced, not mutated, on resize) it is
36
+ * uploaded unconditionally here.
37
+ */
38
+ private applyDrawUniforms;
39
+ beginFrame(options?: {
40
+ stencil?: boolean;
41
+ }): void;
24
42
  endFrame(): void;
25
43
  private getBufferSourceByteLength;
26
44
  private normalizeIndexSource;
@@ -34,8 +34,28 @@ export declare class WebGL2Backend implements GPUBackend {
34
34
  constructor(canvas: HTMLCanvasElement, antialias?: boolean);
35
35
  init(): Promise<void>;
36
36
  private initPrograms;
37
- private getUniforms;
38
- beginFrame(): void;
37
+ private makeProgramEntry;
38
+ /**
39
+ * `u_transform` on `svga-batch-premul` defaults to identity; shape draws overwrite it and
40
+ * this re-upload restores it for the next plain batch draw. Uploaded unconditionally because
41
+ * renderers mutate their scratch transform arrays in place (reference never changes).
42
+ */
43
+ private applyIdentityTransform;
44
+ /** Sampler uniforms are constant per shader; set them once instead of every draw. */
45
+ private bindProgramSamplers;
46
+ /** Shared by `draw()` and the instanced draw paths, which only ever supply `u_projection`. */
47
+ private applyProjectionUniform;
48
+ /**
49
+ * `u_transform` always comes from a renderer-owned scratch `Float32Array` that gets
50
+ * overwritten in place per draw, so its reference never changes — only its content does.
51
+ * Skipping the upload based on reference equality would silently apply a stale transform,
52
+ * so unlike `u_projection` (whose cached arrays are replaced, not mutated, on resize) it is
53
+ * uploaded unconditionally here.
54
+ */
55
+ private applyDrawUniforms;
56
+ beginFrame(options?: {
57
+ stencil?: boolean;
58
+ }): void;
39
59
  endFrame(): void;
40
60
  private getBufferSourceByteLength;
41
61
  createBuffer(data: GPUBufferSource, usage: BufferUsage): GPUBufferHandle;
@@ -20,15 +20,30 @@ export declare class WebGPUBackend implements GPUBackend {
20
20
  private texturedBindGroupLayout;
21
21
  private dualTexturedBindGroupLayout;
22
22
  private instancedTextureBindGroupLayout;
23
+ private uniformOnlyBindGroupLayout;
23
24
  /** `device.limits.minUniformBufferOffsetAlignment` (typically 256). */
24
25
  private uniformSlotStride;
25
26
  /** Ring size for per-draw uniforms (`queue.writeBuffer` must not overwrite before draw — see `draw()`). */
26
27
  private readonly maxUniformSlots;
27
28
  /** Resets each `beginFrame`. */
28
29
  private uniformSlotIndex;
30
+ /**
31
+ * CPU-side mirror of `uniformBuffer`, sized identically. Per-draw uniform writes go here
32
+ * instead of `queue.writeBuffer` directly; `endFrame` uploads the touched byte range in one
33
+ * `writeBuffer` call before `submit`, cutting dozens of JS→native calls per frame down to one
34
+ * (writes are visible to the frame's command buffer as long as they land before `submit`).
35
+ * Eagerly sized from the default stride/slot count so it's always usable even before `init()`
36
+ * creates the real GPU buffer (matching size); `init()` reallocates it to the device's actual
37
+ * uniform offset alignment.
38
+ */
39
+ private uniformShadow;
40
+ /** Half-open touched range `[min, max)` in `uniformShadow`; reset each `beginFrame`. */
41
+ private uniformShadowDirtyMin;
42
+ private uniformShadowDirtyMax;
29
43
  /** Skip redundant `setPipeline` / stencil / VB-IB when drawing many quads with the same geometry. */
30
44
  private lastDrawPipeline;
31
45
  private lastStencilRef;
46
+ private frameUsesStencil;
32
47
  private lastVertexBufferId;
33
48
  private lastIndexBufferId;
34
49
  /** Single 160-byte upload for textured draws (replaces multiple `writeBuffer` calls). */
@@ -98,8 +113,9 @@ export declare class WebGPUBackend implements GPUBackend {
98
113
  constructor(canvas: HTMLCanvasElement, adapter: GPUAdapter, wantAntialias?: boolean);
99
114
  init(): Promise<void>;
100
115
  private configureCanvasContext;
101
- private ensureDepthStencil;
116
+ private ensureFrameAttachments;
102
117
  private initPipelines;
118
+ private ensureStencilPipelines;
103
119
  private ensureInstancedProjectionBuffer;
104
120
  private ensureInstancedStorageBuffer;
105
121
  private ensureInstancedSvgaPipeline;
@@ -110,8 +126,14 @@ export declare class WebGPUBackend implements GPUBackend {
110
126
  private createInstancedAlphaVideoPipeline;
111
127
  private createTexturedPipeline;
112
128
  private createClipMaskPipeline;
113
- beginFrame(): void;
129
+ beginFrame(options?: {
130
+ stencil?: boolean;
131
+ }): void;
114
132
  endFrame(): void;
133
+ /** Uploads the touched range of `uniformShadow` in one call — must run before `queue.submit`. */
134
+ private flushUniformShadow;
135
+ /** Records a per-draw uniform write into the CPU shadow instead of touching the GPU buffer. */
136
+ private writeUniformShadow;
115
137
  private ensureInstancedProjection;
116
138
  private gpuBufferUsage;
117
139
  private bufferSourceByteLength;
@@ -10,6 +10,7 @@ export declare class AlphaVideoRenderer {
10
10
  private scratchTransform;
11
11
  private readonly contentSizeScratch;
12
12
  private readonly layoutScratch;
13
+ private readonly clipScratch;
13
14
  private readonly wcDbgFirstDraw;
14
15
  private readonly wcDbgNullWarns;
15
16
  private readonly htmlGate;
@@ -18,6 +19,7 @@ export declare class AlphaVideoRenderer {
18
19
  private readonly processedHtmlGatesScratch;
19
20
  private readonly instancedTexturesScratch;
20
21
  private readonly instancedUniformsScratch;
22
+ constructor(onHtmlVideoFrame?: (video: HTMLVideoElement) => void);
21
23
  private readonly instancedDrawCommand;
22
24
  private readonly geometryTexturesScratch;
23
25
  private readonly geometryUniformsScratch;
@@ -3,6 +3,8 @@ export declare class ImageRenderer {
3
3
  private quadVB;
4
4
  private quadIB;
5
5
  private readonly scratchTransform;
6
+ private readonly layoutScratch;
7
+ private readonly clipScratch;
6
8
  private projCacheW;
7
9
  private projCacheH;
8
10
  private projCache;
@@ -25,8 +25,16 @@ interface SVGAClipPrewarmOptions {
25
25
  }
26
26
  export type SVGAClipMeshMap = Map<string, ClipMeshCache>;
27
27
  export declare class SVGABatchRenderer {
28
+ private readonly onClipMeshReady?;
28
29
  private static readonly INSTANCED_QUAD_VERTS;
29
30
  private static readonly INSTANCED_QUAD_INDICES;
31
+ /**
32
+ * @param onClipMeshReady Called once a background clip mesh build (queued by the hot-path
33
+ * lookup in `getClipMeshForSprite`, see `queueClipMeshBuild`) finishes, so the owner can
34
+ * `invalidate()` a redraw — otherwise a slot whose animation has stopped dirtying may never
35
+ * pick up the now-available precise clip.
36
+ */
37
+ constructor(onClipMeshReady?: (() => void) | undefined);
30
38
  private pool;
31
39
  private vertexBufHandles;
32
40
  private indexBufHandles;
@@ -42,19 +50,42 @@ export declare class SVGABatchRenderer {
42
50
  private clipMeshCache;
43
51
  /** Meshes with lazily created GPU buffers (released in destroy). */
44
52
  private clipMeshesWithBuffers;
53
+ /** Clip paths queued by `queueClipMeshBuild`, awaiting the next background build flush. */
54
+ private pendingClipMeshPaths;
55
+ /** True while a background build (worker or frame-budgeted main thread) is in flight. */
56
+ private clipMeshBuildInFlight;
45
57
  private clipFrameDataScratch;
58
+ /** One-off scratch used only to capture a row for `svgaStaticBatchCache` (P1-10); never read
59
+ * back — each captured entry clones out of it immediately via `.slice()`. */
60
+ private staticBatchCacheRowScratch;
46
61
  private instancedFrameDataScratch;
47
62
  private clipSpriteVertsScratch;
48
63
  private clipSpriteIndicesScratch;
49
- private shapeVertsScratch;
50
64
  private instancedQuadVB;
51
65
  private instancedQuadIB;
52
66
  private whiteTexture;
53
67
  private shapeMeshCache;
68
+ /** Shape meshes with lazily created static GPU buffers (released in destroy). */
69
+ private shapeMeshesWithBuffers;
54
70
  /** Column-major mat4 scratch for clip-mask u_transform (uploaded synchronously per draw). */
55
71
  private clipTransformMat;
72
+ /** Column-major mat4 scratch for the shape draw's u_transform (consumed synchronously). */
73
+ private shapeTransformMat;
56
74
  /** Scissor rect of the slot currently being drawn (canvas pixels), for clip intersection/restore. */
57
75
  private slotScissor;
76
+ /**
77
+ * Whether `slotScissor` is currently the *active* GPU scissor rect (vs. the outer scissor
78
+ * having been intentionally skipped for a slot that cannot draw outside its own box — see
79
+ * `allFramesInsideViewBox`). Drives `restoreSlotScissor`'s fallback target.
80
+ */
81
+ private slotScissorActive;
82
+ /**
83
+ * True while a clip stencil mask written for a shape is still live for its sprite sibling
84
+ * (texture + clip + shape single-write merge, P1-3). `drawClippedSprite` checks this to
85
+ * skip its own stencil write/clear.
86
+ */
87
+ private sharedClipStencilActive;
88
+ private readonly layoutScratch;
58
89
  /** Grow-only SpriteWork pool; cursor resets each flushGroup. */
59
90
  private spriteWorkPool;
60
91
  private spriteWorkCursor;
@@ -63,22 +94,109 @@ export declare class SVGABatchRenderer {
63
94
  private drawGroupScratch;
64
95
  /** Per-entity result of the "any frame has shapes" scan (no-atlas render check). */
65
96
  private entityHasShapesCache;
97
+ /** 0=no clip, 1=scissor-compatible clips only, 2=stencil required, 255=not analyzed. */
98
+ private entityFrameClipModeCache;
99
+ /**
100
+ * Per-entity `ClipMeshCache` lookup aligned with `spriteTable.clipPaths` (index = `clipPathIndex`
101
+ * value), so the hot per-sprite loop can resolve a clip mesh with a direct array read instead of
102
+ * `getSpriteClipPath` (string build) + a string-keyed `Map` lookup. `undefined` = not yet resolved;
103
+ * resolution always goes through `getClipMesh`'s string cache, so array entries never go stale.
104
+ */
105
+ private entityClipMeshArrayCache;
106
+ /**
107
+ * Per-entity result of scanning every sprite×frame layout AABB (in local viewBox space) to see
108
+ * whether it ever draws outside the entity's own viewBox. Entities with vector shapes are
109
+ * excluded (`false`) since shape geometry isn't covered by this scan. Used by `flushGroup` to
110
+ * skip the redundant outer scissor for contain/fill, non-rotated slots — such slots structurally
111
+ * cannot paint outside their own box, so consecutive same-atlas slots can share one scissor-free
112
+ * batch run instead of forcing a draw call per slot.
113
+ */
114
+ private entityAllFramesInsideViewBoxCache;
66
115
  private readonly affineScratch;
116
+ /**
117
+ * Reused DrawCommand/uniforms/textures per rendering branch (never allocated per draw).
118
+ * Backends consume `DrawCommand` synchronously inside `backend.draw`, so mutating these
119
+ * shared objects between calls is safe as long as no reference is retained past the call.
120
+ * Grouped by branch (plain instanced sprite / plain batch sprite / clipped instanced /
121
+ * clipped batch / clip-mask stencil / shape) per the uniform field sets each shader expects.
122
+ */
123
+ private readonly plainInstancedTexturesScratch;
124
+ private readonly plainInstancedUniformsScratch;
125
+ private readonly plainInstancedDrawCommand;
126
+ private readonly plainBatchTexturesScratch;
127
+ private readonly plainBatchUniformsScratch;
128
+ private readonly plainBatchDrawCommand;
129
+ /** `drawClippedSprite`'s stencil-test pass on instanced backends (webgl2/webgpu). */
130
+ private readonly clipInstancedTexturesScratch;
131
+ private readonly clipInstancedUniformsScratch;
132
+ private readonly clipInstancedDrawCommand;
133
+ /** `drawClippedSprite`'s stencil-test pass on the WebGL1 fallback. */
134
+ private readonly clipBatchTexturesScratch;
135
+ private readonly clipBatchUniformsScratch;
136
+ private readonly clipBatchDrawCommand;
137
+ /**
138
+ * Clip-mask stencil write/clear, shared by `drawClippedSprite`'s pass-1 write,
139
+ * `drawShapeFrame`'s non-rect-clip write, and `clearClipStencil`'s clear (all mutate then
140
+ * synchronously `backend.draw`, never concurrently).
141
+ */
142
+ private readonly clipMaskTexturesScratch;
143
+ private readonly clipMaskUniformsScratch;
144
+ private readonly clipMaskDrawCommand;
145
+ /**
146
+ * `drawShapeFrame`'s fill/stroke draw (always the shared white texture). `u_transform`
147
+ * carries the per-frame sprite x slot transform so the static shape mesh stays on the GPU;
148
+ * `u_opacity` carries slot opacity (per-vertex alpha is baked into the mesh).
149
+ */
150
+ private readonly shapeTexturesScratch;
151
+ private readonly shapeUniformsScratch;
152
+ private readonly shapeDrawCommand;
67
153
  beginFrame(backend: GPUBackend): void;
68
154
  flush(backend: GPUBackend, slots: Map<string, GiftSlot>): void;
69
155
  drawSlot(backend: GPUBackend, slot: GiftSlot): void;
70
156
  drawSlots(backend: GPUBackend, slots: Iterable<GiftSlot>): void;
157
+ requiresStencil(slots: Iterable<GiftSlot>): boolean;
158
+ private getFrameClipMode;
71
159
  prewarmClipMeshes(entity: Pick<VideoEntity, 'spriteTable'>, options?: SVGAClipPrewarmOptions): Promise<number>;
160
+ /** Builds every not-yet-cached path in `paths`, worker-first with a frame-budgeted fallback. */
161
+ private buildMissingClipMeshes;
162
+ /**
163
+ * Called from the render-loop hot path (`getClipMeshForSprite`) when a clip path has no cached
164
+ * mesh yet — instead of triangulating synchronously inline, queue it and build it in the
165
+ * background (batched with any other paths queued in the same tick) so the current frame keeps
166
+ * moving. Safe to call repeatedly: paths already cached or already queued are no-ops.
167
+ */
168
+ private queueClipMeshBuild;
169
+ private scheduleClipMeshBuildFlush;
170
+ private flushPendingClipMeshBuilds;
72
171
  hydrateClipMeshes(meshes: SVGAClipMeshMap | null | undefined): void;
73
172
  snapshotClipMeshes(entity: Pick<VideoEntity, 'spriteTable'>): SVGAClipMeshMap;
74
173
  private flushGroup;
75
174
  private acquireSpriteWork;
175
+ /**
176
+ * Like `acquireSpriteWork`, but for a P1-10 static batch cache replay entry: only `handle` and
177
+ * `cachedRow` matter, since `packFrameData` short-circuits on `cachedRow` and every other draw
178
+ * call only reads `work.handle`.
179
+ */
180
+ private acquireCachedSpriteWork;
76
181
  /** Drop object references so removed slots/entities are not retained by the pool. */
77
182
  private releaseSpriteWorks;
183
+ /** Snapshot check for a P1-10 static batch cache: any mismatch means rebuild, never a stale hit. */
184
+ private staticBatchCacheMatches;
185
+ /** Clones the packed row for `work` into a fresh array so it survives past this frame's scratch reuse. */
186
+ private captureStaticBatchCacheEntry;
187
+ /** Replays a P1-10 static batch cache hit: same override/batch branching as the live loop, just
188
+ * sourced from pre-packed rows instead of recomputing layout/transform/texture lookups. */
189
+ private drawFromStaticBatchCache;
78
190
  private getProjection;
79
191
  private drawBatch;
80
192
  private drawBatchInstancedWebGL;
81
193
  private drawClippedSprite;
194
+ /**
195
+ * WebGL1 clipped-sprite draw: pack + upload the quad, then draw with stencil 'test'.
196
+ * Caller is responsible for the surrounding stencil write/clear. Uses separate buffers to
197
+ * avoid WebGPU writeBuffer aliasing with the batch draw.
198
+ */
199
+ private drawBatchSpriteWithStencilTest;
82
200
  /** Rect clip in canvas space intersected with the slot scissor. Returns false when fully clipped. */
83
201
  private applyClipRectScissor;
84
202
  private restoreSlotScissor;
@@ -92,8 +210,23 @@ export declare class SVGABatchRenderer {
92
210
  /** Resolve atlas/override texture into a pooled SpriteWork entry. Returns false when missing. */
93
211
  private resolveSpriteTextureInto;
94
212
  private drawShapeFrame;
213
+ /**
214
+ * Clear the shared mask after a shape-first clip merge once the sprite sibling has drawn.
215
+ * Called by `drawClippedSprite`'s shared-mask branch.
216
+ */
217
+ private clearSharedClipStencil;
218
+ private ensureShapeMeshBuffers;
219
+ private packShapeTransform;
95
220
  private hasAtlasTexture;
96
221
  private hasRenderableSVGA;
222
+ /**
223
+ * True when every sprite frame's transformed layout rect stays within the entity's own
224
+ * viewBox (with a small epsilon for float error). Computed once per entity and cached — the
225
+ * scan touches every sprite×frame combination, which is only affordable as a one-time cost.
226
+ * Entities with vector shapes always return `false`: shape geometry has its own coordinate
227
+ * system that this scan does not cover, so we conservatively keep per-slot scissoring for them.
228
+ */
229
+ private allFramesInsideViewBox;
97
230
  private getWhiteTexture;
98
231
  private getShapeMesh;
99
232
  private appendShape;
@@ -103,6 +236,20 @@ export declare class SVGABatchRenderer {
103
236
  private clamp01;
104
237
  private cleanupPoints;
105
238
  private getClipMesh;
239
+ /**
240
+ * Hot-path clip mesh lookup: reads `clipPathIndex` directly (no string build/compare) and
241
+ * memoizes the resolved mesh per (entity, clipPathIndex) so repeat frames skip the string-keyed
242
+ * `clipMeshCache` lookup too.
243
+ *
244
+ * Returns `undefined` (rather than triangulating inline) when the path hasn't been built yet —
245
+ * prewarm normally finishes before an entity is ever drawn, but with `svgaPrewarmClipMeshes:
246
+ * false`, an aborted/partially failed prewarm, or a slot rendered before prewarm completes, this
247
+ * lookup would otherwise pay a synchronous SVG-parse + triangulate cost inside the render loop.
248
+ * The path is queued for a background build instead (see `queueClipMeshBuild`); callers already
249
+ * treat a falsy return as "no clip this frame" (draw unclipped / assume stencil conservatively),
250
+ * and a redraw is requested once the build lands so later frames clip precisely.
251
+ */
252
+ private getClipMeshForSprite;
106
253
  private ensureVertexBuffer;
107
254
  private ensureIndexBuffer;
108
255
  private ensureClipSpriteVertexBuffer;
@@ -17,6 +17,27 @@ export declare class VAPRenderer {
17
17
  private scratchTransform;
18
18
  private readonly processedHtmlGatesScratch;
19
19
  private readonly vapVertexScratch;
20
+ private readonly layoutScratch;
21
+ private readonly clipScratch;
22
+ /** `drawInstancedVapBatches` grow-only scratch: avoids a fresh array/Map per frame. */
23
+ private readonly instancedListScratch;
24
+ private readonly instancedByTexScratch;
25
+ private readonly instancedTexturesScratch;
26
+ private readonly instancedUniformsScratch;
27
+ private readonly instancedDrawCommand;
28
+ /** `drawGeometry` (plain VAP quad) reused command/uniforms/textures. */
29
+ private readonly geometryTexturesScratch;
30
+ private readonly geometryUniformsScratch;
31
+ private readonly geometryDrawCommand;
32
+ /** `drawMixedGeometry` (composited rgba overlay) reused command/uniforms/textures. */
33
+ private readonly mixedGeometryTexturesScratch;
34
+ private readonly mixedGeometryUniformsScratch;
35
+ private readonly mixedGeometryDrawCommand;
36
+ /** `drawMixedDirectGeometry` per-frame-object overlay reused command/uniforms/textures. */
37
+ private readonly overlayTexturesScratch;
38
+ private readonly overlayUniformsScratch;
39
+ private readonly overlayDrawCommand;
40
+ constructor(onHtmlVideoFrame?: (video: HTMLVideoElement) => void);
20
41
  private projCacheW;
21
42
  private projCacheH;
22
43
  private projCache;
@@ -62,7 +62,9 @@ export interface GPUBackend {
62
62
  /** Maximum supported 2D texture dimension for this backend/device. */
63
63
  readonly maxTextureSize?: number;
64
64
  init(): Promise<void>;
65
- beginFrame(): void;
65
+ beginFrame(options?: {
66
+ stencil?: boolean;
67
+ }): void;
66
68
  endFrame(): void;
67
69
  createBuffer(data: GPUBufferSource, usage: BufferUsage): GPUBufferHandle;
68
70
  updateBuffer(handle: GPUBufferHandle, data: GPUBufferSource, offset?: number): void;
@@ -269,6 +271,47 @@ export interface ResolvedSVGASlotTexture {
269
271
  logicalWidth: number;
270
272
  logicalHeight: number;
271
273
  }
274
+ /**
275
+ * SVGABatchRenderer-internal: one packed instance row for a `stopped` slot's static batch
276
+ * cache (P1-10). `row` mirrors the exact `FRAME_DATA_FLOATS` layout `packFrameData` produces.
277
+ * `isOverride` mirrors whether the sprite used a `svgaSlots` texture override (drawn immediately,
278
+ * breaking any running atlas batch) vs. a normal atlas sprite (batched with same-texture sprites).
279
+ */
280
+ export interface SVGAStaticBatchCacheEntry {
281
+ row: Float32Array;
282
+ handle: GPUTextureHandle;
283
+ isOverride: boolean;
284
+ }
285
+ /**
286
+ * SVGABatchRenderer-internal: cached packed-instance-row list for a `stopped` SVGA slot, plus
287
+ * the exact snapshot of every input it was built from. Any mismatch against the slot's current
288
+ * values invalidates the cache (see `SVGABatchRenderer`'s match check) — favors over-invalidating
289
+ * to guarantee correctness over maximizing hit rate.
290
+ */
291
+ export interface SVGAStaticBatchCache {
292
+ entries: SVGAStaticBatchCacheEntry[];
293
+ /** `VideoEntity` reference the cache was built against (covers spriteTable/atlasRects identity). */
294
+ entity: unknown;
295
+ /** `entity.atlasTextureHandles` (if any) or `entity.atlasTextureHandle`, whichever is in use. */
296
+ atlasHandlesDep: unknown;
297
+ /**
298
+ * `slot.svgaSlotTextures` map reference. `svgaSlots` overrides are immutable post-load, but
299
+ * tracked defensively so a future change to that invariant can't silently go stale here.
300
+ */
301
+ svgaSlotTextures: unknown;
302
+ currentFrame: number;
303
+ opacity: number;
304
+ x: number;
305
+ y: number;
306
+ width: number;
307
+ height: number;
308
+ scale: number;
309
+ rotation: number;
310
+ sx: number;
311
+ sy: number;
312
+ offsetX: number;
313
+ offsetY: number;
314
+ }
272
315
  export interface ResolvedVAPResource {
273
316
  source: CanvasImageSource;
274
317
  width: number;
@@ -318,9 +361,9 @@ export interface GiftStageOptions {
318
361
  } | false;
319
362
  /**
320
363
  * Edge smoothing: WebGL2 uses the context multisampled framebuffer; WebGPU uses 4× MSAA + resolve.
321
- * Default: **true** for WebGL2, **false** for WebGPU (MSAA is expensive with large transparent-video fills).
322
- * Set `true` explicitly when using WebGPU + sharp SVGA edges.
323
- * Many concurrent transparent videos: keep WebGPU `antialias` off (default) and consider lowering {@link resolution}.
364
+ * Default: **true** for WebGL2 and WebGPU.
365
+ * Many concurrent transparent videos: consider setting `antialias: false` and lowering {@link resolution}
366
+ * when fill-rate or framebuffer bandwidth becomes the bottleneck.
324
367
  */
325
368
  antialias?: boolean;
326
369
  /**
@@ -611,6 +654,32 @@ export interface GiftSlot {
611
654
  map: unknown;
612
655
  size: number;
613
656
  };
657
+ /**
658
+ * RenderManager internal: cached coincident-visual-duplicate key + the exact field snapshot it
659
+ * was built from. Rebuilt only when one of those fields actually changes, avoiding a template
660
+ * string array + `join('|')` allocation on every dirty frame for every visible slot.
661
+ */
662
+ coincidentVisualKeyCache?: {
663
+ key: string;
664
+ resourceKey: string;
665
+ frameKey: string;
666
+ svgaSlotKey: string;
667
+ objectFit: string;
668
+ x: number;
669
+ y: number;
670
+ width: number;
671
+ height: number;
672
+ scale: number;
673
+ opacity: number;
674
+ rotation: number | undefined;
675
+ originX: string | number;
676
+ originY: string | number;
677
+ };
678
+ /**
679
+ * SVGABatchRenderer internal: cached packed instance rows for a `stopped` slot with no
680
+ * shape/clip sprites this frame (P1-10). Rebuilt whenever any tracked input changes.
681
+ */
682
+ svgaStaticBatchCache?: SVGAStaticBatchCache | null;
614
683
  onFrame?: (frame: number, gift: GiftHandle) => void;
615
684
  onComplete?: (gift: GiftHandle) => void;
616
685
  }
@@ -652,6 +721,8 @@ export interface VAPFrameObj {
652
721
  mt?: number;
653
722
  frame: [number, number, number, number];
654
723
  mFrame: [number, number, number, number];
724
+ /** `String(srcId)` precomputed once by `buildVAPFrameMap` to avoid per-draw re-stringification. */
725
+ srcKey?: string;
655
726
  }
656
727
  export interface FlyAnimation {
657
728
  giftId: string;
@@ -20,10 +20,11 @@ export declare function resolveGiftTransformOrigin(slot: GiftSlot, out?: GiftTra
20
20
  export declare function fillGiftTransformMatrix(slot: GiftSlot, x: number, y: number, width: number, height: number, out: Float32Array): Float32Array;
21
21
  /** Apply the gift's parent rotation to an existing local-to-stage affine transform. */
22
22
  export declare function rotateGiftAffine(slot: GiftSlot, transform: Affine2D, out?: Affine2D): Affine2D;
23
- /** AABB of a rotated stage-space rectangle, suitable for the backend scissor. */
24
- export declare function getRotatedRectAABB(slot: GiftSlot, x: number, y: number, width: number, height: number): {
23
+ export interface RectAABB {
25
24
  x: number;
26
25
  y: number;
27
26
  w: number;
28
27
  h: number;
29
- };
28
+ }
29
+ /** AABB of a rotated stage-space rectangle, suitable for the backend scissor. */
30
+ export declare function getRotatedRectAABB(slot: GiftSlot, x: number, y: number, width: number, height: number, out?: RectAABB): RectAABB;
@@ -4,6 +4,7 @@
4
4
  * when many `<video>` elements run together (especially with WebGPU texture copies).
5
5
  */
6
6
  export declare class HtmlVideoUploadGate {
7
+ private readonly onFrameAvailable?;
7
8
  private readonly frameInfo;
8
9
  private readonly pending;
9
10
  private readonly lastHandle;
@@ -18,12 +19,16 @@ export declare class HtmlVideoUploadGate {
18
19
  private readonly stagnationLogged;
19
20
  /** When `requestVideoFrameCallback` is missing, fall back to `currentTime` changes (coarser than rVFC). */
20
21
  private readonly lastTimeFallback;
22
+ /** Low-frequency wake-up used to detect browsers that stop delivering rVFC callbacks. */
23
+ private readonly healthTimers;
24
+ constructor(onFrameAvailable?: (video: HTMLVideoElement) => void);
21
25
  /**
22
26
  * Returns true when `updateTexture` should run for this slot: first time, or a new decoded frame was signaled.
23
27
  */
24
28
  preUpload(slotId: string, video: HTMLVideoElement): boolean;
25
29
  getFrameInfo(slotId: string): VideoFrameCallbackMetadata | undefined;
26
30
  private ensureRvf;
31
+ private ensureHealthTimer;
27
32
  private consumeTimeAdvance;
28
33
  private markProgress;
29
34
  private tryRecoverStalledPlayback;
@@ -1,6 +1,65 @@
1
1
  import type { SVGAWorkerMoviePayload } from '../parsers/svga-parser-worker-client';
2
2
  type PersistKind = 'arrayBuffer' | 'text';
3
3
  type MemoryKind = PersistKind | 'json' | 'svga-payload' | 'svga-asset';
4
+ export declare class AsyncLimiter {
5
+ private maxConcurrent;
6
+ private active;
7
+ private queue;
8
+ constructor(maxConcurrent: number);
9
+ setMaxConcurrent(maxConcurrent: number): void;
10
+ run<T>(task: () => Promise<T>): Promise<T>;
11
+ private drain;
12
+ }
13
+ /**
14
+ * Structural estimate; avoids JSON.stringify of the whole sprites tree (main-thread spike).
15
+ *
16
+ * When the worker-binary path is used, `imageBlob` is the *entire* deserialized SGPB buffer
17
+ * (see `deserializeSVGAWorkerPayload`), not just the image bytes — `spriteTable.frameData`/
18
+ * `clipPathIndex`/`shapesIndex` are typed-array *views* sharing that same underlying
19
+ * `ArrayBuffer` (see `readSpriteTable` in `svga-sprite-table-binary.ts`). Adding their
20
+ * `byteLength` again on top of `imageBlob.byteLength` would double-count them, systematically
21
+ * inflating the estimate and causing the memory LRU to evict/re-parse too eagerly. Only add
22
+ * them when they do *not* share `imageBlob`'s buffer (the legacy `images` record path, where
23
+ * the sprite table is a separate allocation).
24
+ */
25
+ export declare function estimateSVGAWorkerPayloadBytes(payload: SVGAWorkerMoviePayload): number;
26
+ export declare class ResourceCacheManager {
27
+ private readonly limiter;
28
+ private readonly pending;
29
+ private readonly memory;
30
+ private memoryBytes;
31
+ private maxMemoryBytes;
32
+ private maxPersistentBytes;
33
+ private dbPromise;
34
+ private persistentTrimPromise;
35
+ configure(opts?: {
36
+ maxConcurrent?: number;
37
+ maxMemoryBytes?: number;
38
+ maxPersistentBytes?: number;
39
+ }): void;
40
+ runLimited<T>(task: () => Promise<T>): Promise<T>;
41
+ fetchArrayBuffer(url: string, signal?: AbortSignal): Promise<ArrayBuffer>;
42
+ fetchText(url: string, signal?: AbortSignal): Promise<string>;
43
+ fetchJSON<T>(url: string, signal?: AbortSignal): Promise<T>;
44
+ getSVGAWorkerPayload(cacheKey: string): SVGAWorkerMoviePayload | null;
45
+ setSVGAWorkerPayload(cacheKey: string, payload: SVGAWorkerMoviePayload): void;
46
+ getMemoryValue<T>(key: string, kind: MemoryKind): T | null;
47
+ setMemoryValue<T>(key: string, kind: MemoryKind, value: T, size: number): void;
48
+ getPersistedArrayBuffer(key: string): Promise<ArrayBuffer | null>;
49
+ setPersistedArrayBuffer(key: string, value: ArrayBuffer): Promise<void>;
50
+ deletePersistedValue(key: string): Promise<void>;
51
+ private getOrCreatePending;
52
+ private getMemory;
53
+ private setMemory;
54
+ private trimMemory;
55
+ private openDb;
56
+ private getPersistent;
57
+ private setPersistent;
58
+ private touchPersistent;
59
+ private schedulePersistentTrim;
60
+ private trimPersistent;
61
+ private deletePersistent;
62
+ }
4
63
  export declare function configureResourceCache(opts?: {
5
64
  maxConcurrent?: number;
6
65
  maxMemoryBytes?: number;
@@ -32,6 +32,53 @@ export declare function decodeH264ToFrames(codec: string, description: ArrayBuff
32
32
  frames: VideoFrame[];
33
33
  timesSec: Float64Array;
34
34
  }>;
35
+ export interface IncrementalH264DecoderCallbacks {
36
+ onFrame: (index: number, frame: VideoFrame) => void;
37
+ onEos?: () => void;
38
+ onError?: (error: unknown) => void;
39
+ }
40
+ /**
41
+ * Playhead-driven incremental H.264 decode: feeds chunks in decode order only up to
42
+ * `needIdx + decodeAheadFrames` (via {@link ensure}), so memory stays bounded by how far ahead
43
+ * of playback the caller asks for instead of decoding (and retaining) the entire clip upfront.
44
+ * Originally the Worker's demux/decode pump (see `webcodecs-mp4.worker.ts`); lifted here so the
45
+ * main-thread WebCodecs fallback (no Worker available, or `useWorker: false`) can reuse the exact
46
+ * same backpressure/rewind semantics instead of decoding the whole file into memory at once —
47
+ * see P1-10 optimization notes / `WebCodecsMp4Playback.createOnMainThread`.
48
+ */
49
+ export declare class IncrementalH264Decoder {
50
+ private readonly decodeOrder;
51
+ private readonly presTimes;
52
+ private readonly timescale;
53
+ private readonly codec;
54
+ private readonly description;
55
+ private readonly duration;
56
+ private readonly callbacks;
57
+ private decoder;
58
+ private nextChunk;
59
+ private chunksFed;
60
+ private targetNeedIdx;
61
+ private lastPostedMax;
62
+ private minPosted;
63
+ private playbackNeedFloor;
64
+ private freeSlots;
65
+ private eosClosed;
66
+ private pumpScheduled;
67
+ private pumping;
68
+ private destroyed;
69
+ constructor(decodeOrder: Sample[], presTimes: Float64Array, timescale: number, codec: string, description: ArrayBuffer | undefined, duration: number, callbacks: IncrementalH264DecoderCallbacks, initialNeedIdx: number);
70
+ /** Raise the decode target and/or update main-thread ring headroom; resumes the pump if idle. */
71
+ ensure(needIdx: number, freeSlots: number): void;
72
+ /** Restart decode from the nearest keyframe at/before t=0 (backward seek / loop wrap). */
73
+ rewind(): void;
74
+ destroy(): void;
75
+ private schedulePump;
76
+ private doRewind;
77
+ private openDecoder;
78
+ private feedOneChunk;
79
+ private finalizeEos;
80
+ private runPump;
81
+ }
35
82
  export declare function frameIndexForTime(timesSec: Float64Array, clipDurationSec: number, t: number): number;
36
83
  export declare function computeClipDurationSeconds(metaDurationSec: number, timesSec: Float64Array, frames: VideoFrame[]): number;
37
84
  /** Clip duration from demux metadata + presentation timeline only (no decoded frames). */