@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.
- package/dist/actions/index.d.ts +3 -0
- package/dist/actions/index.js +1 -0
- package/dist/actions/splitClipAtPlayhead.d.ts +8 -0
- package/dist/actions/splitClipAtPlayhead.js +34 -0
- package/dist/actions/types.d.ts +8 -0
- package/dist/actions/types.js +1 -0
- package/dist/assets/importFiles.d.ts +29 -0
- package/dist/assets/importFiles.js +351 -0
- package/dist/assets/index.d.ts +10 -0
- package/dist/assets/index.js +13 -0
- package/dist/assets/store.d.ts +13 -0
- package/dist/assets/store.js +20 -0
- package/dist/assets/types.d.ts +23 -0
- package/dist/assets/types.js +1 -0
- package/dist/debug/trace.d.ts +20 -0
- package/dist/debug/trace.js +98 -0
- package/dist/editor/TimelineEngine.d.ts +65 -0
- package/dist/editor/TimelineEngine.js +413 -0
- package/dist/editor-context.d.ts +10 -0
- package/dist/editor-context.js +10 -0
- package/dist/elements/audio.d.ts +13 -0
- package/dist/elements/audio.js +4 -0
- package/dist/elements/base.d.ts +40 -0
- package/dist/elements/base.js +39 -0
- package/dist/elements/image.d.ts +13 -0
- package/dist/elements/image.js +4 -0
- package/dist/elements/text.d.ts +12 -0
- package/dist/elements/text.js +8 -0
- package/dist/elements/video.d.ts +13 -0
- package/dist/elements/video.js +4 -0
- package/dist/export/ExportWorker.d.ts +1 -0
- package/dist/export/ExportWorker.js +371 -0
- package/dist/export/exportVideo.d.ts +3 -0
- package/dist/export/exportVideo.js +118 -0
- package/dist/export/index.d.ts +2 -0
- package/dist/export/index.js +1 -0
- package/dist/export/lazyExport.d.ts +4 -0
- package/dist/export/lazyExport.js +4 -0
- package/dist/export/types.d.ts +37 -0
- package/dist/export/types.js +1 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.js +28 -0
- package/dist/media/audio/AudioPlaybackController.d.ts +26 -0
- package/dist/media/audio/AudioPlaybackController.js +125 -0
- package/dist/media/video/DecoderBackedVideoFrameProvider.d.ts +51 -0
- package/dist/media/video/DecoderBackedVideoFrameProvider.js +125 -0
- package/dist/media/video/FrameCache.d.ts +28 -0
- package/dist/media/video/FrameCache.js +88 -0
- package/dist/media/video/StreamingFrameProducer.d.ts +57 -0
- package/dist/media/video/StreamingFrameProducer.js +356 -0
- package/dist/media/video/VideoDecoderManager.d.ts +59 -0
- package/dist/media/video/VideoDecoderManager.js +342 -0
- package/dist/media/video/VideoFrameProvider.d.ts +101 -0
- package/dist/media/video/VideoFrameProvider.js +257 -0
- package/dist/media/video/demuxer/MediabunnyDemuxer.d.ts +23 -0
- package/dist/media/video/demuxer/MediabunnyDemuxer.js +88 -0
- package/dist/media/video/demuxer/createMediabunnyBackend.d.ts +32 -0
- package/dist/media/video/demuxer/createMediabunnyBackend.js +156 -0
- package/dist/media/video/index.d.ts +8 -0
- package/dist/media/video/index.js +5 -0
- package/dist/playback/PlaybackEngine.d.ts +50 -0
- package/dist/playback/PlaybackEngine.js +188 -0
- package/dist/renderer/gpu/GpuRenderer.d.ts +38 -0
- package/dist/renderer/gpu/GpuRenderer.js +208 -0
- package/dist/renderer/gpu/RenderGraph.d.ts +10 -0
- package/dist/renderer/gpu/RenderGraph.js +80 -0
- package/dist/renderer/gpu/ShaderProgram.d.ts +14 -0
- package/dist/renderer/gpu/ShaderProgram.js +76 -0
- package/dist/renderer/gpu/TexturePool.d.ts +25 -0
- package/dist/renderer/gpu/TexturePool.js +93 -0
- package/dist/renderer/gpu/VideoTexture.d.ts +13 -0
- package/dist/renderer/gpu/VideoTexture.js +54 -0
- package/dist/renderer/gpu/WebGLContext.d.ts +28 -0
- package/dist/renderer/gpu/WebGLContext.js +102 -0
- package/dist/renderer/gpu/debug/DebugGpuRenderer.d.ts +27 -0
- package/dist/renderer/gpu/debug/DebugGpuRenderer.js +108 -0
- package/dist/renderer/gpu/debug/DebugOverlay.d.ts +17 -0
- package/dist/renderer/gpu/debug/DebugOverlay.js +83 -0
- package/dist/renderer/gpu/debug/GpuDebugCounters.d.ts +38 -0
- package/dist/renderer/gpu/debug/GpuDebugCounters.js +72 -0
- package/dist/renderer/gpu/debug/GpuDebugGlobal.d.ts +16 -0
- package/dist/renderer/gpu/debug/GpuDebugGlobal.js +14 -0
- package/dist/renderer/gpu/debug/GpuRendererDebugPanel.d.ts +31 -0
- package/dist/renderer/gpu/debug/GpuRendererDebugPanel.js +128 -0
- package/dist/renderer/gpu/debug/RecordingGl.d.ts +88 -0
- package/dist/renderer/gpu/debug/RecordingGl.js +214 -0
- package/dist/renderer/gpu/debug/playground.d.ts +20 -0
- package/dist/renderer/gpu/debug/playground.js +64 -0
- package/dist/renderer/gpu/debug/scenarios.d.ts +7 -0
- package/dist/renderer/gpu/debug/scenarios.js +145 -0
- package/dist/renderer/gpu/debug/types.d.ts +16 -0
- package/dist/renderer/gpu/debug/types.js +1 -0
- package/dist/renderer/gpu/layers/FrameProbeLayer.d.ts +16 -0
- package/dist/renderer/gpu/layers/FrameProbeLayer.js +127 -0
- package/dist/renderer/gpu/layers/ImageLayer.d.ts +23 -0
- package/dist/renderer/gpu/layers/ImageLayer.js +124 -0
- package/dist/renderer/gpu/layers/TestLayer.d.ts +16 -0
- package/dist/renderer/gpu/layers/TestLayer.js +109 -0
- package/dist/renderer/gpu/layers/TextLayer.d.ts +19 -0
- package/dist/renderer/gpu/layers/TextLayer.js +166 -0
- package/dist/renderer/gpu/layers/VideoLayer.d.ts +38 -0
- package/dist/renderer/gpu/layers/VideoLayer.js +194 -0
- package/dist/renderer/gpu/layers/drawRect.d.ts +13 -0
- package/dist/renderer/gpu/layers/drawRect.js +55 -0
- package/dist/renderer/gpu/layers/objectFit.d.ts +7 -0
- package/dist/renderer/gpu/layers/objectFit.js +26 -0
- package/dist/renderer/gpu/layers/textLayout.d.ts +47 -0
- package/dist/renderer/gpu/layers/textLayout.js +82 -0
- package/dist/renderer/gpu/layers/types.d.ts +18 -0
- package/dist/renderer/gpu/layers/types.js +1 -0
- package/dist/renderer/gpu/shaders/quad.frag.d.ts +1 -0
- package/dist/renderer/gpu/shaders/quad.frag.js +14 -0
- package/dist/renderer/gpu/shaders/quad.vert.d.ts +1 -0
- package/dist/renderer/gpu/shaders/quad.vert.js +28 -0
- package/dist/renderer/gpu/types.d.ts +21 -0
- package/dist/renderer/gpu/types.js +1 -0
- package/dist/renderer/gpu/viewport.d.ts +7 -0
- package/dist/renderer/gpu/viewport.js +33 -0
- package/dist/renderer/types.d.ts +7 -0
- package/dist/renderer/types.js +1 -0
- package/dist/resolver/resolveTimeline.d.ts +3 -0
- package/dist/resolver/resolveTimeline.js +249 -0
- package/dist/resolver/scene.d.ts +54 -0
- package/dist/resolver/scene.js +1 -0
- package/dist/stores/playback.store.d.ts +50 -0
- package/dist/stores/playback.store.js +42 -0
- package/dist/stores/selection.store.d.ts +12 -0
- package/dist/stores/selection.store.js +19 -0
- package/dist/stores/tracks.store.d.ts +19 -0
- package/dist/stores/tracks.store.js +27 -0
- package/dist/stores/transitions.store.d.ts +9 -0
- package/dist/stores/transitions.store.js +7 -0
- package/dist/track/track.d.ts +8 -0
- package/dist/track/track.js +19 -0
- package/dist/types/index.d.ts +117 -0
- package/dist/types/index.js +1 -0
- package/dist/utils/frames.d.ts +20 -0
- package/dist/utils/frames.js +40 -0
- package/dist/utils/id.d.ts +1 -0
- package/dist/utils/id.js +3 -0
- package/dist/utils/snap.d.ts +5 -0
- package/dist/utils/snap.js +79 -0
- package/dist/visitor/add.d.ts +3 -0
- package/dist/visitor/add.js +16 -0
- package/dist/visitor/clone.d.ts +3 -0
- package/dist/visitor/clone.js +25 -0
- package/dist/visitor/remove.d.ts +5 -0
- package/dist/visitor/remove.js +29 -0
- package/dist/visitor/split.d.ts +3 -0
- package/dist/visitor/split.js +31 -0
- package/dist/visitor/update.d.ts +4 -0
- package/dist/visitor/update.js +31 -0
- 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
|
+
}
|