@taole/giftstage 0.1.36 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +770 -694
  2. package/dist/core/audio-manager.d.ts +10 -1
  3. package/dist/core/gift-stage.d.ts +63 -1
  4. package/dist/core/index.d.ts +4 -4
  5. package/dist/core/post-animator.d.ts +1 -1
  6. package/dist/core/render-manager.d.ts +55 -6
  7. package/dist/{gift-stage.cjs.js → gift-stage.cjs} +1401 -287
  8. package/dist/gift-stage.es.js +11500 -6094
  9. package/dist/gpu/backend-operation-result.d.ts +6 -0
  10. package/dist/gpu/detect.d.ts +1 -1
  11. package/dist/gpu/index.d.ts +3 -3
  12. package/dist/gpu/webgl1-backend.d.ts +8 -4
  13. package/dist/gpu/webgl2-backend.d.ts +154 -4
  14. package/dist/gpu/webgpu-backend.d.ts +130 -4
  15. package/dist/index.d.ts +36 -27
  16. package/dist/parsers/atlas-builder.d.ts +1 -1
  17. package/dist/parsers/index.d.ts +7 -7
  18. package/dist/parsers/svga-parser-worker-client.d.ts +1 -1
  19. package/dist/parsers/svga-parser.d.ts +1 -1
  20. package/dist/parsers/svga-proto-js.d.ts +1 -1
  21. package/dist/parsers/svga-sprite-table-binary.d.ts +1 -1
  22. package/dist/parsers/svga-sprite-table.d.ts +1 -1
  23. package/dist/parsers/svga-worker-payload-binary.d.ts +3 -3
  24. package/dist/parsers/vap-config-parser.d.ts +1 -1
  25. package/dist/plugin/motion-track.d.ts +19 -0
  26. package/dist/plugin/playback-scope.d.ts +42 -0
  27. package/dist/plugin/types.d.ts +183 -0
  28. package/dist/public.d.ts +2 -0
  29. package/dist/renderers/alpha-video-renderer.d.ts +1 -1
  30. package/dist/renderers/image-renderer.d.ts +1 -1
  31. package/dist/renderers/index.d.ts +4 -4
  32. package/dist/renderers/particle-batch-renderer.d.ts +19 -0
  33. package/dist/renderers/svga-batch-renderer.d.ts +140 -7
  34. package/dist/renderers/vap-renderer.d.ts +1 -1
  35. package/dist/shaders/index.d.ts +20 -0
  36. package/dist/types/index.d.ts +357 -6
  37. package/dist/utils/clip-mesh.d.ts +13 -0
  38. package/dist/utils/gift-object-fit.d.ts +2 -2
  39. package/dist/utils/gift-transform.d.ts +3 -1
  40. package/dist/utils/resource-cache.d.ts +1 -1
  41. package/dist/utils/slot-content.d.ts +1 -1
  42. package/dist/utils/svga-binary-cache.d.ts +2 -2
  43. package/dist/utils/svga-frame-timeline.d.ts +95 -0
  44. package/dist/utils/svga-hybrid-command-table.d.ts +12 -0
  45. package/dist/utils/svga-slot-state-schema.d.ts +28 -0
  46. package/dist/utils/vap-mix-composer.d.ts +1 -1
  47. package/dist/utils/wc-atlas-upload-size.d.ts +1 -1
  48. package/dist/wasm/wasm-bridge.d.ts +1 -1
  49. package/dist/workers/svga-atlas-worker-client.d.ts +1 -1
  50. package/dist/workers/svga-clip-worker-client.d.ts +1 -1
  51. package/package.json +81 -76
@@ -1,4 +1,4 @@
1
- import type { VAPConfig, VAPFrameItem } from '../types';
1
+ import type { VAPConfig, VAPFrameItem } from '../types/index.js';
2
2
  export declare function parseVAPConfig(config: string | object): Promise<VAPConfig>;
3
3
  export declare function getVAPFrame(config: VAPConfig, frameIndex: number): VAPFrameItem | undefined;
