@elah/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. package/dist/actions/index.d.ts +3 -0
  2. package/dist/actions/index.js +1 -0
  3. package/dist/actions/splitClipAtPlayhead.d.ts +8 -0
  4. package/dist/actions/splitClipAtPlayhead.js +34 -0
  5. package/dist/actions/types.d.ts +8 -0
  6. package/dist/actions/types.js +1 -0
  7. package/dist/assets/importFiles.d.ts +29 -0
  8. package/dist/assets/importFiles.js +351 -0
  9. package/dist/assets/index.d.ts +10 -0
  10. package/dist/assets/index.js +13 -0
  11. package/dist/assets/store.d.ts +13 -0
  12. package/dist/assets/store.js +20 -0
  13. package/dist/assets/types.d.ts +23 -0
  14. package/dist/assets/types.js +1 -0
  15. package/dist/debug/trace.d.ts +20 -0
  16. package/dist/debug/trace.js +98 -0
  17. package/dist/editor/TimelineEngine.d.ts +65 -0
  18. package/dist/editor/TimelineEngine.js +413 -0
  19. package/dist/editor-context.d.ts +10 -0
  20. package/dist/editor-context.js +10 -0
  21. package/dist/elements/audio.d.ts +13 -0
  22. package/dist/elements/audio.js +4 -0
  23. package/dist/elements/base.d.ts +40 -0
  24. package/dist/elements/base.js +39 -0
  25. package/dist/elements/image.d.ts +13 -0
  26. package/dist/elements/image.js +4 -0
  27. package/dist/elements/text.d.ts +12 -0
  28. package/dist/elements/text.js +8 -0
  29. package/dist/elements/video.d.ts +13 -0
  30. package/dist/elements/video.js +4 -0
  31. package/dist/export/ExportWorker.d.ts +1 -0
  32. package/dist/export/ExportWorker.js +371 -0
  33. package/dist/export/exportVideo.d.ts +3 -0
  34. package/dist/export/exportVideo.js +118 -0
  35. package/dist/export/index.d.ts +2 -0
  36. package/dist/export/index.js +1 -0
  37. package/dist/export/lazyExport.d.ts +4 -0
  38. package/dist/export/lazyExport.js +4 -0
  39. package/dist/export/types.d.ts +37 -0
  40. package/dist/export/types.js +1 -0
  41. package/dist/index.d.ts +48 -0
  42. package/dist/index.js +28 -0
  43. package/dist/media/audio/AudioPlaybackController.d.ts +26 -0
  44. package/dist/media/audio/AudioPlaybackController.js +125 -0
  45. package/dist/media/video/DecoderBackedVideoFrameProvider.d.ts +51 -0
  46. package/dist/media/video/DecoderBackedVideoFrameProvider.js +125 -0
  47. package/dist/media/video/FrameCache.d.ts +28 -0
  48. package/dist/media/video/FrameCache.js +88 -0
  49. package/dist/media/video/StreamingFrameProducer.d.ts +57 -0
  50. package/dist/media/video/StreamingFrameProducer.js +356 -0
  51. package/dist/media/video/VideoDecoderManager.d.ts +59 -0
  52. package/dist/media/video/VideoDecoderManager.js +342 -0
  53. package/dist/media/video/VideoFrameProvider.d.ts +101 -0
  54. package/dist/media/video/VideoFrameProvider.js +257 -0
  55. package/dist/media/video/demuxer/MediabunnyDemuxer.d.ts +23 -0
  56. package/dist/media/video/demuxer/MediabunnyDemuxer.js +88 -0
  57. package/dist/media/video/demuxer/createMediabunnyBackend.d.ts +32 -0
  58. package/dist/media/video/demuxer/createMediabunnyBackend.js +156 -0
  59. package/dist/media/video/index.d.ts +8 -0
  60. package/dist/media/video/index.js +5 -0
  61. package/dist/playback/PlaybackEngine.d.ts +50 -0
  62. package/dist/playback/PlaybackEngine.js +188 -0
  63. package/dist/renderer/gpu/GpuRenderer.d.ts +38 -0
  64. package/dist/renderer/gpu/GpuRenderer.js +208 -0
  65. package/dist/renderer/gpu/RenderGraph.d.ts +10 -0
  66. package/dist/renderer/gpu/RenderGraph.js +80 -0
  67. package/dist/renderer/gpu/ShaderProgram.d.ts +14 -0
  68. package/dist/renderer/gpu/ShaderProgram.js +76 -0
  69. package/dist/renderer/gpu/TexturePool.d.ts +25 -0
  70. package/dist/renderer/gpu/TexturePool.js +93 -0
  71. package/dist/renderer/gpu/VideoTexture.d.ts +13 -0
  72. package/dist/renderer/gpu/VideoTexture.js +54 -0
  73. package/dist/renderer/gpu/WebGLContext.d.ts +28 -0
  74. package/dist/renderer/gpu/WebGLContext.js +102 -0
  75. package/dist/renderer/gpu/debug/DebugGpuRenderer.d.ts +27 -0
  76. package/dist/renderer/gpu/debug/DebugGpuRenderer.js +108 -0
  77. package/dist/renderer/gpu/debug/DebugOverlay.d.ts +17 -0
  78. package/dist/renderer/gpu/debug/DebugOverlay.js +83 -0
  79. package/dist/renderer/gpu/debug/GpuDebugCounters.d.ts +38 -0
  80. package/dist/renderer/gpu/debug/GpuDebugCounters.js +72 -0
  81. package/dist/renderer/gpu/debug/GpuDebugGlobal.d.ts +16 -0
  82. package/dist/renderer/gpu/debug/GpuDebugGlobal.js +14 -0
  83. package/dist/renderer/gpu/debug/GpuRendererDebugPanel.d.ts +31 -0
  84. package/dist/renderer/gpu/debug/GpuRendererDebugPanel.js +128 -0
  85. package/dist/renderer/gpu/debug/RecordingGl.d.ts +88 -0
  86. package/dist/renderer/gpu/debug/RecordingGl.js +214 -0
  87. package/dist/renderer/gpu/debug/playground.d.ts +20 -0
  88. package/dist/renderer/gpu/debug/playground.js +64 -0
  89. package/dist/renderer/gpu/debug/scenarios.d.ts +7 -0
  90. package/dist/renderer/gpu/debug/scenarios.js +145 -0
  91. package/dist/renderer/gpu/debug/types.d.ts +16 -0
  92. package/dist/renderer/gpu/debug/types.js +1 -0
  93. package/dist/renderer/gpu/layers/FrameProbeLayer.d.ts +16 -0
  94. package/dist/renderer/gpu/layers/FrameProbeLayer.js +127 -0
  95. package/dist/renderer/gpu/layers/ImageLayer.d.ts +23 -0
  96. package/dist/renderer/gpu/layers/ImageLayer.js +124 -0
  97. package/dist/renderer/gpu/layers/TestLayer.d.ts +16 -0
  98. package/dist/renderer/gpu/layers/TestLayer.js +109 -0
  99. package/dist/renderer/gpu/layers/TextLayer.d.ts +19 -0
  100. package/dist/renderer/gpu/layers/TextLayer.js +166 -0
  101. package/dist/renderer/gpu/layers/VideoLayer.d.ts +38 -0
  102. package/dist/renderer/gpu/layers/VideoLayer.js +194 -0
  103. package/dist/renderer/gpu/layers/drawRect.d.ts +13 -0
  104. package/dist/renderer/gpu/layers/drawRect.js +55 -0
  105. package/dist/renderer/gpu/layers/objectFit.d.ts +7 -0
  106. package/dist/renderer/gpu/layers/objectFit.js +26 -0
  107. package/dist/renderer/gpu/layers/textLayout.d.ts +47 -0
  108. package/dist/renderer/gpu/layers/textLayout.js +82 -0
  109. package/dist/renderer/gpu/layers/types.d.ts +18 -0
  110. package/dist/renderer/gpu/layers/types.js +1 -0
  111. package/dist/renderer/gpu/shaders/quad.frag.d.ts +1 -0
  112. package/dist/renderer/gpu/shaders/quad.frag.js +14 -0
  113. package/dist/renderer/gpu/shaders/quad.vert.d.ts +1 -0
  114. package/dist/renderer/gpu/shaders/quad.vert.js +28 -0
  115. package/dist/renderer/gpu/types.d.ts +21 -0
  116. package/dist/renderer/gpu/types.js +1 -0
  117. package/dist/renderer/gpu/viewport.d.ts +7 -0
  118. package/dist/renderer/gpu/viewport.js +33 -0
  119. package/dist/renderer/types.d.ts +7 -0
  120. package/dist/renderer/types.js +1 -0
  121. package/dist/resolver/resolveTimeline.d.ts +3 -0
  122. package/dist/resolver/resolveTimeline.js +249 -0
  123. package/dist/resolver/scene.d.ts +54 -0
  124. package/dist/resolver/scene.js +1 -0
  125. package/dist/stores/playback.store.d.ts +50 -0
  126. package/dist/stores/playback.store.js +42 -0
  127. package/dist/stores/selection.store.d.ts +12 -0
  128. package/dist/stores/selection.store.js +19 -0
  129. package/dist/stores/tracks.store.d.ts +19 -0
  130. package/dist/stores/tracks.store.js +27 -0
  131. package/dist/stores/transitions.store.d.ts +9 -0
  132. package/dist/stores/transitions.store.js +7 -0
  133. package/dist/track/track.d.ts +8 -0
  134. package/dist/track/track.js +19 -0
  135. package/dist/types/index.d.ts +117 -0
  136. package/dist/types/index.js +1 -0
  137. package/dist/utils/frames.d.ts +20 -0
  138. package/dist/utils/frames.js +40 -0
  139. package/dist/utils/id.d.ts +1 -0
  140. package/dist/utils/id.js +3 -0
  141. package/dist/utils/snap.d.ts +5 -0
  142. package/dist/utils/snap.js +79 -0
  143. package/dist/visitor/add.d.ts +3 -0
  144. package/dist/visitor/add.js +16 -0
  145. package/dist/visitor/clone.d.ts +3 -0
  146. package/dist/visitor/clone.js +25 -0
  147. package/dist/visitor/remove.d.ts +5 -0
  148. package/dist/visitor/remove.js +29 -0
  149. package/dist/visitor/split.d.ts +3 -0
  150. package/dist/visitor/split.js +31 -0
  151. package/dist/visitor/update.d.ts +4 -0
  152. package/dist/visitor/update.js +31 -0
  153. package/package.json +31 -0
