webcodecs-node 0.5.2 → 0.7.1
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/README.md +279 -0
- package/dist/backends/types.d.ts +2 -0
- package/dist/backends/types.d.ts.map +1 -1
- package/dist/backends/types.js.map +1 -1
- package/dist/canvas/canvas-utils.d.ts +115 -0
- package/dist/canvas/canvas-utils.d.ts.map +1 -0
- package/dist/canvas/canvas-utils.js +169 -0
- package/dist/canvas/canvas-utils.js.map +1 -0
- package/dist/canvas/frame-loop.d.ts +113 -0
- package/dist/canvas/frame-loop.d.ts.map +1 -0
- package/dist/canvas/frame-loop.js +291 -0
- package/dist/canvas/frame-loop.js.map +1 -0
- package/dist/canvas/gpu-context.d.ts +61 -0
- package/dist/canvas/gpu-context.d.ts.map +1 -0
- package/dist/canvas/gpu-context.js +134 -0
- package/dist/canvas/gpu-context.js.map +1 -0
- package/dist/canvas/index.d.ts +22 -0
- package/dist/canvas/index.d.ts.map +1 -0
- package/dist/canvas/index.js +25 -0
- package/dist/canvas/index.js.map +1 -0
- package/dist/canvas/types.d.ts +101 -0
- package/dist/canvas/types.d.ts.map +1 -0
- package/dist/canvas/types.js +7 -0
- package/dist/canvas/types.js.map +1 -0
- package/dist/codec-utils/formats.d.ts.map +1 -1
- package/dist/codec-utils/formats.js +31 -0
- package/dist/codec-utils/formats.js.map +1 -1
- package/dist/config/ffmpeg-quality.d.ts +14 -0
- package/dist/config/ffmpeg-quality.d.ts.map +1 -0
- package/dist/config/ffmpeg-quality.js +41 -0
- package/dist/config/ffmpeg-quality.js.map +1 -0
- package/dist/containers/Muxer.d.ts.map +1 -1
- package/dist/containers/Muxer.js +4 -1
- package/dist/containers/Muxer.js.map +1 -1
- package/dist/core/AudioData.d.ts +2 -1
- package/dist/core/AudioData.d.ts.map +1 -1
- package/dist/core/AudioData.js.map +1 -1
- package/dist/core/VideoFrame.d.ts +2 -2
- package/dist/core/VideoFrame.d.ts.map +1 -1
- package/dist/core/VideoFrame.js +58 -48
- package/dist/core/VideoFrame.js.map +1 -1
- package/dist/decoders/AudioDecoder.d.ts +1 -0
- package/dist/decoders/AudioDecoder.d.ts.map +1 -1
- package/dist/decoders/AudioDecoder.js +39 -17
- package/dist/decoders/AudioDecoder.js.map +1 -1
- package/dist/decoders/ImageDecoder.d.ts +1 -0
- package/dist/decoders/ImageDecoder.d.ts.map +1 -1
- package/dist/decoders/ImageDecoder.js +40 -3
- package/dist/decoders/ImageDecoder.js.map +1 -1
- package/dist/decoders/VideoDecoder.d.ts +12 -0
- package/dist/decoders/VideoDecoder.d.ts.map +1 -1
- package/dist/decoders/VideoDecoder.js +56 -13
- package/dist/decoders/VideoDecoder.js.map +1 -1
- package/dist/demos/demo-1080p-transcode.js +8 -2
- package/dist/demos/demo-1080p-transcode.js.map +1 -1
- package/dist/demos/demo-audio-visualizer.d.ts +11 -0
- package/dist/demos/demo-audio-visualizer.d.ts.map +1 -0
- package/dist/demos/demo-audio-visualizer.js +281 -0
- package/dist/demos/demo-audio-visualizer.js.map +1 -0
- package/dist/demos/demo-dvd-logo.d.ts +8 -0
- package/dist/demos/demo-dvd-logo.d.ts.map +1 -0
- package/dist/demos/demo-dvd-logo.js +196 -0
- package/dist/demos/demo-dvd-logo.js.map +1 -0
- package/dist/demos/demo-four-corners.js +9 -0
- package/dist/demos/demo-four-corners.js.map +1 -1
- package/dist/demos/demo-streaming.js +6 -0
- package/dist/demos/demo-streaming.js.map +1 -1
- package/dist/demos/demo-webcodecs.js +1 -0
- package/dist/demos/demo-webcodecs.js.map +1 -1
- package/dist/encoders/AudioEncoder.d.ts +3 -0
- package/dist/encoders/AudioEncoder.d.ts.map +1 -1
- package/dist/encoders/AudioEncoder.js +70 -28
- package/dist/encoders/AudioEncoder.js.map +1 -1
- package/dist/encoders/ImageEncoder.d.ts +80 -0
- package/dist/encoders/ImageEncoder.d.ts.map +1 -0
- package/dist/encoders/ImageEncoder.js +156 -0
- package/dist/encoders/ImageEncoder.js.map +1 -0
- package/dist/encoders/VideoEncoder.d.ts +11 -0
- package/dist/encoders/VideoEncoder.d.ts.map +1 -1
- package/dist/encoders/VideoEncoder.js +58 -7
- package/dist/encoders/VideoEncoder.js.map +1 -1
- package/dist/encoders/index.d.ts +1 -0
- package/dist/encoders/index.d.ts.map +1 -1
- package/dist/encoders/index.js +1 -0
- package/dist/encoders/index.js.map +1 -1
- package/dist/formats/color-space.d.ts +94 -1
- package/dist/formats/color-space.d.ts.map +1 -1
- package/dist/formats/color-space.js +51 -1
- package/dist/formats/color-space.js.map +1 -1
- package/dist/formats/pixel-formats.d.ts +17 -1
- package/dist/formats/pixel-formats.d.ts.map +1 -1
- package/dist/formats/pixel-formats.js +74 -4
- package/dist/formats/pixel-formats.js.map +1 -1
- package/dist/hardware/detection.d.ts.map +1 -1
- package/dist/hardware/detection.js +5 -2
- package/dist/hardware/detection.js.map +1 -1
- package/dist/hardware/encoder-args.d.ts.map +1 -1
- package/dist/hardware/encoder-args.js +6 -6
- package/dist/hardware/encoder-args.js.map +1 -1
- package/dist/index.d.ts +11 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -2
- package/dist/index.js.map +1 -1
- package/dist/node-av/NodeAvAudioDecoder.d.ts.map +1 -1
- package/dist/node-av/NodeAvAudioDecoder.js +3 -0
- package/dist/node-av/NodeAvAudioDecoder.js.map +1 -1
- package/dist/node-av/NodeAvAudioEncoder.d.ts +5 -0
- package/dist/node-av/NodeAvAudioEncoder.d.ts.map +1 -1
- package/dist/node-av/NodeAvAudioEncoder.js +102 -9
- package/dist/node-av/NodeAvAudioEncoder.js.map +1 -1
- package/dist/node-av/NodeAvVideoEncoder.d.ts +2 -0
- package/dist/node-av/NodeAvVideoEncoder.d.ts.map +1 -1
- package/dist/node-av/NodeAvVideoEncoder.js +44 -6
- package/dist/node-av/NodeAvVideoEncoder.js.map +1 -1
- package/dist/node-av/WebPImageDecoder.d.ts +54 -0
- package/dist/node-av/WebPImageDecoder.d.ts.map +1 -0
- package/dist/node-av/WebPImageDecoder.js +176 -0
- package/dist/node-av/WebPImageDecoder.js.map +1 -0
- package/dist/polyfills/OffscreenCanvas.d.ts +141 -7
- package/dist/polyfills/OffscreenCanvas.d.ts.map +1 -1
- package/dist/polyfills/OffscreenCanvas.js +217 -17
- package/dist/polyfills/OffscreenCanvas.js.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/native-frame.d.ts +74 -0
- package/dist/types/native-frame.d.ts.map +1 -0
- package/dist/types/native-frame.js +32 -0
- package/dist/types/native-frame.js.map +1 -0
- package/dist/utils/codec-validation.d.ts +33 -0
- package/dist/utils/codec-validation.d.ts.map +1 -0
- package/dist/utils/codec-validation.js +247 -0
- package/dist/utils/codec-validation.js.map +1 -0
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/type-guards.d.ts +29 -1
- package/dist/utils/type-guards.d.ts.map +1 -1
- package/dist/utils/type-guards.js +47 -2
- package/dist/utils/type-guards.js.map +1 -1
- package/docs/api.md +155 -0
- package/docs/configuration.md +27 -0
- package/examples/README.md +28 -1
- package/examples/canvas-encoding.ts +222 -0
- package/examples/offscreen-canvas.ts +230 -0
- package/package.json +9 -3
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FrameLoop - High-performance frame generation with backpressure
|
|
3
|
+
*
|
|
4
|
+
* Manages frame generation with:
|
|
5
|
+
* - Backpressure via maxQueueSize (default: 8)
|
|
6
|
+
* - Automatic canvas reset before each frame
|
|
7
|
+
* - Proper VideoFrame lifecycle management
|
|
8
|
+
* - Async pipelining for optimal throughput
|
|
9
|
+
*
|
|
10
|
+
* Best practices implemented:
|
|
11
|
+
* 1. Memory lifecycle: Explicit frame closing with try...finally
|
|
12
|
+
* 2. Canvas state: ctx.reset() or clearRect at start of every frame
|
|
13
|
+
* 3. Color formats: RGBA output, even dimensions for YUV420
|
|
14
|
+
* 4. Pipeline optimization: Async pipelining with backpressure
|
|
15
|
+
* 5. Raw buffer export: Always uses toBuffer('raw')
|
|
16
|
+
*/
|
|
17
|
+
import { Canvas } from 'skia-canvas';
|
|
18
|
+
import { VideoFrame } from '../core/VideoFrame.js';
|
|
19
|
+
import type { FrameLoopConfig, FrameLoopState } from './types.js';
|
|
20
|
+
type SkiaContext = any;
|
|
21
|
+
/**
|
|
22
|
+
* FrameLoop class for generating video frames with backpressure control
|
|
23
|
+
*/
|
|
24
|
+
export declare class FrameLoop {
|
|
25
|
+
private canvas;
|
|
26
|
+
private ctx;
|
|
27
|
+
private config;
|
|
28
|
+
private state;
|
|
29
|
+
private frameIndex;
|
|
30
|
+
private queueSize;
|
|
31
|
+
private pendingFrames;
|
|
32
|
+
private resolveWaitForDrain;
|
|
33
|
+
private startTime;
|
|
34
|
+
private frameDurationUs;
|
|
35
|
+
constructor(config: FrameLoopConfig);
|
|
36
|
+
/**
|
|
37
|
+
* Get current state
|
|
38
|
+
*/
|
|
39
|
+
getState(): FrameLoopState;
|
|
40
|
+
/**
|
|
41
|
+
* Get current queue size
|
|
42
|
+
*/
|
|
43
|
+
getQueueSize(): number;
|
|
44
|
+
/**
|
|
45
|
+
* Get canvas width
|
|
46
|
+
*/
|
|
47
|
+
getWidth(): number;
|
|
48
|
+
/**
|
|
49
|
+
* Get canvas height
|
|
50
|
+
*/
|
|
51
|
+
getHeight(): number;
|
|
52
|
+
/**
|
|
53
|
+
* Get the underlying canvas for direct access
|
|
54
|
+
*/
|
|
55
|
+
getCanvas(): Canvas;
|
|
56
|
+
/**
|
|
57
|
+
* Get the 2D context for direct access
|
|
58
|
+
*/
|
|
59
|
+
getContext(): SkiaContext;
|
|
60
|
+
/**
|
|
61
|
+
* Start frame generation
|
|
62
|
+
*
|
|
63
|
+
* @param totalFrames - Total number of frames to generate (Infinity for continuous)
|
|
64
|
+
*/
|
|
65
|
+
start(totalFrames?: number): Promise<void>;
|
|
66
|
+
/**
|
|
67
|
+
* Stop frame generation immediately
|
|
68
|
+
*/
|
|
69
|
+
stop(): void;
|
|
70
|
+
/**
|
|
71
|
+
* Pause frame generation
|
|
72
|
+
*/
|
|
73
|
+
pause(): void;
|
|
74
|
+
/**
|
|
75
|
+
* Resume frame generation
|
|
76
|
+
*/
|
|
77
|
+
resume(): void;
|
|
78
|
+
/**
|
|
79
|
+
* Get the next frame from the loop
|
|
80
|
+
*
|
|
81
|
+
* Called by the encoder to consume frames. The caller takes ownership
|
|
82
|
+
* of the frame and MUST call frame.close() when done.
|
|
83
|
+
*
|
|
84
|
+
* @returns The next VideoFrame, or null if none available
|
|
85
|
+
*/
|
|
86
|
+
takeFrame(): VideoFrame | null;
|
|
87
|
+
/**
|
|
88
|
+
* Signal that a frame has been consumed externally
|
|
89
|
+
*
|
|
90
|
+
* Use this when frames are consumed outside of takeFrame(),
|
|
91
|
+
* to maintain correct backpressure accounting.
|
|
92
|
+
*/
|
|
93
|
+
frameConsumed(): void;
|
|
94
|
+
/**
|
|
95
|
+
* Check if there are frames available
|
|
96
|
+
*/
|
|
97
|
+
hasFrames(): boolean;
|
|
98
|
+
/**
|
|
99
|
+
* Get the current frame index
|
|
100
|
+
*/
|
|
101
|
+
getCurrentFrameIndex(): number;
|
|
102
|
+
private generateFrame;
|
|
103
|
+
private waitForDrain;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Create a FrameLoop with the given configuration
|
|
107
|
+
*
|
|
108
|
+
* @param config - FrameLoop configuration
|
|
109
|
+
* @returns A new FrameLoop instance
|
|
110
|
+
*/
|
|
111
|
+
export declare function createFrameLoop(config: FrameLoopConfig): FrameLoop;
|
|
112
|
+
export {};
|
|
113
|
+
//# sourceMappingURL=frame-loop.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frame-loop.d.ts","sourceRoot":"","sources":["../../src/canvas/frame-loop.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAInD,OAAO,KAAK,EACV,eAAe,EAEf,cAAc,EACf,MAAM,YAAY,CAAC;AAOpB,KAAK,WAAW,GAAG,GAAG,CAAC;AAEvB;;GAEG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,GAAG,CAAc;IACzB,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,KAAK,CAA0B;IACvC,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,aAAa,CAAoB;IACzC,OAAO,CAAC,mBAAmB,CAA6B;IAGxD,OAAO,CAAC,SAAS,CAAa;IAC9B,OAAO,CAAC,eAAe,CAAS;gBAEpB,MAAM,EAAE,eAAe;IAmCnC;;OAEG;IACH,QAAQ,IAAI,cAAc;IAI1B;;OAEG;IACH,YAAY,IAAI,MAAM;IAItB;;OAEG;IACH,QAAQ,IAAI,MAAM;IAIlB;;OAEG;IACH,SAAS,IAAI,MAAM;IAInB;;OAEG;IACH,SAAS,IAAI,MAAM;IAInB;;OAEG;IACH,UAAU,IAAI,WAAW;IAIzB;;;;OAIG;IACG,KAAK,CAAC,WAAW,GAAE,MAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAwC1D;;OAEG;IACH,IAAI,IAAI,IAAI;IAqBZ;;OAEG;IACH,KAAK,IAAI,IAAI;IAMb;;OAEG;IACH,MAAM,IAAI,IAAI;IAWd;;;;;;;OAOG;IACH,SAAS,IAAI,UAAU,GAAG,IAAI;IAgB9B;;;;;OAKG;IACH,aAAa,IAAI,IAAI;IAWrB;;OAEG;IACH,SAAS,IAAI,OAAO;IAIpB;;OAEG;IACH,oBAAoB,IAAI,MAAM;YAQhB,aAAa;IA6C3B,OAAO,CAAC,YAAY;CAKrB;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,eAAe,GAAG,SAAS,CAElE"}
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FrameLoop - High-performance frame generation with backpressure
|
|
3
|
+
*
|
|
4
|
+
* Manages frame generation with:
|
|
5
|
+
* - Backpressure via maxQueueSize (default: 8)
|
|
6
|
+
* - Automatic canvas reset before each frame
|
|
7
|
+
* - Proper VideoFrame lifecycle management
|
|
8
|
+
* - Async pipelining for optimal throughput
|
|
9
|
+
*
|
|
10
|
+
* Best practices implemented:
|
|
11
|
+
* 1. Memory lifecycle: Explicit frame closing with try...finally
|
|
12
|
+
* 2. Canvas state: ctx.reset() or clearRect at start of every frame
|
|
13
|
+
* 3. Color formats: RGBA output, even dimensions for YUV420
|
|
14
|
+
* 4. Pipeline optimization: Async pipelining with backpressure
|
|
15
|
+
* 5. Raw buffer export: Always uses toBuffer('raw')
|
|
16
|
+
*/
|
|
17
|
+
import { VideoFrame } from '../core/VideoFrame.js';
|
|
18
|
+
import { createLogger } from '../utils/logger.js';
|
|
19
|
+
import { createCanvas, ensureEvenDimensions } from './gpu-context.js';
|
|
20
|
+
import { getRawPixels, resetCanvas, bufferToUint8Array } from './canvas-utils.js';
|
|
21
|
+
const logger = createLogger('FrameLoop');
|
|
22
|
+
const DEFAULT_MAX_QUEUE_SIZE = 8;
|
|
23
|
+
/**
|
|
24
|
+
* FrameLoop class for generating video frames with backpressure control
|
|
25
|
+
*/
|
|
26
|
+
export class FrameLoop {
|
|
27
|
+
canvas;
|
|
28
|
+
ctx;
|
|
29
|
+
config;
|
|
30
|
+
state = 'idle';
|
|
31
|
+
frameIndex = 0;
|
|
32
|
+
queueSize = 0;
|
|
33
|
+
pendingFrames = [];
|
|
34
|
+
resolveWaitForDrain = null;
|
|
35
|
+
// Frame timing
|
|
36
|
+
startTime = 0;
|
|
37
|
+
frameDurationUs;
|
|
38
|
+
constructor(config) {
|
|
39
|
+
// Ensure even dimensions for YUV420 compatibility
|
|
40
|
+
const dims = ensureEvenDimensions(config.width, config.height);
|
|
41
|
+
this.config = {
|
|
42
|
+
width: dims.width,
|
|
43
|
+
height: dims.height,
|
|
44
|
+
frameRate: config.frameRate,
|
|
45
|
+
maxQueueSize: config.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE,
|
|
46
|
+
gpu: config.gpu ?? true,
|
|
47
|
+
onFrame: config.onFrame,
|
|
48
|
+
onComplete: config.onComplete ?? (() => { }),
|
|
49
|
+
onError: config.onError ?? (() => { }),
|
|
50
|
+
};
|
|
51
|
+
// Frame duration in microseconds
|
|
52
|
+
this.frameDurationUs = Math.round(1_000_000 / this.config.frameRate);
|
|
53
|
+
// Create GPU-accelerated canvas
|
|
54
|
+
this.canvas = createCanvas({
|
|
55
|
+
width: dims.width,
|
|
56
|
+
height: dims.height,
|
|
57
|
+
gpu: config.gpu,
|
|
58
|
+
});
|
|
59
|
+
this.ctx = this.canvas.getContext('2d');
|
|
60
|
+
// Log creation info (engine property may not be available until first render)
|
|
61
|
+
const engineInfo = this.canvas.engine;
|
|
62
|
+
logger.info(`FrameLoop created: ${dims.width}x${dims.height} @ ${config.frameRate}fps` +
|
|
63
|
+
(engineInfo ? `, GPU: ${engineInfo.renderer}` : ''));
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Get current state
|
|
67
|
+
*/
|
|
68
|
+
getState() {
|
|
69
|
+
return this.state;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Get current queue size
|
|
73
|
+
*/
|
|
74
|
+
getQueueSize() {
|
|
75
|
+
return this.queueSize;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Get canvas width
|
|
79
|
+
*/
|
|
80
|
+
getWidth() {
|
|
81
|
+
return this.config.width;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Get canvas height
|
|
85
|
+
*/
|
|
86
|
+
getHeight() {
|
|
87
|
+
return this.config.height;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Get the underlying canvas for direct access
|
|
91
|
+
*/
|
|
92
|
+
getCanvas() {
|
|
93
|
+
return this.canvas;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Get the 2D context for direct access
|
|
97
|
+
*/
|
|
98
|
+
getContext() {
|
|
99
|
+
return this.ctx;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Start frame generation
|
|
103
|
+
*
|
|
104
|
+
* @param totalFrames - Total number of frames to generate (Infinity for continuous)
|
|
105
|
+
*/
|
|
106
|
+
async start(totalFrames = Infinity) {
|
|
107
|
+
if (this.state === 'running') {
|
|
108
|
+
throw new Error('FrameLoop is already running');
|
|
109
|
+
}
|
|
110
|
+
this.state = 'running';
|
|
111
|
+
this.startTime = performance.now();
|
|
112
|
+
this.frameIndex = 0;
|
|
113
|
+
try {
|
|
114
|
+
while (this.state === 'running' && this.frameIndex < totalFrames) {
|
|
115
|
+
// Backpressure: wait if queue is full
|
|
116
|
+
if (this.queueSize >= this.config.maxQueueSize) {
|
|
117
|
+
logger.debug(`Backpressure: waiting (queue: ${this.queueSize})`);
|
|
118
|
+
await this.waitForDrain();
|
|
119
|
+
if (this.state !== 'running')
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
// Generate frame
|
|
123
|
+
await this.generateFrame();
|
|
124
|
+
this.frameIndex++;
|
|
125
|
+
}
|
|
126
|
+
// Wait for all pending frames to be consumed
|
|
127
|
+
while (this.pendingFrames.length > 0 && this.getState() !== 'stopped') {
|
|
128
|
+
await this.waitForDrain();
|
|
129
|
+
}
|
|
130
|
+
if (this.getState() !== 'stopped') {
|
|
131
|
+
this.state = 'stopped';
|
|
132
|
+
this.config.onComplete();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
this.state = 'stopped';
|
|
137
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
138
|
+
this.config.onError(err);
|
|
139
|
+
throw err;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Stop frame generation immediately
|
|
144
|
+
*/
|
|
145
|
+
stop() {
|
|
146
|
+
this.state = 'stopped';
|
|
147
|
+
// Close any pending frames
|
|
148
|
+
for (const frame of this.pendingFrames) {
|
|
149
|
+
try {
|
|
150
|
+
frame.close();
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
// Ignore close errors
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
this.pendingFrames = [];
|
|
157
|
+
this.queueSize = 0;
|
|
158
|
+
// Unblock any waiting drain
|
|
159
|
+
if (this.resolveWaitForDrain) {
|
|
160
|
+
this.resolveWaitForDrain();
|
|
161
|
+
this.resolveWaitForDrain = null;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Pause frame generation
|
|
166
|
+
*/
|
|
167
|
+
pause() {
|
|
168
|
+
if (this.state === 'running') {
|
|
169
|
+
this.state = 'paused';
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Resume frame generation
|
|
174
|
+
*/
|
|
175
|
+
resume() {
|
|
176
|
+
if (this.state === 'paused') {
|
|
177
|
+
this.state = 'running';
|
|
178
|
+
// Unblock any waiting drain
|
|
179
|
+
if (this.resolveWaitForDrain) {
|
|
180
|
+
this.resolveWaitForDrain();
|
|
181
|
+
this.resolveWaitForDrain = null;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Get the next frame from the loop
|
|
187
|
+
*
|
|
188
|
+
* Called by the encoder to consume frames. The caller takes ownership
|
|
189
|
+
* of the frame and MUST call frame.close() when done.
|
|
190
|
+
*
|
|
191
|
+
* @returns The next VideoFrame, or null if none available
|
|
192
|
+
*/
|
|
193
|
+
takeFrame() {
|
|
194
|
+
const frame = this.pendingFrames.shift();
|
|
195
|
+
if (frame) {
|
|
196
|
+
this.queueSize--;
|
|
197
|
+
// Notify that there's room for more frames
|
|
198
|
+
if (this.resolveWaitForDrain &&
|
|
199
|
+
this.queueSize < this.config.maxQueueSize) {
|
|
200
|
+
this.resolveWaitForDrain();
|
|
201
|
+
this.resolveWaitForDrain = null;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return frame ?? null;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Signal that a frame has been consumed externally
|
|
208
|
+
*
|
|
209
|
+
* Use this when frames are consumed outside of takeFrame(),
|
|
210
|
+
* to maintain correct backpressure accounting.
|
|
211
|
+
*/
|
|
212
|
+
frameConsumed() {
|
|
213
|
+
this.queueSize = Math.max(0, this.queueSize - 1);
|
|
214
|
+
if (this.resolveWaitForDrain &&
|
|
215
|
+
this.queueSize < this.config.maxQueueSize) {
|
|
216
|
+
this.resolveWaitForDrain();
|
|
217
|
+
this.resolveWaitForDrain = null;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Check if there are frames available
|
|
222
|
+
*/
|
|
223
|
+
hasFrames() {
|
|
224
|
+
return this.pendingFrames.length > 0;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Get the current frame index
|
|
228
|
+
*/
|
|
229
|
+
getCurrentFrameIndex() {
|
|
230
|
+
return this.frameIndex;
|
|
231
|
+
}
|
|
232
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
233
|
+
// Private methods
|
|
234
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
235
|
+
async generateFrame() {
|
|
236
|
+
const timing = {
|
|
237
|
+
frameIndex: this.frameIndex,
|
|
238
|
+
timestamp: this.frameIndex * this.frameDurationUs,
|
|
239
|
+
duration: this.frameDurationUs,
|
|
240
|
+
presentationTime: performance.now() - this.startTime,
|
|
241
|
+
};
|
|
242
|
+
// Best practice: Reset canvas at start of every frame
|
|
243
|
+
// This prevents Skia command history buildup
|
|
244
|
+
resetCanvas(this.ctx);
|
|
245
|
+
// Call user's frame rendering function
|
|
246
|
+
await this.config.onFrame(this.ctx, timing);
|
|
247
|
+
// Get raw RGBA pixels (never PNG!)
|
|
248
|
+
const pixels = getRawPixels(this.canvas);
|
|
249
|
+
const pixelArray = bufferToUint8Array(pixels);
|
|
250
|
+
// Create VideoFrame with proper lifecycle
|
|
251
|
+
let frame = null;
|
|
252
|
+
try {
|
|
253
|
+
frame = new VideoFrame(new Uint8Array(pixelArray), {
|
|
254
|
+
format: 'RGBA',
|
|
255
|
+
codedWidth: this.config.width,
|
|
256
|
+
codedHeight: this.config.height,
|
|
257
|
+
timestamp: timing.timestamp,
|
|
258
|
+
duration: timing.duration,
|
|
259
|
+
});
|
|
260
|
+
this.pendingFrames.push(frame);
|
|
261
|
+
this.queueSize++;
|
|
262
|
+
}
|
|
263
|
+
catch (error) {
|
|
264
|
+
// Best practice: Close frame on error
|
|
265
|
+
if (frame) {
|
|
266
|
+
try {
|
|
267
|
+
frame.close();
|
|
268
|
+
}
|
|
269
|
+
catch {
|
|
270
|
+
// Ignore close errors
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
throw error;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
waitForDrain() {
|
|
277
|
+
return new Promise((resolve) => {
|
|
278
|
+
this.resolveWaitForDrain = resolve;
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Create a FrameLoop with the given configuration
|
|
284
|
+
*
|
|
285
|
+
* @param config - FrameLoop configuration
|
|
286
|
+
* @returns A new FrameLoop instance
|
|
287
|
+
*/
|
|
288
|
+
export function createFrameLoop(config) {
|
|
289
|
+
return new FrameLoop(config);
|
|
290
|
+
}
|
|
291
|
+
//# sourceMappingURL=frame-loop.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frame-loop.js","sourceRoot":"","sources":["../../src/canvas/frame-loop.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GPU Context Management for skia-canvas
|
|
3
|
+
*
|
|
4
|
+
* Provides GPU detection and canvas factory with auto-detection
|
|
5
|
+
* for Metal (macOS), Vulkan (Linux/Windows), and D3D (Windows).
|
|
6
|
+
*/
|
|
7
|
+
import { Canvas } from 'skia-canvas';
|
|
8
|
+
import type { GpuEngineInfo, CanvasConfig } from './types.js';
|
|
9
|
+
/**
|
|
10
|
+
* Detect available GPU acceleration
|
|
11
|
+
* Uses a minimal probe canvas to check GPU availability.
|
|
12
|
+
* Results are cached for performance.
|
|
13
|
+
*/
|
|
14
|
+
export declare function detectGpuAcceleration(): GpuEngineInfo;
|
|
15
|
+
/**
|
|
16
|
+
* Check if GPU acceleration is available
|
|
17
|
+
*/
|
|
18
|
+
export declare function isGpuAvailable(): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Get the GPU API name (Metal, Vulkan, D3D, or null for CPU)
|
|
21
|
+
*/
|
|
22
|
+
export declare function getGpuApi(): 'Metal' | 'Vulkan' | 'D3D' | null;
|
|
23
|
+
/**
|
|
24
|
+
* Create a canvas with optimal GPU settings
|
|
25
|
+
*
|
|
26
|
+
* @param config - Canvas configuration
|
|
27
|
+
* @returns A new skia-canvas Canvas instance
|
|
28
|
+
*/
|
|
29
|
+
export declare function createCanvas(config: CanvasConfig): Canvas;
|
|
30
|
+
/**
|
|
31
|
+
* Ensure dimensions are even (required for YUV420 compatibility)
|
|
32
|
+
*
|
|
33
|
+
* Most video codecs (H.264, HEVC, VP9) require even dimensions
|
|
34
|
+
* for chroma subsampling. This utility rounds up odd dimensions.
|
|
35
|
+
*
|
|
36
|
+
* @param width - Original width
|
|
37
|
+
* @param height - Original height
|
|
38
|
+
* @returns Dimensions rounded up to even values
|
|
39
|
+
*/
|
|
40
|
+
export declare function ensureEvenDimensions(width: number, height: number): {
|
|
41
|
+
width: number;
|
|
42
|
+
height: number;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Validate that dimensions are even (throws if not)
|
|
46
|
+
*
|
|
47
|
+
* Many hardware encoders (NVENC, QuickSync, VideoToolbox) will fail
|
|
48
|
+
* silently or crash if dimensions are not divisible by 2. Use this
|
|
49
|
+
* for strict validation in encoder configuration.
|
|
50
|
+
*
|
|
51
|
+
* @param width - Width to validate
|
|
52
|
+
* @param height - Height to validate
|
|
53
|
+
* @throws Error if width or height is odd
|
|
54
|
+
*/
|
|
55
|
+
export declare function validateEvenDimensions(width: number, height: number): void;
|
|
56
|
+
/**
|
|
57
|
+
* Reset the GPU detection cache
|
|
58
|
+
* Useful for testing or when GPU availability may have changed
|
|
59
|
+
*/
|
|
60
|
+
export declare function resetGpuCache(): void;
|
|
61
|
+
//# sourceMappingURL=gpu-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gpu-context.d.ts","sourceRoot":"","sources":["../../src/canvas/gpu-context.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAQ9D;;;;GAIG;AACH,wBAAgB,qBAAqB,IAAI,aAAa,CAyCrD;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,OAAO,CAGxC;AAED;;GAEG;AACH,wBAAgB,SAAS,IAAI,OAAO,GAAG,QAAQ,GAAG,KAAK,GAAG,IAAI,CAG7D;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAYzD;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GACb;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAKnC;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAiB1E;AAED;;;GAGG;AACH,wBAAgB,aAAa,IAAI,IAAI,CAGpC"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GPU Context Management for skia-canvas
|
|
3
|
+
*
|
|
4
|
+
* Provides GPU detection and canvas factory with auto-detection
|
|
5
|
+
* for Metal (macOS), Vulkan (Linux/Windows), and D3D (Windows).
|
|
6
|
+
*/
|
|
7
|
+
import { Canvas } from 'skia-canvas';
|
|
8
|
+
import { createLogger } from '../utils/logger.js';
|
|
9
|
+
const logger = createLogger('GpuContext');
|
|
10
|
+
// Cached GPU availability result
|
|
11
|
+
let gpuAvailability = null;
|
|
12
|
+
let gpuCheckPerformed = false;
|
|
13
|
+
/**
|
|
14
|
+
* Detect available GPU acceleration
|
|
15
|
+
* Uses a minimal probe canvas to check GPU availability.
|
|
16
|
+
* Results are cached for performance.
|
|
17
|
+
*/
|
|
18
|
+
export function detectGpuAcceleration() {
|
|
19
|
+
if (gpuCheckPerformed && gpuAvailability) {
|
|
20
|
+
return gpuAvailability;
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
// Create minimal probe canvas
|
|
24
|
+
const probe = new Canvas(1, 1);
|
|
25
|
+
probe.gpu = true; // Request GPU
|
|
26
|
+
// Access engine info to trigger GPU initialization
|
|
27
|
+
const engine = probe.engine;
|
|
28
|
+
gpuAvailability = {
|
|
29
|
+
renderer: engine.renderer === 'GPU' ? 'GPU' : 'CPU',
|
|
30
|
+
api: engine.api,
|
|
31
|
+
device: engine.device,
|
|
32
|
+
driver: engine.driver,
|
|
33
|
+
threads: engine.threads,
|
|
34
|
+
error: engine.error,
|
|
35
|
+
};
|
|
36
|
+
gpuCheckPerformed = true;
|
|
37
|
+
logger.info(`GPU Detection: ${engine.renderer}${engine.api ? ` (${engine.api})` : ''}`);
|
|
38
|
+
if (engine.error) {
|
|
39
|
+
logger.warn(`GPU fallback reason: ${engine.error}`);
|
|
40
|
+
}
|
|
41
|
+
return gpuAvailability;
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
gpuAvailability = {
|
|
45
|
+
renderer: 'CPU',
|
|
46
|
+
error: error instanceof Error ? error.message : String(error),
|
|
47
|
+
};
|
|
48
|
+
gpuCheckPerformed = true;
|
|
49
|
+
logger.warn('GPU detection failed, using CPU rendering');
|
|
50
|
+
return gpuAvailability;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Check if GPU acceleration is available
|
|
55
|
+
*/
|
|
56
|
+
export function isGpuAvailable() {
|
|
57
|
+
const info = detectGpuAcceleration();
|
|
58
|
+
return info.renderer === 'GPU';
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Get the GPU API name (Metal, Vulkan, D3D, or null for CPU)
|
|
62
|
+
*/
|
|
63
|
+
export function getGpuApi() {
|
|
64
|
+
const info = detectGpuAcceleration();
|
|
65
|
+
return info.api ?? null;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Create a canvas with optimal GPU settings
|
|
69
|
+
*
|
|
70
|
+
* @param config - Canvas configuration
|
|
71
|
+
* @returns A new skia-canvas Canvas instance
|
|
72
|
+
*/
|
|
73
|
+
export function createCanvas(config) {
|
|
74
|
+
const canvas = new Canvas(config.width, config.height);
|
|
75
|
+
// Set GPU preference (auto-detect if not specified)
|
|
76
|
+
if (config.gpu !== undefined) {
|
|
77
|
+
canvas.gpu = config.gpu;
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
// Auto-detect: use GPU if available
|
|
81
|
+
canvas.gpu = isGpuAvailable();
|
|
82
|
+
}
|
|
83
|
+
return canvas;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Ensure dimensions are even (required for YUV420 compatibility)
|
|
87
|
+
*
|
|
88
|
+
* Most video codecs (H.264, HEVC, VP9) require even dimensions
|
|
89
|
+
* for chroma subsampling. This utility rounds up odd dimensions.
|
|
90
|
+
*
|
|
91
|
+
* @param width - Original width
|
|
92
|
+
* @param height - Original height
|
|
93
|
+
* @returns Dimensions rounded up to even values
|
|
94
|
+
*/
|
|
95
|
+
export function ensureEvenDimensions(width, height) {
|
|
96
|
+
return {
|
|
97
|
+
width: width % 2 === 0 ? width : width + 1,
|
|
98
|
+
height: height % 2 === 0 ? height : height + 1,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Validate that dimensions are even (throws if not)
|
|
103
|
+
*
|
|
104
|
+
* Many hardware encoders (NVENC, QuickSync, VideoToolbox) will fail
|
|
105
|
+
* silently or crash if dimensions are not divisible by 2. Use this
|
|
106
|
+
* for strict validation in encoder configuration.
|
|
107
|
+
*
|
|
108
|
+
* @param width - Width to validate
|
|
109
|
+
* @param height - Height to validate
|
|
110
|
+
* @throws Error if width or height is odd
|
|
111
|
+
*/
|
|
112
|
+
export function validateEvenDimensions(width, height) {
|
|
113
|
+
const errors = [];
|
|
114
|
+
if (width % 2 !== 0) {
|
|
115
|
+
errors.push(`width (${width}) must be even for video encoding`);
|
|
116
|
+
}
|
|
117
|
+
if (height % 2 !== 0) {
|
|
118
|
+
errors.push(`height (${height}) must be even for video encoding`);
|
|
119
|
+
}
|
|
120
|
+
if (errors.length > 0) {
|
|
121
|
+
throw new Error(`Invalid dimensions: ${errors.join(', ')}. ` +
|
|
122
|
+
`Hardware encoders require even dimensions for YUV420 chroma subsampling. ` +
|
|
123
|
+
`Use ensureEvenDimensions() to auto-fix, or adjust your source dimensions.`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Reset the GPU detection cache
|
|
128
|
+
* Useful for testing or when GPU availability may have changed
|
|
129
|
+
*/
|
|
130
|
+
export function resetGpuCache() {
|
|
131
|
+
gpuAvailability = null;
|
|
132
|
+
gpuCheckPerformed = false;
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=gpu-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gpu-context.js","sourceRoot":"","sources":["../../src/canvas/gpu-context.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canvas Module - skia-canvas integration for WebCodecs
|
|
3
|
+
*
|
|
4
|
+
* Provides GPU-accelerated canvas operations using skia-canvas:
|
|
5
|
+
* - Metal acceleration on macOS
|
|
6
|
+
* - Vulkan acceleration on Linux/Windows
|
|
7
|
+
* - D3D acceleration on Windows
|
|
8
|
+
* - Automatic CPU fallback
|
|
9
|
+
*
|
|
10
|
+
* Best practices implemented:
|
|
11
|
+
* - Memory lifecycle with explicit frame closing
|
|
12
|
+
* - Canvas state reset before each frame
|
|
13
|
+
* - RGBA raw buffer export (never PNG)
|
|
14
|
+
* - Even dimensions for YUV420 compatibility
|
|
15
|
+
* - Backpressure with configurable queue size
|
|
16
|
+
*/
|
|
17
|
+
export { Canvas, loadImage, FontLibrary } from 'skia-canvas';
|
|
18
|
+
export { detectGpuAcceleration, isGpuAvailable, getGpuApi, createCanvas, ensureEvenDimensions, validateEvenDimensions, resetGpuCache, } from './gpu-context.js';
|
|
19
|
+
export { createPixelBuffer, createPixelBufferWithColor, getRawPixels, getRawPixelsAsync, resetCanvas, pixelsToImageData, drawPixelsToCanvas, bufferToUint8Array, resizePixels, } from './canvas-utils.js';
|
|
20
|
+
export { FrameLoop, createFrameLoop } from './frame-loop.js';
|
|
21
|
+
export type { GpuEngineInfo, CanvasConfig, FrameTiming, FrameLoopConfig, FrameLoopState, RawBufferOptions, FrameCallback, SkiaCanvas, SkiaEngineInfo, } from './types.js';
|
|
22
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/canvas/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG7D,OAAO,EACL,qBAAqB,EACrB,cAAc,EACd,SAAS,EACT,YAAY,EACZ,oBAAoB,EACpB,sBAAsB,EACtB,aAAa,GACd,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,iBAAiB,EACjB,0BAA0B,EAC1B,YAAY,EACZ,iBAAiB,EACjB,WAAW,EACX,iBAAiB,EACjB,kBAAkB,EAClB,kBAAkB,EAClB,YAAY,GACb,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAG7D,YAAY,EACV,aAAa,EACb,YAAY,EACZ,WAAW,EACX,eAAe,EACf,cAAc,EACd,gBAAgB,EAChB,aAAa,EACb,UAAU,EACV,cAAc,GACf,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canvas Module - skia-canvas integration for WebCodecs
|
|
3
|
+
*
|
|
4
|
+
* Provides GPU-accelerated canvas operations using skia-canvas:
|
|
5
|
+
* - Metal acceleration on macOS
|
|
6
|
+
* - Vulkan acceleration on Linux/Windows
|
|
7
|
+
* - D3D acceleration on Windows
|
|
8
|
+
* - Automatic CPU fallback
|
|
9
|
+
*
|
|
10
|
+
* Best practices implemented:
|
|
11
|
+
* - Memory lifecycle with explicit frame closing
|
|
12
|
+
* - Canvas state reset before each frame
|
|
13
|
+
* - RGBA raw buffer export (never PNG)
|
|
14
|
+
* - Even dimensions for YUV420 compatibility
|
|
15
|
+
* - Backpressure with configurable queue size
|
|
16
|
+
*/
|
|
17
|
+
// Re-export skia-canvas for direct use
|
|
18
|
+
export { Canvas, loadImage, FontLibrary } from 'skia-canvas';
|
|
19
|
+
// GPU context management
|
|
20
|
+
export { detectGpuAcceleration, isGpuAvailable, getGpuApi, createCanvas, ensureEvenDimensions, validateEvenDimensions, resetGpuCache, } from './gpu-context.js';
|
|
21
|
+
// Canvas utilities
|
|
22
|
+
export { createPixelBuffer, createPixelBufferWithColor, getRawPixels, getRawPixelsAsync, resetCanvas, pixelsToImageData, drawPixelsToCanvas, bufferToUint8Array, resizePixels, } from './canvas-utils.js';
|
|
23
|
+
// FrameLoop helper
|
|
24
|
+
export { FrameLoop, createFrameLoop } from './frame-loop.js';
|
|
25
|
+
//# sourceMappingURL=index.js.map
|