webcodecs-utils 0.2.4 → 0.2.6

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 (54) hide show
  1. package/README.md +142 -0
  2. package/dist/audio/extract-channels.d.ts +45 -0
  3. package/dist/audio/extract-channels.d.ts.map +1 -0
  4. package/dist/audio/extract-channels.js +25 -0
  5. package/dist/audio/get-sample-rate.d.ts +19 -0
  6. package/dist/audio/get-sample-rate.d.ts.map +1 -0
  7. package/dist/audio/get-sample-rate.js +14 -0
  8. package/dist/audio/mp3.d.ts +162 -0
  9. package/dist/audio/mp3.d.ts.map +1 -0
  10. package/dist/audio/mp3.js +173 -0
  11. package/dist/demux/example-muxer.d.ts +74 -0
  12. package/dist/demux/example-muxer.d.ts.map +1 -0
  13. package/dist/demux/example-muxer.js +40 -0
  14. package/dist/demux/get-chunks.d.ts +81 -0
  15. package/dist/demux/get-chunks.d.ts.map +1 -0
  16. package/dist/demux/get-chunks.js +88 -0
  17. package/dist/demux/mp4-demuxer.d.ts +84 -0
  18. package/dist/demux/mp4-demuxer.d.ts.map +1 -0
  19. package/dist/demux/mp4-demuxer.js +215 -0
  20. package/dist/demux/simple-demuxer.d.ts +49 -0
  21. package/dist/demux/simple-demuxer.d.ts.map +1 -0
  22. package/dist/demux/simple-demuxer.js +87 -0
  23. package/dist/in-memory-storage.d.ts +30 -0
  24. package/dist/in-memory-storage.d.ts.map +1 -0
  25. package/dist/in-memory-storage.js +54 -0
  26. package/dist/index.cjs +5 -298
  27. package/dist/index.d.ts +17 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +36 -13873
  30. package/dist/mux/simple-muxer.d.ts +47 -0
  31. package/dist/mux/simple-muxer.d.ts.map +1 -0
  32. package/dist/mux/simple-muxer.js +83 -0
  33. package/dist/polyfills/media-stream-track-processor.d.ts +5 -0
  34. package/dist/polyfills/media-stream-track-processor.d.ts.map +1 -0
  35. package/dist/polyfills/media-stream-track-processor.js +109 -0
  36. package/dist/streams/video-decode-stream.d.ts +11 -0
  37. package/dist/streams/video-decode-stream.d.ts.map +1 -0
  38. package/dist/streams/video-decode-stream.js +39 -0
  39. package/dist/streams/video-encode-stream.d.ts +15 -0
  40. package/dist/streams/video-encode-stream.d.ts.map +1 -0
  41. package/dist/streams/video-encode-stream.js +40 -0
  42. package/dist/streams/video-process-stream.d.ts +22 -0
  43. package/dist/streams/video-process-stream.d.ts.map +1 -0
  44. package/dist/streams/video-process-stream.js +21 -0
  45. package/dist/video/get-bitrate.d.ts +35 -0
  46. package/dist/video/get-bitrate.d.ts.map +1 -0
  47. package/dist/video/get-bitrate.js +12 -0
  48. package/dist/video/get-codec-string.d.ts +46 -0
  49. package/dist/video/get-codec-string.d.ts.map +1 -0
  50. package/dist/video/get-codec-string.js +195 -0
  51. package/dist/video/gpu-renderer.d.ts +108 -0
  52. package/dist/video/gpu-renderer.d.ts.map +1 -0
  53. package/dist/video/gpu-renderer.js +266 -0
  54. package/package.json +1 -1
