@taole/giftstage 0.1.33 → 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,7 +19,23 @@ 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;
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;
23
39
  beginFrame(options?: {
24
40
  stencil?: boolean;
25
41
  }): void;
@@ -34,7 +34,25 @@ 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;
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;
38
56
  beginFrame(options?: {
39
57
  stencil?: boolean;
40
58
  }): void;
@@ -27,6 +27,19 @@ export declare class WebGPUBackend implements GPUBackend {
27
27
  private readonly maxUniformSlots;
28
28
  /** Resets each `beginFrame`. */
29
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;
30
43
  /** Skip redundant `setPipeline` / stencil / VB-IB when drawing many quads with the same geometry. */
31
44
  private lastDrawPipeline;
32
45
  private lastStencilRef;
@@ -117,6 +130,10 @@ export declare class WebGPUBackend implements GPUBackend {
117
130
  stencil?: boolean;
118
131
  }): void;
119
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;
120
137
  private ensureInstancedProjection;
121
138
  private gpuBufferUsage;
122
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;
@@ -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;
@@ -65,7 +96,60 @@ export declare class SVGABatchRenderer {
65
96
  private entityHasShapesCache;
66
97
  /** 0=no clip, 1=scissor-compatible clips only, 2=stencil required, 255=not analyzed. */
67
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;
68
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;
69
153
  beginFrame(backend: GPUBackend): void;
70
154
  flush(backend: GPUBackend, slots: Map<string, GiftSlot>): void;
71
155
  drawSlot(backend: GPUBackend, slot: GiftSlot): void;
@@ -73,16 +157,46 @@ export declare class SVGABatchRenderer {
73
157
  requiresStencil(slots: Iterable<GiftSlot>): boolean;
74
158
  private getFrameClipMode;
75
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;
76
171
  hydrateClipMeshes(meshes: SVGAClipMeshMap | null | undefined): void;
77
172
  snapshotClipMeshes(entity: Pick<VideoEntity, 'spriteTable'>): SVGAClipMeshMap;
78
173
  private flushGroup;
79
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;
80
181
  /** Drop object references so removed slots/entities are not retained by the pool. */
81
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;
82
190
  private getProjection;
83
191
  private drawBatch;
84
192
  private drawBatchInstancedWebGL;
85
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;
86
200
  /** Rect clip in canvas space intersected with the slot scissor. Returns false when fully clipped. */
87
201
  private applyClipRectScissor;
88
202
  private restoreSlotScissor;
@@ -96,8 +210,23 @@ export declare class SVGABatchRenderer {
96
210
  /** Resolve atlas/override texture into a pooled SpriteWork entry. Returns false when missing. */
97
211
  private resolveSpriteTextureInto;
98
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;
99
220
  private hasAtlasTexture;
100
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;
101
230
  private getWhiteTexture;
102
231
  private getShapeMesh;
103
232
  private appendShape;
@@ -107,6 +236,20 @@ export declare class SVGABatchRenderer {
107
236
  private clamp01;
108
237
  private cleanupPoints;
109
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;
110
253
  private ensureVertexBuffer;
111
254
  private ensureIndexBuffer;
112
255
  private ensureClipSpriteVertexBuffer;
@@ -17,6 +17,26 @@ 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;
20
40
  constructor(onHtmlVideoFrame?: (video: HTMLVideoElement) => void);
21
41
  private projCacheW;
22
42
  private projCacheH;
@@ -271,6 +271,47 @@ export interface ResolvedSVGASlotTexture {
271
271
  logicalWidth: number;
272
272
  logicalHeight: number;
273
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
+ }
274
315
  export interface ResolvedVAPResource {
275
316
  source: CanvasImageSource;
276
317
  width: number;
@@ -613,6 +654,32 @@ export interface GiftSlot {
613
654
  map: unknown;
614
655
  size: number;
615
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;
616
683
  onFrame?: (frame: number, gift: GiftHandle) => void;
617
684
  onComplete?: (gift: GiftHandle) => void;
618
685
  }
@@ -654,6 +721,8 @@ export interface VAPFrameObj {
654
721
  mt?: number;
655
722
  frame: [number, number, number, number];
656
723
  mFrame: [number, number, number, number];
724
+ /** `String(srcId)` precomputed once by `buildVAPFrameMap` to avoid per-draw re-stringification. */
725
+ srcKey?: string;
657
726
  }
658
727
  export interface FlyAnimation {
659
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;
@@ -10,6 +10,19 @@ export declare class AsyncLimiter {
10
10
  run<T>(task: () => Promise<T>): Promise<T>;
11
11
  private drain;
12
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;
13
26
  export declare class ResourceCacheManager {
14
27
  private readonly limiter;
15
28
  private readonly pending;
@@ -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). */
@@ -48,6 +48,8 @@ export declare class WebCodecsMp4Playback {
48
48
  private readonly timesSec;
49
49
  private readonly worker;
50
50
  private readonly workerMsgId;
51
+ /** Main-thread (no-Worker) counterpart of `worker`: same incremental pump, called in-process. */
52
+ private readonly mainThreadDecoder;
51
53
  private readonly streaming;
52
54
  private readonly ring;
53
55
  private readonly frames;
@@ -64,7 +66,19 @@ export declare class WebCodecsMp4Playback {
64
66
  */
65
67
  static create(source: string | ArrayBuffer, signal?: AbortSignal, opts?: WebCodecsMp4CreateOptions): Promise<WebCodecsMp4Playback>;
66
68
  private static createViaWorker;
69
+ /**
70
+ * Same incremental/backpressured decode as the Worker path (see `IncrementalH264Decoder`),
71
+ * called in-process instead of via `postMessage`. Bounds resident `VideoFrame`s to
72
+ * `maxDecodedFrames` via a `VideoFrameRing` instead of decoding the whole clip into a
73
+ * `VideoFrame[]` that stays fully resident for the playback's lifetime (P1-11) — the previous
74
+ * behavior made the main-thread fallback (used when Workers are unavailable or `useWorker:
75
+ * false`, typically the lowest-end devices) the worst case for decode-time memory peaks.
76
+ */
67
77
  private static createOnMainThread;
78
+ /** Send a `rewind` through whichever transport is active (Worker `postMessage` or in-process). */
79
+ private sendRewind;
80
+ /** Send an `ensure` through whichever transport is active (Worker `postMessage` or in-process). */
81
+ private sendEnsure;
68
82
  ensureDecodedThrough(tSec: number): void;
69
83
  getFrameForTime(tSec: number): VideoFrame | null;
70
84
  destroy(): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taole/giftstage",
3
- "version": "0.1.33",
3
+ "version": "0.1.34",
4
4
  "description": "High-performance WebGPU/WebGL gift animation player unifying SVGA, VAP, AlphaVideo, and images",
5
5
  "type": "module",
6
6
  "main": "dist/gift-stage.cjs.js",