@@ -0,0 +1,208 @@
1
+ import { GpuRendererDebugPanel, } from './debug/GpuRendererDebugPanel';
2
+ import { GpuDebugCounters } from './debug/GpuDebugCounters';
3
+ import { VideoLayer } from './layers/VideoLayer';
4
+ import { FrameProbeLayer } from './layers/FrameProbeLayer';
5
+ import { TextLayer } from './layers/TextLayer';
6
+ import { ImageLayer } from './layers/ImageLayer';
7
+ import { RenderGraph } from './RenderGraph';
8
+ import { TexturePool } from './TexturePool';
9
+ import { computeContainViewport } from './viewport';
10
+ import { WebGLContext } from './WebGLContext';
11
+ export class GpuRenderer {
12
+ constructor(options = {}) {
13
+ this._glCtx = null;
14
+ this._renderGraph = null;
15
+ this._texturePool = null;
16
+ this._videoLayer = null;
17
+ this._textLayer = null;
18
+ this._imageLayer = null;
19
+ this._debugPanel = null;
20
+ this._container = null;
21
+ this._mounted = false;
22
+ this._debugEnabled = false;
23
+ this._lastScene = null;
24
+ this._viewport = { width: 0, height: 0 };
25
+ this._lastRenderTime = 0;
26
+ this._fps = 0;
27
+ this._lastRenderDurationMs = 0;
28
+ this._noOpTicks = 0;
29
+ this._options = options;
30
+ }
31
+ mount(container) {
32
+ if (this._mounted) {
33
+ throw new Error('GpuRenderer: mount() called more than once');
34
+ }
35
+ const clearColor = this._options.clearColor ?? [0, 0, 0, 1];
36
+ this._glCtx = new WebGLContext({
37
+ onLost: () => this._handleContextLost(),
38
+ onRestore: () => this._handleContextRestored(),
39
+ preserveDrawingBuffer: this._options.preserveDrawingBuffer,
40
+ });
41
+ this._glCtx.setClearColor(...clearColor);
42
+ container.appendChild(this._glCtx.canvas);
43
+ this._container = container;
44
+ this._viewport = {
45
+ width: this._glCtx.canvas.width,
46
+ height: this._glCtx.canvas.height,
47
+ };
48
+ this._texturePool = new TexturePool({
49
+ maxTextures: this._options.maxTextures,
50
+ });
51
+ const videoLayerArg = this._options.providerFactory
52
+ ?? (this._options.demuxerFactory
53
+ ? {
54
+ demuxerFactory: this._options.demuxerFactory,
55
+ decoderFactory: this._options.decoderFactory,
56
+ maxOutstanding: this._options.maxOutstandingDecodes,
57
+ }
58
+ : undefined);
59
+ let videoLayer;
60
+ if (this._options.probeLayer) {
61
+ videoLayer = new FrameProbeLayer();
62
+ }
63
+ else {
64
+ this._videoLayer = new VideoLayer(this._texturePool, videoLayerArg);
65
+ videoLayer = this._videoLayer;
66
+ }
67
+ this._renderGraph = new RenderGraph();
68
+ this._renderGraph.registerLayer(videoLayer, (scene) => scene.videos, (item) => item.id, (item) => item.zIndex);
69
+ this._imageLayer = new ImageLayer();
70
+ this._renderGraph.registerLayer(this._imageLayer, (scene) => scene.images, (item) => item.id, (item) => item.zIndex);
71
+ this._textLayer = new TextLayer();
72
+ this._renderGraph.registerLayer(this._textLayer, (scene) => scene.texts, (item) => item.id, (item) => item.zIndex);
73
+ this._mounted = true;
74
+ if (this._debugEnabled) {
75
+ this._attachDebugPanel();
76
+ }
77
+ }
78
+ resize(cssWidth, cssHeight, dpr = 1) {
79
+ if (!this._glCtx || !this._mounted)
80
+ return;
81
+ this._glCtx.resize(cssWidth, cssHeight, dpr);
82
+ this._viewport = {
83
+ width: this._glCtx.canvas.width,
84
+ height: this._glCtx.canvas.height,
85
+ };
86
+ }
87
+ render(scene) {
88
+ if (!this._mounted || !this._glCtx || !this._renderGraph)
89
+ return;
90
+ if (this._glCtx.isLost)
91
+ return;
92
+ if (scene === this._lastScene) {
93
+ this._lastRenderDurationMs = 0;
94
+ this._noOpTicks++;
95
+ return;
96
+ }
97
+ const gl = this._glCtx.gl;
98
+ if (!gl)
99
+ return;
100
+ const now = performance.now();
101
+ if (this._lastRenderTime > 0) {
102
+ const dt = now - this._lastRenderTime;
103
+ if (dt > 0) {
104
+ this._fps = 1000 / dt;
105
+ }
106
+ }
107
+ this._lastRenderTime = now;
108
+ const renderStart = now;
109
+ this._glCtx.clear();
110
+ const canvas = this._glCtx.canvas;
111
+ const fit = computeContainViewport(canvas.width, canvas.height, scene.stage.width, scene.stage.height);
112
+ gl.viewport(fit.x, fit.y, fit.width, fit.height);
113
+ const ctx = this._buildLayerContext(scene, gl);
114
+ this._renderGraph.execute(scene, ctx);
115
+ this._lastRenderDurationMs = performance.now() - renderStart;
116
+ this._lastScene = scene;
117
+ }
118
+ getCanvas() {
119
+ return this._glCtx?.canvas ?? null;
120
+ }
121
+ setDebug(enabled) {
122
+ this._debugEnabled = enabled;
123
+ if (enabled) {
124
+ this._attachDebugPanel();
125
+ }
126
+ else if (this._debugPanel) {
127
+ this._debugPanel.dispose();
128
+ this._debugPanel = null;
129
+ }
130
+ }
131
+ dispose() {
132
+ this._debugPanel?.dispose();
133
+ this._debugPanel = null;
134
+ this._renderGraph?.dispose();
135
+ this._renderGraph = null;
136
+ this._videoLayer = null;
137
+ this._textLayer = null;
138
+ this._imageLayer = null;
139
+ const gl = this._glCtx?.gl ?? null;
140
+ if (gl && this._texturePool) {
141
+ this._texturePool.dispose(gl);
142
+ }
143
+ this._texturePool = null;
144
+ this._glCtx?.dispose();
145
+ this._glCtx?.canvas.remove();
146
+ this._glCtx = null;
147
+ this._container = null;
148
+ this._mounted = false;
149
+ this._lastScene = null;
150
+ this._viewport = { width: 0, height: 0 };
151
+ this._lastRenderTime = 0;
152
+ this._fps = 0;
153
+ this._lastRenderDurationMs = 0;
154
+ this._noOpTicks = 0;
155
+ }
156
+ get videoLayer() {
157
+ return this._videoLayer;
158
+ }
159
+ get fps() {
160
+ return this._fps;
161
+ }
162
+ get lastRenderDurationMs() {
163
+ return this._lastRenderDurationMs;
164
+ }
165
+ _buildLayerContext(scene, gl) {
166
+ return {
167
+ gl,
168
+ stage: scene.stage,
169
+ viewport: this._viewport,
170
+ fps: scene.fps,
171
+ };
172
+ }
173
+ _attachDebugPanel() {
174
+ if (!this._debugEnabled || this._debugPanel || !this._container)
175
+ return;
176
+ this._debugPanel = new GpuRendererDebugPanel(this._container, () => this._buildDebugSnapshot());
177
+ }
178
+ _buildDebugSnapshot() {
179
+ const counters = GpuDebugCounters.snapshot();
180
+ const scene = this._lastScene;
181
+ return {
182
+ fps: this._fps,
183
+ currentFrame: scene?.frame ?? 0,
184
+ activeClipCount: scene?.videos.length ?? 0,
185
+ textureCount: this._videoLayer?.getTextureCount() ?? 0,
186
+ cacheHitRatio: counters.cacheHitRatio,
187
+ renderDurationMs: this._lastRenderDurationMs,
188
+ decoderStates: this._videoLayer?.getDecoderStates() ?? {},
189
+ droppedFrames: counters.droppedFrames,
190
+ outstandingDecodes: counters.outstandingDecodes,
191
+ activeProviders: this._videoLayer?.getProviderCount() ?? 0,
192
+ noOpTicks: this._noOpTicks,
193
+ };
194
+ }
195
+ _handleContextLost() {
196
+ this._lastScene = null;
197
+ this._texturePool?.handleContextLost();
198
+ this._videoLayer?.notifyContextLost();
199
+ this._textLayer?.notifyContextLost();
200
+ this._imageLayer?.notifyContextLost();
201
+ this._renderGraph?.notifyContextLost();
202
+ }
203
+ _handleContextRestored() {
204
+ this._lastScene = null;
205
+ const clearColor = this._options.clearColor ?? [0, 0, 0, 1];
206
+ this._glCtx?.setClearColor(...clearColor);
207
+ }
208
+ }
@@ -0,0 +1,10 @@
1
+ import type { Scene } from '../../resolver/scene';
2
+ import type { Layer, LayerContext } from './layers/types';
3
+ export declare class RenderGraph {
4
+ private readonly _registrations;
5
+ registerLayer<TItem>(layer: Layer<TItem>, getItems: (scene: Scene) => TItem[], getId: (item: TItem) => string, getZIndex: (item: TItem) => number): void;
6
+ execute(scene: Scene, ctx: LayerContext): void;
7
+ notifyContextLost(): void;
8
+ dispose(): void;
9
+ private _diff;
10
+ }
@@ -0,0 +1,80 @@
1
+ export class RenderGraph {
2
+ constructor() {
3
+ this._registrations = [];
4
+ }
5
+ registerLayer(layer, getItems, getId, getZIndex) {
6
+ this._registrations.push({
7
+ layer: layer,
8
+ getItems: getItems,
9
+ getId: getId,
10
+ getZIndex: getZIndex,
11
+ activeItems: new Map(),
12
+ });
13
+ }
14
+ execute(scene, ctx) {
15
+ const drawList = [];
16
+ for (const reg of this._registrations) {
17
+ const currentItems = reg.getItems(scene);
18
+ const diff = this._diff(currentItems, reg.activeItems, reg.getId);
19
+ for (const id of diff.leaving) {
20
+ reg.layer.release(id);
21
+ reg.activeItems.delete(id);
22
+ }
23
+ for (const item of diff.entering) {
24
+ reg.layer.acquire(item, ctx);
25
+ reg.activeItems.set(reg.getId(item), item);
26
+ }
27
+ for (const item of currentItems) {
28
+ const id = reg.getId(item);
29
+ if (reg.activeItems.has(id)) {
30
+ reg.activeItems.set(id, item);
31
+ }
32
+ }
33
+ for (const item of reg.activeItems.values()) {
34
+ drawList.push({
35
+ layer: reg.layer,
36
+ item,
37
+ zIndex: reg.getZIndex(item),
38
+ });
39
+ }
40
+ }
41
+ drawList.sort((a, b) => a.zIndex - b.zIndex);
42
+ for (const entry of drawList) {
43
+ entry.layer.draw(entry.item, ctx);
44
+ }
45
+ }
46
+ notifyContextLost() {
47
+ for (const reg of this._registrations) {
48
+ for (const id of reg.activeItems.keys()) {
49
+ reg.layer.release(id);
50
+ }
51
+ reg.activeItems.clear();
52
+ }
53
+ }
54
+ dispose() {
55
+ for (const reg of this._registrations) {
56
+ for (const id of reg.activeItems.keys()) {
57
+ reg.layer.release(id);
58
+ }
59
+ reg.activeItems.clear();
60
+ reg.layer.dispose();
61
+ }
62
+ this._registrations.length = 0;
63
+ }
64
+ _diff(current, active, getId) {
65
+ const currentIds = new Set(current.map(getId));
66
+ const entering = [];
67
+ const leaving = [];
68
+ for (const item of current) {
69
+ if (!active.has(getId(item))) {
70
+ entering.push(item);
71
+ }
72
+ }
73
+ for (const id of active.keys()) {
74
+ if (!currentIds.has(id)) {
75
+ leaving.push(id);
76
+ }
77
+ }
78
+ return { entering, leaving };
79
+ }
80
+ }
@@ -0,0 +1,14 @@
1
+ export declare class ShaderProgram {
2
+ private readonly program;
3
+ private readonly uniformCache;
4
+ private constructor();
5
+ static create(gl: WebGL2RenderingContext, vertSrc: string, fragSrc: string): ShaderProgram;
6
+ use(gl: WebGL2RenderingContext): void;
7
+ setUniform1i(gl: WebGL2RenderingContext, name: string, value: number): void;
8
+ setUniform1f(gl: WebGL2RenderingContext, name: string, value: number): void;
9
+ setUniform2f(gl: WebGL2RenderingContext, name: string, x: number, y: number): void;
10
+ setUniformMatrix3fv(gl: WebGL2RenderingContext, name: string, transpose: boolean, value: Float32Array | number[]): void;
11
+ dispose(gl: WebGL2RenderingContext): void;
12
+ private getUniformLocation;
13
+ private static compileShader;
14
+ }
@@ -0,0 +1,76 @@
1
+ export class ShaderProgram {
2
+ constructor(program) {
3
+ this.uniformCache = new Map();
4
+ this.program = program;
5
+ }
6
+ static create(gl, vertSrc, fragSrc) {
7
+ const vert = ShaderProgram.compileShader(gl, gl.VERTEX_SHADER, vertSrc);
8
+ const frag = ShaderProgram.compileShader(gl, gl.FRAGMENT_SHADER, fragSrc);
9
+ const program = gl.createProgram();
10
+ if (!program)
11
+ throw new Error('ShaderProgram: gl.createProgram() returned null');
12
+ gl.attachShader(program, vert);
13
+ gl.attachShader(program, frag);
14
+ gl.linkProgram(program);
15
+ gl.detachShader(program, vert);
16
+ gl.detachShader(program, frag);
17
+ gl.deleteShader(vert);
18
+ gl.deleteShader(frag);
19
+ if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
20
+ const log = gl.getProgramInfoLog(program) ?? '(no log)';
21
+ gl.deleteProgram(program);
22
+ throw new Error(`ShaderProgram: link failed — ${log}`);
23
+ }
24
+ return new ShaderProgram(program);
25
+ }
26
+ use(gl) {
27
+ gl.useProgram(this.program);
28
+ }
29
+ setUniform1i(gl, name, value) {
30
+ const loc = this.getUniformLocation(gl, name);
31
+ if (loc !== null)
32
+ gl.uniform1i(loc, value);
33
+ }
34
+ setUniform1f(gl, name, value) {
35
+ const loc = this.getUniformLocation(gl, name);
36
+ if (loc !== null)
37
+ gl.uniform1f(loc, value);
38
+ }
39
+ setUniform2f(gl, name, x, y) {
40
+ const loc = this.getUniformLocation(gl, name);
41
+ if (loc !== null)
42
+ gl.uniform2f(loc, x, y);
43
+ }
44
+ setUniformMatrix3fv(gl, name, transpose, value) {
45
+ const loc = this.getUniformLocation(gl, name);
46
+ if (loc !== null)
47
+ gl.uniformMatrix3fv(loc, transpose, value);
48
+ }
49
+ dispose(gl) {
50
+ gl.deleteProgram(this.program);
51
+ this.uniformCache.clear();
52
+ }
53
+ getUniformLocation(gl, name) {
54
+ if (this.uniformCache.has(name)) {
55
+ return this.uniformCache.get(name) ?? null;
56
+ }
57
+ const loc = gl.getUniformLocation(this.program, name);
58
+ this.uniformCache.set(name, loc);
59
+ return loc;
60
+ }
61
+ static compileShader(gl, type, src) {
62
+ const shader = gl.createShader(type);
63
+ if (!shader) {
64
+ throw new Error(`ShaderProgram: gl.createShader(${type}) returned null`);
65
+ }
66
+ gl.shaderSource(shader, src);
67
+ gl.compileShader(shader);
68
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
69
+ const log = gl.getShaderInfoLog(shader) ?? '(no log)';
70
+ gl.deleteShader(shader);
71
+ const typeName = type === gl.VERTEX_SHADER ? 'vertex' : 'fragment';
72
+ throw new Error(`ShaderProgram: ${typeName} shader compile failed — ${log}`);
73
+ }
74
+ return shader;
75
+ }
76
+ }
@@ -0,0 +1,25 @@
1
+ export interface PoolTexture {
2
+ readonly glTexture: WebGLTexture;
3
+ readonly width: number;
4
+ readonly height: number;
5
+ readonly internalFormat: GLenum;
6
+ }
7
+ export interface TexturePoolOptions {
8
+ maxTextures?: number;
9
+ }
10
+ export declare class TexturePool {
11
+ private readonly _maxTextures;
12
+ private readonly _free;
13
+ private readonly _lruFree;
14
+ private _allocated;
15
+ constructor(options?: TexturePoolOptions);
16
+ acquire(gl: WebGL2RenderingContext, width: number, height: number, internalFormat?: GLenum): PoolTexture | null;
17
+ release(entry: PoolTexture): void;
18
+ handleContextLost(): void;
19
+ getLeasedCount(): number;
20
+ dispose(gl: WebGL2RenderingContext): void;
21
+ private _bucketKey;
22
+ private _removeFromLru;
23
+ private _allocate;
24
+ private _evict;
25
+ }
@@ -0,0 +1,93 @@
1
+ const DEFAULT_MAX_TEXTURES = 16;
2
+ export class TexturePool {
3
+ constructor(options = {}) {
4
+ this._free = new Map();
5
+ this._lruFree = [];
6
+ this._allocated = 0;
7
+ this._maxTextures = options.maxTextures ?? DEFAULT_MAX_TEXTURES;
8
+ }
9
+ acquire(gl, width, height, internalFormat) {
10
+ const fmt = internalFormat ?? gl.RGBA;
11
+ const key = this._bucketKey(width, height, fmt);
12
+ const bucket = this._free.get(key);
13
+ if (bucket && bucket.length > 0) {
14
+ const entry = bucket.pop();
15
+ this._removeFromLru(entry);
16
+ return entry;
17
+ }
18
+ if (this._allocated < this._maxTextures) {
19
+ return this._allocate(gl, width, height, fmt);
20
+ }
21
+ if (this._lruFree.length > 0) {
22
+ this._evict(gl);
23
+ return this._allocate(gl, width, height, fmt);
24
+ }
25
+ return null;
26
+ }
27
+ release(entry) {
28
+ const key = this._bucketKey(entry.width, entry.height, entry.internalFormat);
29
+ let bucket = this._free.get(key);
30
+ if (!bucket) {
31
+ bucket = [];
32
+ this._free.set(key, bucket);
33
+ }
34
+ bucket.push(entry);
35
+ this._lruFree.push(entry);
36
+ }
37
+ handleContextLost() {
38
+ this._free.clear();
39
+ this._lruFree.length = 0;
40
+ this._allocated = 0;
41
+ }
42
+ getLeasedCount() {
43
+ return this._allocated - this._lruFree.length;
44
+ }
45
+ dispose(gl) {
46
+ for (const entry of this._lruFree) {
47
+ gl.deleteTexture(entry.glTexture);
48
+ }
49
+ this._free.clear();
50
+ this._lruFree.length = 0;
51
+ this._allocated = 0;
52
+ }
53
+ _bucketKey(width, height, internalFormat) {
54
+ return `${width}:${height}:${internalFormat}`;
55
+ }
56
+ _removeFromLru(entry) {
57
+ const idx = this._lruFree.indexOf(entry);
58
+ if (idx !== -1) {
59
+ this._lruFree.splice(idx, 1);
60
+ }
61
+ }
62
+ _allocate(gl, width, height, internalFormat) {
63
+ const glTexture = gl.createTexture();
64
+ if (!glTexture) {
65
+ throw new Error('TexturePool: gl.createTexture() returned null');
66
+ }
67
+ gl.bindTexture(gl.TEXTURE_2D, glTexture);
68
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
69
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
70
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
71
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
72
+ gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
73
+ gl.bindTexture(gl.TEXTURE_2D, null);
74
+ this._allocated++;
75
+ return { glTexture, width, height, internalFormat };
76
+ }
77
+ _evict(gl) {
78
+ const oldest = this._lruFree.shift();
79
+ if (!oldest)
80
+ return;
81
+ const key = this._bucketKey(oldest.width, oldest.height, oldest.internalFormat);
82
+ const bucket = this._free.get(key);
83
+ if (bucket) {
84
+ const idx = bucket.indexOf(oldest);
85
+ if (idx !== -1)
86
+ bucket.splice(idx, 1);
87
+ if (bucket.length === 0)
88
+ this._free.delete(key);
89
+ }
90
+ gl.deleteTexture(oldest.glTexture);
91
+ this._allocated--;
92
+ }
93
+ }
@@ -0,0 +1,13 @@
1
+ import type { TexturePool } from './TexturePool';
2
+ export declare class VideoTexture {
3
+ private readonly _pool;
4
+ private _entry;
5
+ private _hasContent;
6
+ constructor(pool: TexturePool);
7
+ get hasContent(): boolean;
8
+ upload(gl: WebGL2RenderingContext, frame: VideoFrame | ImageBitmap): boolean;
9
+ bind(gl: WebGL2RenderingContext, unit: number): number;
10
+ handleContextLost(): void;
11
+ release(): void;
12
+ dispose(): void;
13
+ }
@@ -0,0 +1,54 @@
1
+ export class VideoTexture {
2
+ constructor(pool) {
3
+ this._entry = null;
4
+ this._hasContent = false;
5
+ this._pool = pool;
6
+ }
7
+ get hasContent() {
8
+ return this._hasContent;
9
+ }
10
+ upload(gl, frame) {
11
+ const width = 'displayWidth' in frame ? frame.displayWidth : frame.width;
12
+ const height = 'displayHeight' in frame ? frame.displayHeight : frame.height;
13
+ if (!this._entry
14
+ || this._entry.width !== width
15
+ || this._entry.height !== height) {
16
+ if (this._entry) {
17
+ this._pool.release(this._entry);
18
+ this._entry = null;
19
+ }
20
+ const acquired = this._pool.acquire(gl, width, height, gl.RGBA);
21
+ if (!acquired) {
22
+ return false;
23
+ }
24
+ this._entry = acquired;
25
+ }
26
+ gl.bindTexture(gl.TEXTURE_2D, this._entry.glTexture);
27
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, frame);
28
+ gl.bindTexture(gl.TEXTURE_2D, null);
29
+ this._hasContent = true;
30
+ return true;
31
+ }
32
+ bind(gl, unit) {
33
+ if (!this._entry || !this._hasContent) {
34
+ return -1;
35
+ }
36
+ gl.activeTexture(gl.TEXTURE0 + unit);
37
+ gl.bindTexture(gl.TEXTURE_2D, this._entry.glTexture);
38
+ return unit;
39
+ }
40
+ handleContextLost() {
41
+ this._entry = null;
42
+ this._hasContent = false;
43
+ }
44
+ release() {
45
+ if (this._entry) {
46
+ this._pool.release(this._entry);
47
+ this._entry = null;
48
+ }
49
+ this._hasContent = false;
50
+ }
51
+ dispose() {
52
+ this.release();
53
+ }
54
+ }
@@ -0,0 +1,28 @@
1
+ export type ContextLostHandler = () => void;
2
+ export type ContextRestoredHandler = (gl: WebGL2RenderingContext) => void;
3
+ export interface WebGLContextOptions {
4
+ onLost?: ContextLostHandler;
5
+ onRestore?: ContextRestoredHandler;
6
+ alpha?: boolean;
7
+ powerPreference?: WebGLPowerPreference;
8
+ preserveDrawingBuffer?: boolean;
9
+ }
10
+ export declare class WebGLContext {
11
+ readonly canvas: HTMLCanvasElement;
12
+ private _gl;
13
+ private _isWebGL2;
14
+ private _lost;
15
+ private readonly _onLost;
16
+ private readonly _onRestore;
17
+ private readonly _handleLost;
18
+ private readonly _handleRestored;
19
+ constructor(options?: WebGLContextOptions);
20
+ get isLost(): boolean;
21
+ get isWebGL2(): boolean;
22
+ get gl(): WebGL2RenderingContext | null;
23
+ resize(cssWidth: number, cssHeight: number, dpr?: number): void;
24
+ clear(): void;
25
+ setClearColor(r: number, g: number, b: number, a: number): void;
26
+ dispose(): void;
27
+ private _initGLState;
28
+ }