@@ -0,0 +1,195 @@
1
+ function s(a, m, c, o) {
2
+ const S = [
3
+ { maxMacroblocks: 99, maxBitrate: 64e3, level: 10 },
4
+ // Level 1
5
+ { maxMacroblocks: 396, maxBitrate: 192e3, level: 11 },
6
+ // Level 1.1
7
+ { maxMacroblocks: 396, maxBitrate: 384e3, level: 12 },
8
+ // Level 1.2
9
+ { maxMacroblocks: 396, maxBitrate: 768e3, level: 13 },
10
+ // Level 1.3
11
+ { maxMacroblocks: 396, maxBitrate: 2e6, level: 20 },
12
+ // Level 2
13
+ { maxMacroblocks: 792, maxBitrate: 4e6, level: 21 },
14
+ // Level 2.1
15
+ { maxMacroblocks: 1620, maxBitrate: 4e6, level: 22 },
16
+ // Level 2.2
17
+ { maxMacroblocks: 1620, maxBitrate: 1e7, level: 30 },
18
+ // Level 3
19
+ { maxMacroblocks: 3600, maxBitrate: 14e6, level: 31 },
20
+ // Level 3.1
21
+ { maxMacroblocks: 5120, maxBitrate: 2e7, level: 32 },
22
+ // Level 3.2
23
+ { maxMacroblocks: 8192, maxBitrate: 2e7, level: 40 },
24
+ // Level 4
25
+ { maxMacroblocks: 8192, maxBitrate: 5e7, level: 41 },
26
+ // Level 4.1
27
+ { maxMacroblocks: 8704, maxBitrate: 5e7, level: 42 },
28
+ // Level 4.2
29
+ { maxMacroblocks: 22080, maxBitrate: 135e6, level: 50 },
30
+ // Level 5
31
+ { maxMacroblocks: 36864, maxBitrate: 24e7, level: 51 },
32
+ // Level 5.1
33
+ { maxMacroblocks: 36864, maxBitrate: 24e7, level: 52 },
34
+ // Level 5.2
35
+ { maxMacroblocks: 139264, maxBitrate: 24e7, level: 60 },
36
+ // Level 6
37
+ { maxMacroblocks: 139264, maxBitrate: 48e7, level: 61 },
38
+ // Level 6.1
39
+ { maxMacroblocks: 139264, maxBitrate: 8e8, level: 62 }
40
+ // Level 6.2
41
+ ], u = [
42
+ { maxPictureSize: 36864, maxBitrate: 128e3, tier: "L", level: 30 },
43
+ // Level 1 (Low Tier)
44
+ { maxPictureSize: 122880, maxBitrate: 15e5, tier: "L", level: 60 },
45
+ // Level 2 (Low Tier)
46
+ { maxPictureSize: 245760, maxBitrate: 3e6, tier: "L", level: 63 },
47
+ // Level 2.1 (Low Tier)
48
+ { maxPictureSize: 552960, maxBitrate: 6e6, tier: "L", level: 90 },
49
+ // Level 3 (Low Tier)
50
+ { maxPictureSize: 983040, maxBitrate: 1e7, tier: "L", level: 93 },
51
+ // Level 3.1 (Low Tier)
52
+ { maxPictureSize: 2228224, maxBitrate: 12e6, tier: "L", level: 120 },
53
+ // Level 4 (Low Tier)
54
+ { maxPictureSize: 2228224, maxBitrate: 3e7, tier: "H", level: 120 },
55
+ // Level 4 (High Tier)
56
+ { maxPictureSize: 2228224, maxBitrate: 2e7, tier: "L", level: 123 },
57
+ // Level 4.1 (Low Tier)
58
+ { maxPictureSize: 2228224, maxBitrate: 5e7, tier: "H", level: 123 },
59
+ // Level 4.1 (High Tier)
60
+ { maxPictureSize: 8912896, maxBitrate: 25e6, tier: "L", level: 150 },
61
+ // Level 5 (Low Tier)
62
+ { maxPictureSize: 8912896, maxBitrate: 1e8, tier: "H", level: 150 },
63
+ // Level 5 (High Tier)
64
+ { maxPictureSize: 8912896, maxBitrate: 4e7, tier: "L", level: 153 },
65
+ // Level 5.1 (Low Tier)
66
+ { maxPictureSize: 8912896, maxBitrate: 16e7, tier: "H", level: 153 },
67
+ // Level 5.1 (High Tier)
68
+ { maxPictureSize: 8912896, maxBitrate: 6e7, tier: "L", level: 156 },
69
+ // Level 5.2 (Low Tier)
70
+ { maxPictureSize: 8912896, maxBitrate: 24e7, tier: "H", level: 156 },
71
+ // Level 5.2 (High Tier)
72
+ { maxPictureSize: 35651584, maxBitrate: 6e7, tier: "L", level: 180 },
73
+ // Level 6 (Low Tier)
74
+ { maxPictureSize: 35651584, maxBitrate: 24e7, tier: "H", level: 180 },
75
+ // Level 6 (High Tier)
76
+ { maxPictureSize: 35651584, maxBitrate: 12e7, tier: "L", level: 183 },
77
+ // Level 6.1 (Low Tier)
78
+ { maxPictureSize: 35651584, maxBitrate: 48e7, tier: "H", level: 183 },
79
+ // Level 6.1 (High Tier)
80
+ { maxPictureSize: 35651584, maxBitrate: 24e7, tier: "L", level: 186 },
81
+ // Level 6.2 (Low Tier)
82
+ { maxPictureSize: 35651584, maxBitrate: 8e8, tier: "H", level: 186 }
83
+ // Level 6.2 (High Tier)
84
+ ], n = [
85
+ { maxPictureSize: 36864, maxBitrate: 2e5, level: 10 },
86
+ // Level 1
87
+ { maxPictureSize: 73728, maxBitrate: 8e5, level: 11 },
88
+ // Level 1.1
89
+ { maxPictureSize: 122880, maxBitrate: 18e5, level: 20 },
90
+ // Level 2
91
+ { maxPictureSize: 245760, maxBitrate: 36e5, level: 21 },
92
+ // Level 2.1
93
+ { maxPictureSize: 552960, maxBitrate: 72e5, level: 30 },
94
+ // Level 3
95
+ { maxPictureSize: 983040, maxBitrate: 12e6, level: 31 },
96
+ // Level 3.1
97
+ { maxPictureSize: 2228224, maxBitrate: 18e6, level: 40 },
98
+ // Level 4
99
+ { maxPictureSize: 2228224, maxBitrate: 3e7, level: 41 },
100
+ // Level 4.1
101
+ { maxPictureSize: 8912896, maxBitrate: 6e7, level: 50 },
102
+ // Level 5
103
+ { maxPictureSize: 8912896, maxBitrate: 12e7, level: 51 },
104
+ // Level 5.1
105
+ { maxPictureSize: 8912896, maxBitrate: 18e7, level: 52 },
106
+ // Level 5.2
107
+ { maxPictureSize: 35651584, maxBitrate: 18e7, level: 60 },
108
+ // Level 6
109
+ { maxPictureSize: 35651584, maxBitrate: 24e7, level: 61 },
110
+ // Level 6.1
111
+ { maxPictureSize: 35651584, maxBitrate: 48e7, level: 62 }
112
+ // Level 6.2
113
+ ], P = [
114
+ { maxPictureSize: 147456, maxBitrate: 15e5, tier: "M", level: 0 },
115
+ // Level 2.0 (Main Tier)
116
+ { maxPictureSize: 278784, maxBitrate: 3e6, tier: "M", level: 1 },
117
+ // Level 2.1 (Main Tier)
118
+ { maxPictureSize: 665856, maxBitrate: 6e6, tier: "M", level: 4 },
119
+ // Level 3.0 (Main Tier)
120
+ { maxPictureSize: 1065024, maxBitrate: 1e7, tier: "M", level: 5 },
121
+ // Level 3.1 (Main Tier)
122
+ { maxPictureSize: 2359296, maxBitrate: 12e6, tier: "M", level: 8 },
123
+ // Level 4.0 (Main Tier)
124
+ { maxPictureSize: 2359296, maxBitrate: 3e7, tier: "H", level: 8 },
125
+ // Level 4.0 (High Tier)
126
+ { maxPictureSize: 2359296, maxBitrate: 2e7, tier: "M", level: 9 },
127
+ // Level 4.1 (Main Tier)
128
+ { maxPictureSize: 2359296, maxBitrate: 5e7, tier: "H", level: 9 },
129
+ // Level 4.1 (High Tier)
130
+ { maxPictureSize: 8912896, maxBitrate: 3e7, tier: "M", level: 12 },
131
+ // Level 5.0 (Main Tier)
132
+ { maxPictureSize: 8912896, maxBitrate: 1e8, tier: "H", level: 12 },
133
+ // Level 5.0 (High Tier)
134
+ { maxPictureSize: 8912896, maxBitrate: 4e7, tier: "M", level: 13 },
135
+ // Level 5.1 (Main Tier)
136
+ { maxPictureSize: 8912896, maxBitrate: 16e7, tier: "H", level: 13 },
137
+ // Level 5.1 (High Tier)
138
+ { maxPictureSize: 8912896, maxBitrate: 6e7, tier: "M", level: 14 },
139
+ // Level 5.2 (Main Tier)
140
+ { maxPictureSize: 8912896, maxBitrate: 24e7, tier: "H", level: 14 },
141
+ // Level 5.2 (High Tier)
142
+ { maxPictureSize: 35651584, maxBitrate: 6e7, tier: "M", level: 15 },
143
+ // Level 5.3 (Main Tier)
144
+ { maxPictureSize: 35651584, maxBitrate: 24e7, tier: "H", level: 15 },
145
+ // Level 5.3 (High Tier)
146
+ { maxPictureSize: 35651584, maxBitrate: 6e7, tier: "M", level: 16 },
147
+ // Level 6.0 (Main Tier)
148
+ { maxPictureSize: 35651584, maxBitrate: 24e7, tier: "H", level: 16 },
149
+ // Level 6.0 (High Tier)
150
+ { maxPictureSize: 35651584, maxBitrate: 1e8, tier: "M", level: 17 },
151
+ // Level 6.1 (Main Tier)
152
+ { maxPictureSize: 35651584, maxBitrate: 48e7, tier: "H", level: 17 },
153
+ // Level 6.1 (High Tier)
154
+ { maxPictureSize: 35651584, maxBitrate: 16e7, tier: "M", level: 18 },
155
+ // Level 6.2 (Main Tier)
156
+ { maxPictureSize: 35651584, maxBitrate: 8e8, tier: "H", level: 18 },
157
+ // Level 6.2 (High Tier)
158
+ { maxPictureSize: 35651584, maxBitrate: 16e7, tier: "M", level: 19 },
159
+ // Level 6.3 (Main Tier)
160
+ { maxPictureSize: 35651584, maxBitrate: 8e8, tier: "H", level: 19 }
161
+ // Level 6.3 (High Tier)
162
+ ];
163
+ function v(e) {
164
+ return e ? e[e.length - 1] : void 0;
165
+ }
166
+ if (a === "avc") {
167
+ const r = Math.ceil(m / 16) * Math.ceil(c / 16), t = S.find(
168
+ (z) => r <= z.maxMacroblocks && o <= z.maxBitrate
169
+ ) ?? v(S), l = t ? t.level : 0, i = "64".padStart(2, "0"), x = "00", B = l.toString(16).padStart(2, "0");
170
+ return `avc1.${i}${x}${B}`;
171
+ } else if (a === "hevc") {
172
+ const e = "", t = "6", l = m * c, i = u.find(
173
+ (B) => l <= B.maxPictureSize && o <= B.maxBitrate
174
+ ) ?? v(u);
175
+ return `hev1.${e}1.${t}.${i.tier}${i.level}.B0`;
176
+ } else {
177
+ if (a === "vp8")
178
+ return "vp8";
179
+ if (a === "vp9") {
180
+ const e = "00", r = m * c, t = n.find(
181
+ (i) => r <= i.maxPictureSize && o <= i.maxBitrate
182
+ ) ?? v(n);
183
+ return `vp09.${e}.${t.level.toString().padStart(2, "0")}.08`;
184
+ } else if (a === "av1") {
185
+ const r = m * c, t = P.find(
186
+ (x) => r <= x.maxPictureSize && o <= x.maxBitrate
187
+ ) ?? v(P);
188
+ return `av01.0.${t.level.toString().padStart(2, "0")}${t.tier}.08`;
189
+ }
190
+ }
191
+ throw new TypeError(`Unhandled codec '${a}'.`);
192
+ }
193
+ export {
194
+ s as getCodecString
195
+ };
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Zero-copy VideoFrame rendering using WebGPU with ImageBitmap fallback.
3
+ *
4
+ * This class provides high-performance video rendering by using WebGPU's
5
+ * `importExternalTexture()` for zero-copy rendering when available, or falling
6
+ * back to ImageBitmapRenderer for compatibility.
7
+ *
8
+ * Features:
9
+ * - **Zero-copy rendering** via WebGPU (no pixel copying between GPU and CPU)
10
+ * - **Two filter modes**: Linear (hardware accelerated) and Bicubic (high quality)
11
+ * - **Automatic fallback** to ImageBitmapRenderer if WebGPU is unavailable
12
+ * - **Works with VideoFrame** objects from VideoDecoder
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * const canvas = document.getElementById('canvas');
17
+ * const renderer = new GPUFrameRenderer(canvas, { filterMode: 'linear' });
18
+ * await renderer.init();
19
+ *
20
+ * // In VideoDecoder output callback:
21
+ * decoder.configure({
22
+ * output: (frame) => {
23
+ * renderer.drawImage(frame);
24
+ * frame.close();
25
+ * },
26
+ * error: (e) => console.error(e)
27
+ * });
28
+ * ```
29
+ *
30
+ * @example
31
+ * ```typescript
32
+ * // Switch to high-quality bicubic filtering
33
+ * renderer.setFilterMode('bicubic');
34
+ *
35
+ * // Check current mode
36
+ * if (renderer.getMode() === 'webgpu') {
37
+ * console.log('Using WebGPU zero-copy rendering');
38
+ * }
39
+ * ```
40
+ */
41
+ export declare class GPUFrameRenderer {
42
+ canvas: HTMLCanvasElement | OffscreenCanvas;
43
+ mode: 'webgpu' | 'bitmap' | null;
44
+ filterMode: 'linear' | 'bicubic';
45
+ device: GPUDevice | null;
46
+ context: GPUCanvasContext | null;
47
+ linearPipeline: GPURenderPipeline | null;
48
+ bicubicPipeline: GPURenderPipeline | null;
49
+ sampler: GPUSampler | null;
50
+ uniformBuffer: GPUBuffer | null;
51
+ bitmapCtx: ImageBitmapRenderingContext | null;
52
+ /**
53
+ * Create a new GPUFrameRenderer.
54
+ *
55
+ * @param canvas - The canvas element to render to (HTMLCanvasElement or OffscreenCanvas)
56
+ * @param options - Configuration options
57
+ * @param options.filterMode - Scaling filter: 'linear' (default) or 'bicubic' (higher quality)
58
+ */
59
+ constructor(canvas: HTMLCanvasElement | OffscreenCanvas, options?: {
60
+ filterMode?: 'linear' | 'bicubic';
61
+ });
62
+ /**
63
+ * Initialize the renderer. Must be called before drawImage().
64
+ *
65
+ * Attempts to initialize WebGPU first for zero-copy rendering. If WebGPU
66
+ * is unavailable or initialization fails, automatically falls back to
67
+ * ImageBitmapRenderer.
68
+ *
69
+ * @returns Promise that resolves when initialization is complete
70
+ *
71
+ * @example
72
+ * ```typescript
73
+ * const renderer = new GPUFrameRenderer(canvas);
74
+ * await renderer.init();
75
+ * // Ready to render
76
+ * ```
77
+ */
78
+ init(): Promise<void>;
79
+ initWebGPU(): Promise<boolean>;
80
+ initBitmapRenderer(): void;
81
+ /**
82
+ * Draw a VideoFrame to the canvas
83
+ * @param {VideoFrame} source - The VideoFrame to draw
84
+ */
85
+ drawImage(source: VideoFrame): void;
86
+ drawImageWebGPU(videoFrame: VideoFrame): void;
87
+ drawImageBitmap(videoFrame: VideoFrame): Promise<void>;
88
+ /**
89
+ * Get the current rendering mode
90
+ * @returns {'webgpu'|'bitmap'|null}
91
+ */
92
+ getMode(): "webgpu" | "bitmap" | null;
93
+ /**
94
+ * Get the current filter mode
95
+ * @returns {'linear'|'bicubic'}
96
+ */
97
+ getFilterMode(): "linear" | "bicubic";
98
+ /**
99
+ * Set the filter mode (only applies to WebGPU mode)
100
+ * @param {'linear'|'bicubic'} mode
101
+ */
102
+ setFilterMode(mode: 'linear' | 'bicubic'): void;
103
+ /**
104
+ * Clean up resources
105
+ */
106
+ destroy(): void;
107
+ }
108
+ //# sourceMappingURL=gpu-renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gpu-renderer.d.ts","sourceRoot":"","sources":["../../src/video/gpu-renderer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,qBAAa,gBAAgB;IAEzB,MAAM,EAAE,iBAAiB,GAAG,eAAe,CAAC;IAC5C,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,IAAI,CAAC;IACjC,UAAU,EAAE,QAAQ,GAAG,SAAS,CAAC;IACjC,MAAM,EAAE,SAAS,GAAG,IAAI,CAAC;IACzB,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACjC,cAAc,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACzC,eAAe,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAC1C,OAAO,EAAE,UAAU,GAAG,IAAI,CAAC;IAC3B,aAAa,EAAE,SAAS,GAAG,IAAI,CAAC;IAChC,SAAS,EAAE,2BAA2B,GAAG,IAAI,CAAC;IAE9C;;;;;;OAMG;gBACS,MAAM,EAAE,iBAAiB,GAAG,eAAe,EAAE,OAAO,GAAE;QAAE,UAAU,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAA;KAAO;IAiB5G;;;;;;;;;;;;;;;OAeG;IACG,IAAI;IA8BJ,UAAU,IAAI,OAAO,CAAC,OAAO,CAAC;IAmKpC,kBAAkB;IAIlB;;;OAGG;IACH,SAAS,CAAC,MAAM,EAAE,UAAU;IAU5B,eAAe,CAAC,UAAU,EAAE,UAAU;IA2DhC,eAAe,CAAC,UAAU,EAAE,UAAU;IAM5C;;;OAGG;IACH,OAAO;IAIP;;;OAGG;IACH,aAAa;IAIb;;;OAGG;IACH,aAAa,CAAC,IAAI,EAAE,QAAQ,GAAG,SAAS;IAOxC;;OAEG;IACH,OAAO;CAKR"}
@@ -0,0 +1,266 @@
1
+ class l {
2
+ /**
3
+ * Create a new GPUFrameRenderer.
4
+ *
5
+ * @param canvas - The canvas element to render to (HTMLCanvasElement or OffscreenCanvas)
6
+ * @param options - Configuration options
7
+ * @param options.filterMode - Scaling filter: 'linear' (default) or 'bicubic' (higher quality)
8
+ */
9
+ constructor(e, t = {}) {
10
+ this.canvas = e, this.mode = "webgpu", this.filterMode = t.filterMode || "linear", this.device = null, this.context = null, this.linearPipeline = null, this.bicubicPipeline = null, this.sampler = null, this.uniformBuffer = null, this.bitmapCtx = null;
11
+ }
12
+ /**
13
+ * Initialize the renderer. Must be called before drawImage().
14
+ *
15
+ * Attempts to initialize WebGPU first for zero-copy rendering. If WebGPU
16
+ * is unavailable or initialization fails, automatically falls back to
17
+ * ImageBitmapRenderer.
18
+ *
19
+ * @returns Promise that resolves when initialization is complete
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * const renderer = new GPUFrameRenderer(canvas);
24
+ * await renderer.init();
25
+ * // Ready to render
26
+ * ```
27
+ */
28
+ async init() {
29
+ const e = navigator.platform.toLowerCase().includes("linux"), t = navigator.userAgent.toLowerCase().includes("firefox");
30
+ if (e && t) {
31
+ console.log("GPUDrawImage: Linux + Firefox detected, using ImageBitmapRenderer"), this.initBitmapRenderer(), this.mode = "bitmap";
32
+ return;
33
+ }
34
+ if (navigator.gpu)
35
+ try {
36
+ await this.initWebGPU(), this.mode = "webgpu", console.log("GPUDrawImage: Using WebGPU (zero-copy)");
37
+ return;
38
+ } catch (i) {
39
+ console.warn("GPUDrawImage: WebGPU initialization failed, falling back to ImageBitmap", i);
40
+ }
41
+ this.initBitmapRenderer(), this.mode = "bitmap", console.log("GPUDrawImage: Using ImageBitmapRenderer (fallback)");
42
+ }
43
+ async initWebGPU() {
44
+ const e = await navigator.gpu.requestAdapter();
45
+ if (!e || (this.device = await e.requestDevice(), !this.device) || (this.context = this.canvas.getContext("webgpu"), !this.context)) return !1;
46
+ const t = navigator.gpu.getPreferredCanvasFormat();
47
+ this.context.configure({
48
+ device: this.device,
49
+ format: t,
50
+ alphaMode: "opaque"
51
+ }), this.sampler = this.device.createSampler({
52
+ magFilter: "linear",
53
+ minFilter: "linear"
54
+ }), this.uniformBuffer = this.device.createBuffer({
55
+ size: 8,
56
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
57
+ });
58
+ const i = `
59
+ struct VertexOutput {
60
+ @builtin(position) position: vec4f,
61
+ @location(0) texCoord: vec2f,
62
+ }
63
+
64
+ @vertex
65
+ fn vertexMain(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {
66
+ var pos = array<vec2f, 6>(
67
+ vec2f(-1.0, -1.0),
68
+ vec2f(1.0, -1.0),
69
+ vec2f(-1.0, 1.0),
70
+ vec2f(-1.0, 1.0),
71
+ vec2f(1.0, -1.0),
72
+ vec2f(1.0, 1.0)
73
+ );
74
+
75
+ var texCoord = array<vec2f, 6>(
76
+ vec2f(0.0, 1.0),
77
+ vec2f(1.0, 1.0),
78
+ vec2f(0.0, 0.0),
79
+ vec2f(0.0, 0.0),
80
+ vec2f(1.0, 1.0),
81
+ vec2f(1.0, 0.0)
82
+ );
83
+
84
+ var output: VertexOutput;
85
+ output.position = vec4f(pos[vertexIndex], 0.0, 1.0);
86
+ output.texCoord = texCoord[vertexIndex];
87
+ return output;
88
+ }
89
+ `, r = this.device.createShaderModule({
90
+ code: i + `
91
+ @group(0) @binding(0) var videoTexture: texture_external;
92
+ @group(0) @binding(1) var texSampler: sampler;
93
+
94
+ @fragment
95
+ fn fragmentMain(input: VertexOutput) -> @location(0) vec4f {
96
+ return textureSampleBaseClampToEdge(videoTexture, texSampler, input.texCoord);
97
+ }
98
+ `
99
+ }), a = this.device.createShaderModule({
100
+ code: i + `
101
+ @group(0) @binding(0) var videoTexture: texture_external;
102
+ @group(0) @binding(1) var<uniform> texSize: vec2f;
103
+
104
+ // Bicubic weight function (Catmull-Rom)
105
+ fn cubic(x: f32) -> f32 {
106
+ let x_abs = abs(x);
107
+ if (x_abs <= 1.0) {
108
+ return 1.5 * x_abs * x_abs * x_abs - 2.5 * x_abs * x_abs + 1.0;
109
+ } else if (x_abs < 2.0) {
110
+ return -0.5 * x_abs * x_abs * x_abs + 2.5 * x_abs * x_abs - 4.0 * x_abs + 2.0;
111
+ }
112
+ return 0.0;
113
+ }
114
+
115
+ @fragment
116
+ fn fragmentMain(input: VertexOutput) -> @location(0) vec4f {
117
+ let texCoord = input.texCoord;
118
+
119
+ let coord = texCoord * texSize;
120
+ let coordFloor = floor(coord);
121
+ let f = coord - coordFloor;
122
+
123
+ var result = vec4f(0.0, 0.0, 0.0, 0.0);
124
+ var weightSum = 0.0;
125
+
126
+ // Read exact pixel values from 4x4 neighborhood using textureLoad
127
+ for (var y = -1; y <= 2; y++) {
128
+ for (var x = -1; x <= 2; x++) {
129
+ let pixelCoord = vec2i(i32(coordFloor.x) + x, i32(coordFloor.y) + y);
130
+
131
+ // Clamp to valid texture coordinates
132
+ let clampedCoord = clamp(pixelCoord, vec2i(0, 0), vec2i(i32(texSize.x) - 1, i32(texSize.y) - 1));
133
+
134
+ let weight = cubic(f.x - f32(x)) * cubic(f.y - f32(y));
135
+ result += textureLoad(videoTexture, clampedCoord) * weight;
136
+ weightSum += weight;
137
+ }
138
+ }
139
+
140
+ return result / weightSum;
141
+ }
142
+ `
143
+ });
144
+ return this.linearPipeline = this.device.createRenderPipeline({
145
+ layout: "auto",
146
+ vertex: {
147
+ module: r,
148
+ entryPoint: "vertexMain"
149
+ },
150
+ fragment: {
151
+ module: r,
152
+ entryPoint: "fragmentMain",
153
+ targets: [{
154
+ format: t
155
+ }]
156
+ },
157
+ primitive: {
158
+ topology: "triangle-list"
159
+ }
160
+ }), this.bicubicPipeline = this.device.createRenderPipeline({
161
+ layout: "auto",
162
+ vertex: {
163
+ module: a,
164
+ entryPoint: "vertexMain"
165
+ },
166
+ fragment: {
167
+ module: a,
168
+ entryPoint: "fragmentMain",
169
+ targets: [{
170
+ format: t
171
+ }]
172
+ },
173
+ primitive: {
174
+ topology: "triangle-list"
175
+ }
176
+ }), !0;
177
+ }
178
+ initBitmapRenderer() {
179
+ this.bitmapCtx = this.canvas.getContext("bitmaprenderer");
180
+ }
181
+ /**
182
+ * Draw a VideoFrame to the canvas
183
+ * @param {VideoFrame} source - The VideoFrame to draw
184
+ */
185
+ drawImage(e) {
186
+ if (this.mode === "webgpu")
187
+ this.drawImageWebGPU(e);
188
+ else if (this.mode === "bitmap")
189
+ this.drawImageBitmap(e);
190
+ else
191
+ throw new Error("GPUDrawImage not initialized. Call init() first.");
192
+ }
193
+ drawImageWebGPU(e) {
194
+ const t = this.filterMode === "bicubic" ? this.bicubicPipeline : this.linearPipeline, i = this.filterMode === "bicubic", r = [
195
+ {
196
+ binding: 0,
197
+ resource: this.device.importExternalTexture({
198
+ source: e
199
+ })
200
+ }
201
+ ];
202
+ if (i) {
203
+ const u = new Float32Array([e.displayWidth, e.displayHeight]);
204
+ this.device.queue.writeBuffer(this.uniformBuffer, 0, u), r.push({
205
+ binding: 1,
206
+ resource: {
207
+ //@ts-expect-error
208
+ buffer: this.uniformBuffer
209
+ }
210
+ });
211
+ } else
212
+ r.push({
213
+ binding: 1,
214
+ //@ts-expect-error
215
+ resource: this.sampler
216
+ });
217
+ const a = this.device.createBindGroup({
218
+ layout: t.getBindGroupLayout(0),
219
+ entries: r
220
+ }), n = this.device.createCommandEncoder(), s = this.context.getCurrentTexture().createView(), o = n.beginRenderPass({
221
+ colorAttachments: [{
222
+ view: s,
223
+ clearValue: { r: 0, g: 0, b: 0, a: 1 },
224
+ loadOp: "clear",
225
+ storeOp: "store"
226
+ }]
227
+ });
228
+ o.setPipeline(t), o.setBindGroup(0, a), o.draw(6), o.end(), this.device.queue.submit([n.finish()]);
229
+ }
230
+ async drawImageBitmap(e) {
231
+ const t = await createImageBitmap(e);
232
+ this.bitmapCtx.transferFromImageBitmap(t);
233
+ }
234
+ /**
235
+ * Get the current rendering mode
236
+ * @returns {'webgpu'|'bitmap'|null}
237
+ */
238
+ getMode() {
239
+ return this.mode;
240
+ }
241
+ /**
242
+ * Get the current filter mode
243
+ * @returns {'linear'|'bicubic'}
244
+ */
245
+ getFilterMode() {
246
+ return this.filterMode;
247
+ }
248
+ /**
249
+ * Set the filter mode (only applies to WebGPU mode)
250
+ * @param {'linear'|'bicubic'} mode
251
+ */
252
+ setFilterMode(e) {
253
+ if (e !== "linear" && e !== "bicubic")
254
+ throw new Error('Filter mode must be "linear" or "bicubic"');
255
+ this.filterMode = e;
256
+ }
257
+ /**
258
+ * Clean up resources
259
+ */
260
+ destroy() {
261
+ this.device && this.device.destroy();
262
+ }
263
+ }
264
+ export {
265
+ l as GPUFrameRenderer
266
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "webcodecs-utils",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "description": "Utility functions for working with WebCodecs API",
5
5
  "homepage": "https://webcodecsfundamentals.org",
6
6
  "repository": {