4
4
  export interface VAPContentSize {
@@ -0,0 +1,19 @@
1
+ import type { GPUBackend } from '../types/index.js';
2
+ import { type MotionTrackDescriptor, type MotionTrackLease, type ReadonlyTableLease } from './types.js';
3
+ export declare class MotionTrackLeaseImpl implements MotionTrackLease {
4
+ readonly trackCount: number;
5
+ readonly sampleCount: number;
6
+ readonly sampleIntervalMs: number;
7
+ readonly durationMs: number;
8
+ readonly table: ReadonlyTableLease;
9
+ readonly gpuGlobalsBuffer: import("../index.js").GPUBufferHandle | null;
10
+ private readonly backend;
11
+ private readonly gpuGlobals;
12
+ private readonly scratch;
13
+ private _released;
14
+ constructor(backend: GPUBackend | null, descriptor: MotionTrackDescriptor);
15
+ get released(): boolean;
16
+ sample(trackIndex: number, timeMs: number, out?: Float32Array<ArrayBuffer>): Float32Array;
17
+ updateGpuGlobals(timeMs: number, cssScaleX: number, cssScaleY: number): void;
18
+ release(): void;
19
+ }
@@ -0,0 +1,42 @@
1
+ import type { AddGiftOptions, GiftHandle } from '../types/index.js';
2
+ import type { MotionBindingOptions, MotionTrackDescriptor, MotionTrackLease, ParticleBatchDescriptor, ParticleBatchHandle, PlaybackFrame, PlaybackFrameController, PlaybackScope, PlaybackScopeHost, PlaybackScopeStartOptions, PlaybackScopeState } from './types.js';
3
+ export declare class PlaybackScopeImpl implements PlaybackScope, PlaybackFrameController {
4
+ readonly id: string;
5
+ private readonly host;
6
+ private readonly controller;
7
+ private readonly gifts;
8
+ private readonly tracks;
9
+ private readonly bindings;
10
+ private readonly particleBatches;
11
+ private readonly frameListeners;
12
+ private readonly finishListeners;
13
+ private unregisterController;
14
+ private _state;
15
+ private _currentTimeMs;
16
+ private durationMs;
17
+ private lastTickAtMs;
18
+ constructor(host: PlaybackScopeHost, id?: string);
19
+ get signal(): AbortSignal;
20
+ get state(): PlaybackScopeState;
21
+ get currentTimeMs(): number;
22
+ get active(): boolean;
23
+ addGift(options: AddGiftOptions): Promise<GiftHandle>;
24
+ createMotionTrack(descriptor: MotionTrackDescriptor): MotionTrackLease;
25
+ createParticleBatch(descriptor: ParticleBatchDescriptor): ParticleBatchHandle;
26
+ bindMotion(gift: GiftHandle, track: MotionTrackLease, trackIndex: number, options?: MotionBindingOptions): () => void;
27
+ onFrame(listener: (frame: PlaybackFrame) => void): () => void;
28
+ onFinish(listener: () => void): () => void;
29
+ start(options: PlaybackScopeStartOptions): void;
30
+ pause(): void;
31
+ resume(): void;
32
+ seek(timeMs: number): void;
33
+ stop(): void;
34
+ tick(nowMs: number, deltaMs: number): boolean;
35
+ destroy(): void;
36
+ private applyAll;
37
+ private applyParticleTime;
38
+ private applyBinding;
39
+ private applyMediaTime;
40
+ private emitFrame;
41
+ private assertAlive;
42
+ }
@@ -0,0 +1,183 @@
1
+ import type { AddGiftOptions, BackendType, SVGAFrameEvaluation, SVGAFrameTimelineTransport, SVGARenderPath, GiftHandle, GiftSlot, GiftType, GPUBackend, GPUBufferHandle } from '../types/index.js';
2
+ /** x, y, scaleX, scaleY, rotation(rad), opacity, visible, interpolateToNext. */
3
+ export declare const MOTION_TRACK_SAMPLE_FLOATS = 8;
4
+ export type PlaybackScopeState = 'idle' | 'playing' | 'paused' | 'stopped' | 'destroyed';
5
+ export interface GiftAssetMetadata {
6
+ type: GiftType;
7
+ width: number;
8
+ height: number;
9
+ fps?: number;
10
+ frameCount?: number;
11
+ spriteCount?: number;
12
+ atlasPageCount?: number;
13
+ hasShapes?: boolean;
14
+ hasClipPaths?: boolean;
15
+ /** True only when every drawable frame is proven to stay inside the SVGA viewBox. */
16
+ allFramesInsideViewBox?: boolean;
17
+ }
18
+ export interface GiftAssetLease {
19
+ readonly metadata: GiftAssetMetadata;
20
+ readonly released: boolean;
21
+ release(): void;
22
+ }
23
+ export interface SVGAFrameTimelineCapabilities {
24
+ transport: SVGAFrameTimelineTransport;
25
+ direct: boolean;
26
+ hybrid: boolean;
27
+ maxTableBytes: number;
28
+ supportedPaths: SVGARenderPath[];
29
+ defaultEvaluation: SVGAFrameEvaluation;
30
+ /** Compatibility aliases for consumers that used the earlier draft shape. */
31
+ gpuDirect: boolean;
32
+ gpuHybrid: boolean;
33
+ cpuReference: true;
34
+ tableBudgetBytes: number;
35
+ }
36
+ export interface GiftStageCapabilities {
37
+ backend: BackendType;
38
+ sharedTimeline: true;
39
+ scopedPlayback: true;
40
+ assetLeases: true;
41
+ motionTrack: {
42
+ /** `gpu-table` means the immutable table is resident on the GPU; CPU sampling remains the reference fallback. */
43
+ mode: 'gpu-table' | 'cpu-reference';
44
+ /** Media paths whose rendered parent transform can sample the table in-shader. */
45
+ gpuMediaTypes: GiftType[];
46
+ /** Complex media and older backends retain the deterministic CPU sampler. */
47
+ cpuReferenceFallback: true;
48
+ nonUniformScale: true;
49
+ sampleFloats: typeof MOTION_TRACK_SAMPLE_FLOATS;
50
+ };
51
+ particles: {
52
+ backend: 'webgpu' | 'webgl2' | 'unavailable';
53
+ canvas2D: false;
54
+ };
55
+ /** Public SVGA frame-table path support exposed to playback plugins. */
56
+ svgaFrameTimeline?: SVGAFrameTimelineCapabilities;
57
+ }
58
+ export interface ReadonlyTableDescriptor {
59
+ data: Float32Array;
60
+ stride: number;
61
+ label?: string;
62
+ }
63
+ export interface ReadonlyTableLease {
64
+ readonly data: Float32Array;
65
+ readonly stride: number;
66
+ readonly gpuBuffer: GPUBufferHandle | null;
67
+ readonly released: boolean;
68
+ release(): void;
69
+ }
70
+ export interface MotionTrackDescriptor {
71
+ /** Number of independent tracks packed in `samples`. */
72
+ trackCount: number;
73
+ /** Number of samples for each track. */
74
+ sampleCount: number;
75
+ /** Uniform distance between samples, in milliseconds. */
76
+ sampleIntervalMs: number;
77
+ /** Track-major samples using {@link MOTION_TRACK_SAMPLE_FLOATS}. */
78
+ samples: Float32Array;
79
+ durationMs?: number;
80
+ label?: string;
81
+ }
82
+ export interface MotionTrackLease {
83
+ readonly trackCount: number;
84
+ readonly sampleCount: number;
85
+ readonly sampleIntervalMs: number;
86
+ readonly durationMs: number;
87
+ readonly table: ReadonlyTableLease;
88
+ readonly released: boolean;
89
+ sample(trackIndex: number, timeMs: number, out?: Float32Array): Float32Array;
90
+ release(): void;
91
+ }
92
+ export interface MotionBindingOptions {
93
+ /** Track x/y represent the visual center. Default: true. */
94
+ center?: boolean;
95
+ }
96
+ export interface PlaybackScopeStartOptions {
97
+ durationMs: number;
98
+ fromMs?: number;
99
+ }
100
+ export interface PlaybackFrame {
101
+ nowMs: number;
102
+ /** Active host-timeline delta; excludes explicit pause and hidden-page suspension. */
103
+ deltaMs: number;
104
+ progress: number;
105
+ }
106
+ /**
107
+ * Particle instance layout (16 floats): birth, lifetime, x, y, vx, vy, ax, ay,
108
+ * scale, scaleSpeed, rotation, spin, r, g, b, a. Times are seconds; positions are CSS pixels.
109
+ */
110
+ export interface ParticleBatchDescriptor {
111
+ count: number;
112
+ instances: Float32Array;
113
+ particleSize?: number;
114
+ opacity?: number;
115
+ /** Sorts particle batches only. Particle batches composite after all media gift slots. */
116
+ zIndex?: number;
117
+ blend?: 'normal' | 'additive';
118
+ label?: string;
119
+ }
120
+ export interface ParticleBatchHandle {
121
+ readonly count: number;
122
+ readonly destroyed: boolean;
123
+ setTime(timeMs: number): void;
124
+ setOpacity(opacity: number): void;
125
+ destroy(): void;
126
+ }
127
+ export interface PlaybackScope {
128
+ readonly id: string;
129
+ readonly signal: AbortSignal;
130
+ readonly state: PlaybackScopeState;
131
+ readonly currentTimeMs: number;
132
+ addGift(options: AddGiftOptions): Promise<GiftHandle>;
133
+ createMotionTrack(descriptor: MotionTrackDescriptor): MotionTrackLease;
134
+ createParticleBatch(descriptor: ParticleBatchDescriptor): ParticleBatchHandle;
135
+ bindMotion(gift: GiftHandle, track: MotionTrackLease, trackIndex: number, options?: MotionBindingOptions): () => void;
136
+ onFrame(listener: (frame: PlaybackFrame) => void): () => void;
137
+ onFinish(listener: () => void): () => void;
138
+ start(options: PlaybackScopeStartOptions): void;
139
+ pause(): void;
140
+ resume(): void;
141
+ seek(timeMs: number): void;
142
+ stop(): void;
143
+ destroy(): void;
144
+ }
145
+ export interface GiftStagePluginHost {
146
+ readonly stage: unknown;
147
+ readonly capabilities: GiftStageCapabilities;
148
+ getViewport(): {
149
+ width: number;
150
+ height: number;
151
+ };
152
+ preload(type: 'svga' | 'image', source: string | ArrayBuffer, signal?: AbortSignal): Promise<GiftAssetLease>;
153
+ createPlaybackScope(id?: string): PlaybackScope;
154
+ }
155
+ export interface GiftStagePlugin<TApi = unknown> {
156
+ readonly name: string;
157
+ /** Host contract version implemented by this plugin. Version 1 plugins may
158
+ * use PlaybackScope, asset leases, MotionTrack and GPU particle batches. */
159
+ readonly apiVersion?: 1;
160
+ setup(host: GiftStagePluginHost): TApi | Promise<TApi>;
161
+ }
162
+ export interface MountedGiftStagePlugin<TApi = unknown> {
163
+ readonly name: string;
164
+ readonly api: TApi;
165
+ readonly destroyed: boolean;
166
+ destroy(): void;
167
+ }
168
+ /** Internal bridge used by the scope without widening GiftStage's public surface. */
169
+ export interface PlaybackScopeHost {
170
+ readonly backendType: BackendType;
171
+ readonly backend: GPUBackend | null;
172
+ getSlot(id: string): GiftSlot | undefined;
173
+ addGift(options: AddGiftOptions): Promise<GiftHandle>;
174
+ removeGift(id: string): void;
175
+ applyMediaTime(id: string, timeMs: number, emitInitialFrame?: boolean, discontinuous?: boolean): void;
176
+ invalidate(resetClock?: boolean): void;
177
+ registerFrameController(controller: PlaybackFrameController): () => void;
178
+ createParticleBatch(descriptor: ParticleBatchDescriptor): ParticleBatchHandle;
179
+ }
180
+ export interface PlaybackFrameController {
181
+ readonly active: boolean;
182
+ tick(nowMs: number, deltaMs: number): boolean;
183
+ }
@@ -0,0 +1,2 @@
1
+ /// <reference types="@webgpu/types" />
2
+ export * from './index.js';
@@ -1,4 +1,4 @@
1
- import type { GPUBackend, GiftSlot } from '../types';
1
+ import type { GPUBackend, GiftSlot } from '../types/index.js';
2
2
  export declare class AlphaVideoRenderer {
3
3
  private quadVB;
4
4
  private quadIB;
@@ -1,4 +1,4 @@
1
- import type { GPUBackend, GiftSlot } from '../types';
1
+ import type { GPUBackend, GiftSlot } from '../types/index.js';
2
2
  export declare class ImageRenderer {
3
3
  private quadVB;
4
4
  private quadIB;
@@ -1,4 +1,4 @@
1
- export { SVGABatchRenderer } from './svga-batch-renderer';
2
- export { VAPRenderer } from './vap-renderer';
3
- export { AlphaVideoRenderer } from './alpha-video-renderer';
4
- export { buildBatchVerticesJS } from './vertex-builder';
1
+ export { SVGABatchRenderer } from './svga-batch-renderer.js';
2
+ export { VAPRenderer } from './vap-renderer.js';
3
+ export { AlphaVideoRenderer } from './alpha-video-renderer.js';
4
+ export { buildBatchVerticesJS } from './vertex-builder.js';
@@ -0,0 +1,19 @@
1
+ import type { GPUBackend } from '../types/index.js';
2
+ import type { ParticleBatchDescriptor, ParticleBatchHandle } from '../plugin/types.js';
3
+ export declare class ParticleBatchRenderer {
4
+ private readonly backend;
5
+ private readonly batches;
6
+ private quadVB;
7
+ private quadIB;
8
+ private nextOrder;
9
+ private projW;
10
+ private projH;
11
+ private projection;
12
+ private readonly uniforms;
13
+ private readonly command;
14
+ constructor(backend: GPUBackend);
15
+ createBatch(descriptor: ParticleBatchDescriptor): ParticleBatchHandle;
16
+ get activeCount(): number;
17
+ draw(): void;
18
+ destroy(): void;
19
+ }
@@ -1,4 +1,12 @@
1
- import type { GPUBackend, GPUBufferHandle, GiftSlot, VideoEntity } from '../types';
1
+ import type { GPUBackend, GPUBufferHandle, GiftSlot, VideoEntity } from '../types/index.js';
2
+ /** Signals RenderManager to discard and replay the complete frame after a resource fallback. */
3
+ export declare class SVGAFastPathFrameReplayError extends Error {
4
+ readonly entity: VideoEntity;
5
+ constructor(entity: VideoEntity);
6
+ }
7
+ export declare class SVGAStencilTransactionError extends Error {
8
+ constructor(message: string);
9
+ }
2
10
  export interface ClipMeshCache {
3
11
  vertices: Float32Array;
4
12
  indices: Uint32Array;
@@ -31,8 +39,14 @@ interface SVGAClipPrewarmOptions {
31
39
  signal?: AbortSignal;
32
40
  }
33
41
  export type SVGAClipMeshMap = Map<string, ClipMeshCache>;
42
+ export interface SVGABatchRuntimeDiagnosticEvent {
43
+ type: 'clip-protocol-error' | 'clip-mesh-invalid' | 'backend-operation-rejected' | 'atlas-command-invalid' | 'stencil-transaction-failure';
44
+ message?: string;
45
+ }
34
46
  export declare class SVGABatchRenderer {
35
47
  private readonly onClipMeshReady?;
48
+ private readonly onHybridValidationFailure?;
49
+ private readonly onRuntimeDiagnostic?;
36
50
  private static readonly INSTANCED_QUAD_VERTS;
37
51
  private static readonly INSTANCED_QUAD_INDICES;
38
52
  /**
@@ -41,7 +55,10 @@ export declare class SVGABatchRenderer {
41
55
  * `invalidate()` a redraw — otherwise a slot whose animation has stopped dirtying may never
42
56
  * pick up the now-available precise clip.
43
57
  */
44
- constructor(onClipMeshReady?: (() => void) | undefined);
58
+ constructor(onClipMeshReady?: (() => void) | undefined, onHybridValidationFailure?: ((entity: VideoEntity) => void) | undefined, onRuntimeDiagnostic?: ((event: SVGABatchRuntimeDiagnosticEvent) => void) | undefined);
59
+ /** Observers are downstream of renderer state and may never participate in control flow. */
60
+ private emitRuntimeDiagnostic;
61
+ private notifyClipMeshReady;
45
62
  private pool;
46
63
  private vertexBufHandles;
47
64
  private indexBufHandles;
@@ -55,18 +72,39 @@ export declare class SVGABatchRenderer {
55
72
  private projCacheH;
56
73
  private projCache;
57
74
  private clipMeshCache;
75
+ private clipMeshStates;
76
+ private clipMeshStateWaiters;
77
+ private clipMeshGeneration;
78
+ /** Shared producers outlive individual addGift/preload subscribers. */
79
+ private clipMeshProducerController;
80
+ private clipMeshProducerTasks;
81
+ private destroyed;
82
+ private clipMeshDestroyed;
83
+ private clipGeometryKindCache;
58
84
  /** Meshes with lazily created GPU buffers (released in destroy). */
59
85
  private clipMeshesWithBuffers;
86
+ private clipMeshRefCounts;
87
+ private entityRetainedClipMeshes;
60
88
  /** Clip paths queued by `queueClipMeshBuild`, awaiting the next background build flush. */
61
89
  private pendingClipMeshPaths;
62
90
  /** True while a background build (worker or frame-budgeted main thread) is in flight. */
63
91
  private clipMeshBuildInFlight;
92
+ private stencilTransactionState;
64
93
  private clipFrameDataScratch;
65
94
  /** One-off scratch used only to capture a row for `svgaStaticBatchCache` (P1-10); never read
66
95
  * back — each captured entry clones out of it immediately via `.slice()`. */
67
96
  private staticBatchCacheRowScratch;
68
97
  private instancedFrameDataScratch;
69
98
  private instancedFrameDataWords;
99
+ /**
100
+ * Hybrid table batches can span slots without splitting into one draw per
101
+ * slot. Each row carries only parent/clip/schedule state; SVGA-local frame
102
+ * transforms, alpha and visibility remain resident in the frame table.
103
+ */
104
+ private timelineBatchDataScratch;
105
+ private timelineBatchFrameScratch;
106
+ /** Four vec4 values per Direct slot; child sprite rows remain resident in the frame table. */
107
+ private timelineDirectSlotDataScratch;
70
108
  private clipSpriteVertsScratch;
71
109
  private clipSpriteIndicesScratch;
72
110
  private clipMaskBatchTransformsScratch;
@@ -76,6 +114,8 @@ export declare class SVGABatchRenderer {
76
114
  private shapeMeshCache;
77
115
  /** Shape meshes with lazily created static GPU buffers (released in destroy). */
78
116
  private shapeMeshesWithBuffers;
117
+ private shapeMeshRefCounts;
118
+ private entityRetainedShapeMeshes;
79
119
  /** Column-major mat4 scratch for clip-mask u_transform (uploaded synchronously per draw). */
80
120
  private clipTransformMat;
81
121
  /** Column-major mat4 scratch for the shape draw's u_transform (consumed synchronously). */
@@ -105,6 +145,9 @@ export declare class SVGABatchRenderer {
105
145
  /** Reused scratch arrays (consumed synchronously). */
106
146
  private batchSpritesScratch;
107
147
  private drawGroupScratch;
148
+ private directSlotsScratch;
149
+ private hybridSlotsScratch;
150
+ private timelineSlotStateUploadId;
108
151
  private sharedSlotLayerWorkPool;
109
152
  private sharedSlotLayerWorksScratch;
110
153
  private clipLayerBatchPool;
@@ -113,6 +156,7 @@ export declare class SVGABatchRenderer {
113
156
  private entityHasShapesCache;
114
157
  /** 0=no clip, 1=scissor-compatible clips only, 2=stencil required, 255=not analyzed. */
115
158
  private entityFrameClipModeCache;
159
+ private gpuMotionEntityEligibilityCache;
116
160
  /**
117
161
  * Per-entity `ClipMeshCache` lookup aligned with `spriteTable.clipPaths` (index = `clipPathIndex`
118
162
  * value), so the hot per-sprite loop can resolve a clip mesh with a direct array read instead of
@@ -131,6 +175,21 @@ export declare class SVGABatchRenderer {
131
175
  private readonly plainInstancedTexturesScratch;
132
176
  private readonly plainInstancedUniformsScratch;
133
177
  private readonly plainInstancedDrawCommand;
178
+ /**
179
+ * GPU frame-table draw. Frame rows and sprite UV metadata are immutable on
180
+ * the entity; this reusable command uploads only one compact slot state.
181
+ */
182
+ private readonly timelineTexturesScratch;
183
+ private readonly timelineUniformsScratch;
184
+ private readonly timelineSlotStateScratch;
185
+ /** Parent/object-fit state is O(slot), even when a Hybrid schedule references
186
+ * the slot from many sprite layers in the same frame. */
187
+ private readonly timelineSlotStateCache;
188
+ private readonly timelineRenderableFrameCache;
189
+ private readonly timelineOriginScratch;
190
+ private readonly timelineDrawCommand;
191
+ private readonly timelineShapeDrawCommand;
192
+ private readonly timelineClipDrawCommand;
134
193
  private readonly plainBatchTexturesScratch;
135
194
  private readonly plainBatchUniformsScratch;
136
195
  private readonly plainBatchDrawCommand;
@@ -161,9 +220,24 @@ export declare class SVGABatchRenderer {
161
220
  private readonly shapeUniformsScratch;
162
221
  private readonly shapeDrawCommand;
163
222
  beginFrame(backend: GPUBackend): void;
223
+ endFrame(): void;
224
+ abortFrame(): void;
164
225
  flush(backend: GPUBackend, slots: Map<string, GiftSlot>): void;
165
226
  drawSlot(backend: GPUBackend, slot: GiftSlot): void;
166
227
  drawSlots(backend: GPUBackend, slots: Iterable<GiftSlot>): void;
228
+ private submitBackendDraw;
229
+ private assertRendererAlive;
230
+ private requireBackendDraw;
231
+ private areDirectSlotsBatchCompatible;
232
+ private areHybridSlotsBatchCompatible;
233
+ private areTimelineMotionBindingsCompatible;
234
+ private rejectFastPathFrame;
235
+ /**
236
+ * Preserves slot-major paint order while collapsing compatible same-resource
237
+ * Direct slots into one indexed instanced draw. Chunks contain complete slots
238
+ * so no slot can cross a render-order boundary.
239
+ */
240
+ private flushDirectSlots;
167
241
  requiresStencil(slots: Iterable<GiftSlot>): boolean;
168
242
  /**
169
243
  * Returns the target frame only when every clip path referenced by its visible sprites has a
@@ -175,9 +249,32 @@ export declare class SVGABatchRenderer {
175
249
  private getFrameClipMode;
176
250
  prewarmClipMeshes(entity: Pick<VideoEntity, 'spriteTable'>, options?: SVGAClipPrewarmOptions): Promise<number>;
177
251
  prewarmClipMeshesForFrame(entity: Pick<VideoEntity, 'spriteTable'>, frame: number, options?: SVGAClipPrewarmOptions): Promise<number>;
252
+ /**
253
+ * Precompiles every immutable Shape group and uploads its static mesh once.
254
+ * Hybrid table promotion waits for this preparation so the first promoted
255
+ * frame never pays SVG parsing/triangulation or buffer allocation.
256
+ */
257
+ prewarmShapeMeshes(backend: GPUBackend, entity: Pick<VideoEntity, 'spriteTable'>): number;
258
+ /** Validates every immutable Hybrid mesh reference and retains its GPU buffers for this entity. */
259
+ retainHybridMeshes(backend: GPUBackend, entity: VideoEntity): void;
260
+ releaseHybridMeshes(backend: GPUBackend, entity: VideoEntity): void;
178
261
  private collectFrameClipPaths;
179
- /** Builds every not-yet-cached path in `paths`, worker-first with a frame-budgeted fallback. */
262
+ /**
263
+ * Ensures shared producers exist, then waits as the current subscriber. A subscriber signal
264
+ * never owns or cancels shared mesh production; it only detaches this caller's waiter.
265
+ */
180
266
  private buildMissingClipMeshes;
267
+ /** Claims paths synchronously so concurrent subscribers can only observe one producer. */
268
+ private startClipMeshProducer;
269
+ /** Worker-first shared production with exactly one main-thread fallback. */
270
+ private runClipMeshProducer;
271
+ /** No non-destroy producer exit may strand a path in `building`. */
272
+ private failClipMeshProducer;
273
+ private emitClipProducerDiagnosticsAfterTerminal;
274
+ private publishClipMeshState;
275
+ private waitForClipMeshPaths;
276
+ private waitForClipMeshPath;
277
+ private settleClipMeshStateWaiters;
181
278
  /**
182
279
  * Called from the render-loop hot path (`getClipMeshForSprite`) when a clip path has no cached
183
280
  * mesh yet — instead of triangulating synchronously inline, queue it and build it in the
@@ -189,6 +286,24 @@ export declare class SVGABatchRenderer {
189
286
  private flushPendingClipMeshBuilds;
190
287
  hydrateClipMeshes(meshes: SVGAClipMeshMap | null | undefined): void;
191
288
  snapshotClipMeshes(entity: Pick<VideoEntity, 'spriteTable'>): SVGAClipMeshMap;
289
+ private canUseTimelinePath;
290
+ private resolveTimelineSlotState;
291
+ private resolveCachedTimelineSlotState;
292
+ private drawTimelineRange;
293
+ private drawTimelineSlot;
294
+ private getClipMeshByIndex;
295
+ private handleInvalidClipMesh;
296
+ private drawTimelineMesh;
297
+ private flushHybridScheduledSlots;
298
+ /**
299
+ * Reordering a shared command slice across slots is legal only when their
300
+ * conservative outer clip bounds are disjoint. The test also locks the
301
+ * authoritative frame so one immutable operation sequence serves the group.
302
+ */
303
+ private prepareDisjointHybridSlots;
304
+ /** Executes only the immutable command slice for the CPU-authoritative frame. */
305
+ private drawHybridScheduledSlot;
306
+ private drawHybridScheduledOperations;
192
307
  private flushGroup;
193
308
  /**
194
309
  * Transposes disjoint slots sharing one entity into sprite layers. Reordering across slots is
@@ -221,8 +336,26 @@ export declare class SVGABatchRenderer {
221
336
  private drawFromStaticBatchCache;
222
337
  private getProjection;
223
338
  private drawBatch;
339
+ /**
340
+ * Hybrid path: CPU keeps the already-established topology/order decisions,
341
+ * while each contiguous texture run fetches its transform/alpha from the
342
+ * immutable GPU frame table. A compact per-draw schedule row carries only
343
+ * parent/clip/frame/metadata routing when the run spans slots or sprite rows.
344
+ */
345
+ private tryDrawTimelineBatch;
346
+ /**
347
+ * Draws a Hybrid batch whose CPU-precompiled schedule spans multiple slots
348
+ * or non-contiguous sprite rows. The compact 16-float rows contain no SVGA
349
+ * child matrix/alpha: the shader fetches those by `(spriteIndex, frame)`.
350
+ */
351
+ private drawTimelineWorks;
352
+ /** Resolves all planner/capacity state before a stencil transaction publishes its mask. */
353
+ private prepareTimelineWorks;
354
+ private submitPreparedTimelineWorks;
355
+ private canUseGpuMotion;
224
356
  private drawBatchInstancedWebGL;
225
357
  private drawClippedSprite;
358
+ private drawTimelineStencilTest;
226
359
  /**
227
360
  * WebGL1 clipped-sprite draw: pack + upload the quad, then draw with stencil 'test'.
228
361
  * Caller is responsible for the surrounding stencil write/clear. Uses separate buffers to
@@ -263,7 +396,7 @@ export declare class SVGABatchRenderer {
263
396
  private appendStroke;
264
397
  private clamp01;
265
398
  private cleanupPoints;
266
- private getClipMesh;
399
+ private getClipGeometryKind;
267
400
  /**
268
401
  * Hot-path clip mesh lookup: reads `clipPathIndex` directly (no string build/compare) and
269
402
  * memoizes the resolved mesh per (entity, clipPathIndex) so repeat frames skip the string-keyed
@@ -273,9 +406,9 @@ export declare class SVGABatchRenderer {
273
406
  * prewarm normally finishes before an entity is ever drawn, but with `svgaPrewarmClipMeshes:
274
407
  * false`, an aborted/partially failed prewarm, or a slot rendered before prewarm completes, this
275
408
  * lookup would otherwise pay a synchronous SVG-parse + triangulate cost inside the render loop.
276
- * The path is queued for a background build instead (see `queueClipMeshBuild`); callers already
277
- * treat a falsy return as "no clip this frame" (draw unclipped / assume stencil conservatively),
278
- * and a redraw is requested once the build lands so later frames clip precisely.
409
+ * The path is queued for a background build instead (see `queueClipMeshBuild`). Callers discard
410
+ * the complete frame while it is unresolved; a redraw is requested once the mesh lands, so no
411
+ * sprite is ever submitted without its authoritative clip.
279
412
  */
280
413
  private getClipMeshForSprite;
281
414
  private ensureVertexBuffer;
@@ -1,4 +1,4 @@
1
- import type { GPUBackend, GiftSlot } from '../types';
1
+ import type { GPUBackend, GiftSlot } from '../types/index.js';
2
2
  export declare class VAPRenderer {
3
3
  private instancedQuadVB;
4
4
  private instancedQuadIB;
@@ -7,6 +7,18 @@ export declare const glsl: {
7
7
  readonly vert: string;
8
8
  readonly frag: string;
9
9
  };
10
+ readonly svgaFrameTableInstancedPremul: {
11
+ readonly vert: string;
12
+ readonly frag: string;
13
+ };
14
+ readonly svgaFrameTableShape: {
15
+ readonly vert: string;
16
+ readonly frag: string;
17
+ };
18
+ readonly svgaFrameTableClipMask: {
19
+ readonly vert: string;
20
+ readonly frag: string;
21
+ };
10
22
  readonly vap: {
11
23
  readonly vert: string;
12
24
  readonly frag: string;
@@ -39,6 +51,10 @@ export declare const glsl: {
39
51
  readonly vert: string;
40
52
  readonly frag: string;
41
53
  };
54
+ readonly particleInstanced: {
55
+ readonly vert: string;
56
+ readonly frag: string;
57
+ };
42
58
  };
43
59
  export declare const wgsl: {
44
60
  readonly svgaBatchPremul: string;
@@ -51,4 +67,8 @@ export declare const wgsl: {
51
67
  readonly rgbaTextured: string;
52
68
  readonly clipMask: string;
53
69
  readonly clipMaskInstanced: string;
70
+ readonly particleInstanced: string;
71
+ readonly svgaMotionInstancedPremul: string;
72
+ readonly svgaFrameTableShape: string;
73
+ readonly svgaFrameTableClipMask: string;
54
74
  };