remotion 4.0.456 → 4.0.457

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 (34) hide show
  1. package/dist/cjs/HtmlInCanvas.d.ts +1 -1
  2. package/dist/cjs/HtmlInCanvas.js +7 -2
  3. package/dist/cjs/audio/shared-audio-tags.js +14 -4
  4. package/dist/cjs/canvas-effects/HtmlInCanvas.d.ts +12 -0
  5. package/dist/cjs/canvas-effects/HtmlInCanvas.js +188 -0
  6. package/dist/cjs/canvas-effects/Solid.d.ts +12 -0
  7. package/dist/cjs/canvas-effects/Solid.js +93 -0
  8. package/dist/cjs/canvas-effects/canvas-pool.d.ts +13 -0
  9. package/dist/cjs/canvas-effects/canvas-pool.js +85 -0
  10. package/dist/cjs/canvas-effects/define-effect.d.ts +3 -0
  11. package/dist/cjs/canvas-effects/define-effect.js +28 -0
  12. package/dist/cjs/canvas-effects/effect-internals.d.ts +7 -0
  13. package/dist/cjs/canvas-effects/effect-internals.js +42 -0
  14. package/dist/cjs/canvas-effects/effect-types.d.ts +31 -0
  15. package/dist/cjs/canvas-effects/effect-types.js +12 -0
  16. package/dist/cjs/canvas-effects/gpu-device.d.ts +2 -0
  17. package/dist/cjs/canvas-effects/gpu-device.js +36 -0
  18. package/dist/cjs/canvas-effects/index.d.ts +1 -0
  19. package/dist/cjs/canvas-effects/index.js +2 -0
  20. package/dist/cjs/canvas-effects/run-effect-chain.d.ts +23 -0
  21. package/dist/cjs/canvas-effects/run-effect-chain.js +119 -0
  22. package/dist/cjs/canvas-effects/use-effect-chain-state.d.ts +3 -0
  23. package/dist/cjs/canvas-effects/use-effect-chain-state.js +38 -0
  24. package/dist/cjs/canvas-effects/use-effect-chain.d.ts +13 -0
  25. package/dist/cjs/canvas-effects/use-effect-chain.js +200 -0
  26. package/dist/cjs/canvas-effects/use-memoized-effects.d.ts +2 -0
  27. package/dist/cjs/canvas-effects/use-memoized-effects.js +21 -0
  28. package/dist/cjs/enable-effects.d.ts +1 -0
  29. package/dist/cjs/enable-effects.js +4 -0
  30. package/dist/cjs/version.d.ts +1 -1
  31. package/dist/cjs/version.js +1 -1
  32. package/dist/esm/index.mjs +106 -89
  33. package/dist/esm/version.mjs +1 -1
  34. package/package.json +2 -2
@@ -55,5 +55,5 @@ export type HtmlInCanvasProps = Omit<SequenceProps, 'children' | 'durationInFram
55
55
  readonly onInit?: HtmlInCanvasOnInit;
56
56
  };
57
57
  export declare const HtmlInCanvas: React.ForwardRefExoticComponent<HtmlInCanvasProps & React.RefAttributes<HTMLCanvasElement>> & {
58
- readonly isHtmlInCanvasSupported: typeof isHtmlInCanvasSupported;
58
+ readonly isSupported: typeof isHtmlInCanvasSupported;
59
59
  };
@@ -53,6 +53,7 @@ const defaultOnPaint = ({ canvas, element, elementImage, }) => {
53
53
  element.style.transform = transform.toString();
54
54
  };
55
55
  /* eslint-enable react/require-default-props */
56
+ const HtmlInCanvasAncestorContext = (0, react_1.createContext)(false);
56
57
  const htmlInCanvasSchema = {
57
58
  'style.translate': {
58
59
  type: 'translate',
@@ -84,6 +85,7 @@ const htmlInCanvasSchema = {
84
85
  },
85
86
  };
86
87
  const HtmlInCanvasInner = (0, react_1.forwardRef)(({ width, height, _experimentalEffects: experimentalEffects = [], children, onPaint, onInit, _experimentalControls: controls, style, durationInFrames, ...sequenceProps }, ref) => {
88
+ const isInsideAncestorHtmlInCanvas = (0, react_1.useContext)(HtmlInCanvasAncestorContext);
87
89
  assertHtmlInCanvasDimensions(width, height);
88
90
  const { continueRender, cancelRender } = (0, use_delay_render_js_1.useDelayRender)();
89
91
  if (!(0, exports.isHtmlInCanvasSupported)()) {
@@ -246,12 +248,15 @@ const HtmlInCanvasInner = (0, react_1.forwardRef)(({ width, height, _experimenta
246
248
  ...style,
247
249
  };
248
250
  }, [width, height, style]);
249
- return ((0, jsx_runtime_1.jsx)(Sequence_js_1.Sequence, { durationInFrames: resolvedDuration, name: "<HtmlInCanvas>", _experimentalControls: controls, layout: "none", ...sequenceProps, children: (0, jsx_runtime_1.jsx)("canvas", { ref: setLayoutCanvasRef, width: width, height: height, children: (0, jsx_runtime_1.jsx)("div", { ref: divRef, style: innerStyle, children: children }) }) }));
251
+ if (isInsideAncestorHtmlInCanvas) {
252
+ throw new Error('<HtmlInCanvas> effects cannot be nested together. Chrome will only display the outer effect. Consider merging the effects into one if you can.');
253
+ }
254
+ return ((0, jsx_runtime_1.jsx)(Sequence_js_1.Sequence, { durationInFrames: resolvedDuration, name: "<HtmlInCanvas>", _experimentalControls: controls, layout: "none", ...sequenceProps, children: (0, jsx_runtime_1.jsx)(HtmlInCanvasAncestorContext.Provider, { value: true, children: (0, jsx_runtime_1.jsx)("canvas", { ref: setLayoutCanvasRef, width: width, height: height, children: (0, jsx_runtime_1.jsx)("div", { ref: divRef, style: innerStyle, children: children }) }) }) }));
250
255
  });
251
256
  HtmlInCanvasInner.displayName = 'HtmlInCanvas';
252
257
  const HtmlInCanvasWrapped = (0, wrap_in_schema_js_1.wrapInSchema)(HtmlInCanvasInner, htmlInCanvasSchema);
253
258
  exports.HtmlInCanvas = Object.assign(HtmlInCanvasWrapped, {
254
- isHtmlInCanvasSupported: exports.isHtmlInCanvasSupported,
259
+ isSupported: exports.isHtmlInCanvasSupported,
255
260
  });
256
261
  exports.HtmlInCanvas.displayName = 'HtmlInCanvas';
257
262
  (0, enable_sequence_stack_traces_js_1.addSequenceStackTraces)(exports.HtmlInCanvas);
@@ -169,17 +169,27 @@ const SharedAudioContextProvider = ({ children, audioLatencyHint, audioEnabled }
169
169
  return Promise.resolve();
170
170
  }
171
171
  audioContextIsPlayingEventually.current = true;
172
- isResuming.current = (0, wait_until_actually_resumed_js_1.waitUntilActuallyResumed)(ctxAndGain.audioContext, logLevel)
173
- .then(() => { })
174
- .finally(() => {
172
+ const resumePromise = ctxAndGain.audioContext.resume();
173
+ isResuming.current = new Promise((resolve) => {
174
+ (0, wait_until_actually_resumed_js_1.waitUntilActuallyResumed)(ctxAndGain.audioContext, logLevel).then(resolve);
175
+ resumePromise.catch((err) => {
176
+ log_js_1.Log.warn({ logLevel, tag: 'audio' }, 'AudioContext resume rejected, continuing without audio sync', err);
177
+ resolve();
178
+ });
179
+ }).finally(() => {
175
180
  isResuming.current = null;
176
181
  });
177
182
  ctxAndGain.gainNode.gain.cancelScheduledValues(ctxAndGain.audioContext.currentTime);
178
183
  (_a = ctxAndGain.gainNode) === null || _a === void 0 ? void 0 : _a.gain.setValueAtTime(0, ctxAndGain.audioContext.currentTime);
179
184
  (_b = ctxAndGain.gainNode) === null || _b === void 0 ? void 0 : _b.gain.linearRampToValueAtTime(1, ctxAndGain.audioContext.currentTime + 0.03);
180
- return ctxAndGain.audioContext.resume().then(() => {
185
+ return resumePromise
186
+ .then(() => {
181
187
  nodesToResume.current.forEach((r, node) => node.start(r.scheduledTime, r.offset, r.duration));
182
188
  nodesToResume.current.clear();
189
+ })
190
+ .catch(() => {
191
+ // Already logged above; swallow to avoid unhandled rejection
192
+ // since callers (e.g. use-playback.ts) do not await this.
183
193
  });
184
194
  }, [ctxAndGain, logLevel]);
185
195
  const getIsResumingAudioContext = (0, react_1.useCallback)(() => {
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+ import type { EffectsProp } from './effect-types.js';
3
+ export type HtmlInCanvasProps = {
4
+ readonly width: number;
5
+ readonly height: number;
6
+ readonly effects?: EffectsProp;
7
+ readonly children: React.ReactNode;
8
+ readonly className?: string;
9
+ readonly style?: React.CSSProperties;
10
+ readonly pixelRatio?: number;
11
+ };
12
+ export declare const HtmlInCanvas: React.ComponentType<HtmlInCanvasProps>;
@@ -0,0 +1,188 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HtmlInCanvas = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_1 = require("react");
6
+ const enable_sequence_stack_traces_js_1 = require("../enable-sequence-stack-traces.js");
7
+ const Sequence_js_1 = require("../Sequence.js");
8
+ const use_current_frame_js_1 = require("../use-current-frame.js");
9
+ const use_delay_render_js_1 = require("../use-delay-render.js");
10
+ const use_video_config_js_1 = require("../use-video-config.js");
11
+ const wrap_in_schema_js_1 = require("../wrap-in-schema.js");
12
+ const run_effect_chain_js_1 = require("./run-effect-chain.js");
13
+ const isHtmlInCanvasSupported = () => {
14
+ if (typeof document === 'undefined') {
15
+ return false;
16
+ }
17
+ const canvas = document.createElement('canvas');
18
+ const ctx = canvas.getContext('2d');
19
+ return (typeof (ctx === null || ctx === void 0 ? void 0 : ctx.drawElementImage) === 'function' &&
20
+ typeof canvas.requestPaint === 'function');
21
+ };
22
+ const htmlInCanvasSchema = {
23
+ 'style.translate': {
24
+ type: 'translate',
25
+ step: 1,
26
+ default: '0px 0px',
27
+ description: 'Position',
28
+ },
29
+ 'style.scale': {
30
+ type: 'number',
31
+ min: 0.05,
32
+ max: 100,
33
+ step: 0.01,
34
+ default: 1,
35
+ description: 'Scale',
36
+ },
37
+ 'style.rotate': {
38
+ type: 'rotation',
39
+ step: 1,
40
+ default: '0deg',
41
+ description: 'Rotation',
42
+ },
43
+ 'style.opacity': {
44
+ type: 'number',
45
+ min: 0,
46
+ max: 1,
47
+ step: 0.01,
48
+ default: 1,
49
+ description: 'Opacity',
50
+ },
51
+ };
52
+ const HtmlInCanvasInner = ({ width, height, effects = [], children, className, style, pixelRatio = 1, controls, }) => {
53
+ const frame = (0, use_current_frame_js_1.useCurrentFrame)();
54
+ const { delayRender, continueRender, cancelRender } = (0, use_delay_render_js_1.useDelayRender)();
55
+ const sourceCanvasRef = (0, react_1.useRef)(null);
56
+ const sceneRef = (0, react_1.useRef)(null);
57
+ const [outputCanvas, setOutputCanvas] = (0, react_1.useState)(null);
58
+ const chainStateRef = (0, react_1.useRef)(null);
59
+ const sizeRef = (0, react_1.useRef)(null);
60
+ if (!sizeRef.current ||
61
+ sizeRef.current.width !== width ||
62
+ sizeRef.current.height !== height) {
63
+ if (chainStateRef.current) {
64
+ (0, run_effect_chain_js_1.cleanupEffectChainState)(chainStateRef.current);
65
+ }
66
+ chainStateRef.current = (0, run_effect_chain_js_1.createEffectChainState)(width, height);
67
+ sizeRef.current = { width, height };
68
+ }
69
+ // Refs so the paint handler always reads fresh values.
70
+ const effectsRef = (0, react_1.useRef)(effects);
71
+ effectsRef.current = effects;
72
+ const frameRef = (0, react_1.useRef)(frame);
73
+ frameRef.current = frame;
74
+ const pixelRatioRef = (0, react_1.useRef)(pixelRatio);
75
+ pixelRatioRef.current = pixelRatio;
76
+ const widthRef = (0, react_1.useRef)(width);
77
+ widthRef.current = width;
78
+ const heightRef = (0, react_1.useRef)(height);
79
+ heightRef.current = height;
80
+ // Track the current delayRender handle so the paint handler can settle it.
81
+ const pendingHandleRef = (0, react_1.useRef)(null);
82
+ const onPaint = (0, react_1.useCallback)(() => {
83
+ const sourceCanvas = sourceCanvasRef.current;
84
+ const sceneEl = sceneRef.current;
85
+ const chainState = chainStateRef.current;
86
+ const output = outputCanvas;
87
+ const handle = pendingHandleRef.current;
88
+ if (!sourceCanvas ||
89
+ !sceneEl ||
90
+ !chainState ||
91
+ !output ||
92
+ handle === null) {
93
+ return;
94
+ }
95
+ const ctx = sourceCanvas.getContext('2d');
96
+ if (!ctx) {
97
+ cancelRender(new Error('Failed to acquire 2D context for <HtmlInCanvas> source'));
98
+ return;
99
+ }
100
+ const w = widthRef.current;
101
+ const h = heightRef.current;
102
+ ctx.reset();
103
+ ctx.drawElementImage(sceneEl, 0, 0, w, h);
104
+ const capturedHandle = handle;
105
+ pendingHandleRef.current = null;
106
+ (0, run_effect_chain_js_1.runEffectChain)({
107
+ state: chainState,
108
+ source: sourceCanvas,
109
+ effects: effectsRef.current,
110
+ output,
111
+ frame: frameRef.current,
112
+ width: w,
113
+ height: h,
114
+ pixelRatio: pixelRatioRef.current,
115
+ })
116
+ .then((completed) => {
117
+ if (completed) {
118
+ continueRender(capturedHandle);
119
+ }
120
+ })
121
+ .catch((err) => {
122
+ cancelRender(err);
123
+ });
124
+ }, [outputCanvas, continueRender, cancelRender]);
125
+ // Set up layoutSubtree and persistent paint listener.
126
+ (0, react_1.useEffect)(() => {
127
+ if (!isHtmlInCanvasSupported()) {
128
+ cancelRender(new Error('HTML in Canvas is not supported. Open this page in Chrome Canary with chrome://flags/#canvas-draw-element enabled.'));
129
+ return;
130
+ }
131
+ const sourceCanvas = sourceCanvasRef.current;
132
+ if (!sourceCanvas) {
133
+ return;
134
+ }
135
+ sourceCanvas.layoutSubtree = true;
136
+ sourceCanvas.addEventListener('paint', onPaint);
137
+ return () => {
138
+ sourceCanvas.removeEventListener('paint', onPaint);
139
+ };
140
+ }, [onPaint, cancelRender]);
141
+ // On each frame change: block the renderer and request a paint.
142
+ (0, react_1.useEffect)(() => {
143
+ const handle = delayRender(`HtmlInCanvas (frame ${frame})`);
144
+ // Continue a stale handle from a previous frame that never got a paint.
145
+ if (pendingHandleRef.current !== null) {
146
+ continueRender(pendingHandleRef.current);
147
+ }
148
+ pendingHandleRef.current = handle;
149
+ const sourceCanvas = sourceCanvasRef.current;
150
+ sourceCanvas === null || sourceCanvas === void 0 ? void 0 : sourceCanvas.requestPaint();
151
+ return () => {
152
+ if (pendingHandleRef.current === handle) {
153
+ continueRender(handle);
154
+ pendingHandleRef.current = null;
155
+ }
156
+ };
157
+ }, [frame, delayRender, continueRender]);
158
+ // Cleanup chain state on unmount.
159
+ (0, react_1.useEffect)(() => {
160
+ return () => {
161
+ if (chainStateRef.current) {
162
+ (0, run_effect_chain_js_1.cleanupEffectChainState)(chainStateRef.current);
163
+ }
164
+ };
165
+ }, []);
166
+ const { durationInFrames } = (0, use_video_config_js_1.useVideoConfig)();
167
+ return (jsx_runtime_1.jsxs(Sequence_js_1.Sequence, { durationInFrames: durationInFrames, name: "<HtmlInCanvas>", controls: controls, children: [
168
+ jsx_runtime_1.jsx("canvas", { ref: sourceCanvasRef, width: width, height: height, style: {
169
+ position: 'absolute',
170
+ inset: 0,
171
+ width,
172
+ height,
173
+ visibility: 'visible',
174
+ }, children: jsx_runtime_1.jsx("div", { ref: sceneRef, style: {
175
+ width,
176
+ height,
177
+ }, children: children }) }), jsx_runtime_1.jsx("canvas", { ref: setOutputCanvas, width: width, height: height, className: className, style: {
178
+ position: 'absolute',
179
+ inset: 0,
180
+ width,
181
+ height,
182
+ ...style,
183
+ } })
184
+ ] }));
185
+ };
186
+ exports.HtmlInCanvas = (0, wrap_in_schema_js_1.wrapInSchema)(HtmlInCanvasInner, htmlInCanvasSchema);
187
+ exports.HtmlInCanvas.displayName = 'HtmlInCanvas';
188
+ (0, enable_sequence_stack_traces_js_1.addSequenceStackTraces)(exports.HtmlInCanvas);
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+ import type { EffectsProp } from './effect-types.js';
3
+ export type SolidProps = {
4
+ readonly color: string;
5
+ readonly width: number;
6
+ readonly height: number;
7
+ readonly _experimentalEffects?: EffectsProp;
8
+ readonly className?: string;
9
+ readonly style?: React.CSSProperties;
10
+ readonly pixelRatio?: number;
11
+ };
12
+ export declare const Solid: React.FC<SolidProps>;
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Solid = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_1 = require("react");
6
+ const enable_effects_js_1 = require("../enable-effects.js");
7
+ const use_current_frame_js_1 = require("../use-current-frame.js");
8
+ const use_delay_render_js_1 = require("../use-delay-render.js");
9
+ const run_effect_chain_js_1 = require("./run-effect-chain.js");
10
+ const use_effect_chain_state_js_1 = require("./use-effect-chain-state.js");
11
+ const Solid = ({ color, width, height, _experimentalEffects: experimentalEffects = [], className, style, pixelRatio = 1, }) => {
12
+ const frame = (0, use_current_frame_js_1.useCurrentFrame)();
13
+ const { delayRender, continueRender, cancelRender } = (0, use_delay_render_js_1.useDelayRender)();
14
+ const [outputCanvas, setOutputCanvas] = (0, react_1.useState)(null);
15
+ const sourceCanvas = (0, react_1.useMemo)(() => {
16
+ if (typeof document === 'undefined') {
17
+ return null;
18
+ }
19
+ const canvas = document.createElement('canvas');
20
+ canvas.width = 1;
21
+ canvas.height = 1;
22
+ return canvas;
23
+ }, []);
24
+ const chainState = (0, use_effect_chain_state_js_1.useEffectChainState)(width, height);
25
+ // Fill source and run effect chain on every frame / color change.
26
+ (0, react_1.useEffect)(() => {
27
+ if (!outputCanvas || !sourceCanvas) {
28
+ return;
29
+ }
30
+ const handle = delayRender(`Solid effect chain (frame ${frame})`);
31
+ if (!enable_effects_js_1.ENABLE_EFFECTS) {
32
+ const outCtx = outputCanvas.getContext('2d', { colorSpace: 'srgb' });
33
+ if (!outCtx) {
34
+ cancelRender(new Error('Failed to acquire 2D context for <Solid> output'));
35
+ return;
36
+ }
37
+ outCtx.fillStyle = color;
38
+ outCtx.fillRect(0, 0, width, height);
39
+ continueRender(handle);
40
+ return () => {
41
+ continueRender(handle);
42
+ };
43
+ }
44
+ if (!chainState) {
45
+ continueRender(handle);
46
+ return () => {
47
+ continueRender(handle);
48
+ };
49
+ }
50
+ const ctx = sourceCanvas.getContext('2d', { colorSpace: 'srgb' });
51
+ if (!ctx) {
52
+ cancelRender(new Error('Failed to acquire 2D context for <Solid> source'));
53
+ return;
54
+ }
55
+ ctx.fillStyle = color;
56
+ ctx.fillRect(0, 0, 1, 1);
57
+ (0, run_effect_chain_js_1.runEffectChain)({
58
+ state: chainState.current,
59
+ source: sourceCanvas,
60
+ effects: experimentalEffects,
61
+ output: outputCanvas,
62
+ frame,
63
+ width,
64
+ height,
65
+ })
66
+ .then((completed) => {
67
+ if (completed) {
68
+ continueRender(handle);
69
+ }
70
+ })
71
+ .catch((err) => {
72
+ cancelRender(err);
73
+ });
74
+ return () => {
75
+ continueRender(handle);
76
+ };
77
+ }, [
78
+ frame,
79
+ color,
80
+ experimentalEffects,
81
+ outputCanvas,
82
+ sourceCanvas,
83
+ chainState,
84
+ width,
85
+ height,
86
+ pixelRatio,
87
+ delayRender,
88
+ continueRender,
89
+ cancelRender,
90
+ ]);
91
+ return (jsx_runtime_1.jsx("canvas", { ref: setOutputCanvas, width: width, height: height, className: className, style: style }));
92
+ };
93
+ exports.Solid = Solid;
@@ -0,0 +1,13 @@
1
+ import type { Backend } from './effect-types.js';
2
+ type CanvasPair = readonly [HTMLCanvasElement, HTMLCanvasElement];
3
+ export declare class CanvasPool {
4
+ private readonly width;
5
+ private readonly height;
6
+ private readonly pairs;
7
+ private readonly lostContexts;
8
+ constructor(width: number, height: number);
9
+ getPair(backend: Backend): CanvasPair;
10
+ assertContextNotLost(canvas: HTMLCanvasElement): void;
11
+ private allocateCanvas;
12
+ }
13
+ export {};
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CanvasPool = void 0;
4
+ // Per-chain canvas pool. Each chain owns its own pool; pools are not shared
5
+ // across chains because dimensions are chain-specific.
6
+ //
7
+ // Canvases are allocated lazily on first use of a given backend. Once
8
+ // allocated, they are reused every frame for the chain's lifetime. Contexts
9
+ // are created with the cross-backend alpha/sRGB contract enforced (see
10
+ // `effect-types.ts`).
11
+ class CanvasPool {
12
+ width;
13
+ height;
14
+ pairs = new Map();
15
+ lostContexts = new Set();
16
+ constructor(width, height) {
17
+ this.width = width;
18
+ this.height = height;
19
+ }
20
+ getPair(backend) {
21
+ const existing = this.pairs.get(backend);
22
+ if (existing) {
23
+ return existing;
24
+ }
25
+ const pair = [
26
+ this.allocateCanvas(backend),
27
+ this.allocateCanvas(backend),
28
+ ];
29
+ this.pairs.set(backend, pair);
30
+ return pair;
31
+ }
32
+ assertContextNotLost(canvas) {
33
+ if (this.lostContexts.has(canvas)) {
34
+ throw new Error('WebGL context was lost during canvas effect rendering. ' +
35
+ 'This typically happens in headless or memory-constrained environments (e.g. Remotion Lambda). ' +
36
+ 'Try reducing concurrency or increasing the Lambda function memory.');
37
+ }
38
+ }
39
+ allocateCanvas(backend) {
40
+ const canvas = document.createElement('canvas');
41
+ canvas.width = this.width;
42
+ canvas.height = this.height;
43
+ switch (backend) {
44
+ case '2d': {
45
+ const ctx = canvas.getContext('2d', {
46
+ colorSpace: 'srgb',
47
+ });
48
+ if (!ctx) {
49
+ throw new Error('Failed to acquire 2D context for canvas effect');
50
+ }
51
+ return canvas;
52
+ }
53
+ case 'webgl2': {
54
+ const ctx = canvas.getContext('webgl2', {
55
+ premultipliedAlpha: true,
56
+ alpha: true,
57
+ preserveDrawingBuffer: true,
58
+ });
59
+ if (!ctx) {
60
+ throw new Error('Failed to acquire WebGL2 context for canvas effect');
61
+ }
62
+ canvas.addEventListener('webglcontextlost', (e) => {
63
+ e.preventDefault();
64
+ this.lostContexts.add(canvas);
65
+ });
66
+ canvas.addEventListener('webglcontextrestored', () => {
67
+ this.lostContexts.delete(canvas);
68
+ });
69
+ ctx.pixelStorei(ctx.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
70
+ return canvas;
71
+ }
72
+ case 'webgpu': {
73
+ if (typeof navigator === 'undefined' || !('gpu' in navigator)) {
74
+ throw new Error('WebGPU is not available in this environment for canvas effect');
75
+ }
76
+ return canvas;
77
+ }
78
+ default: {
79
+ const exhaustive = backend;
80
+ throw new Error(`Unknown effect backend: ${exhaustive}`);
81
+ }
82
+ }
83
+ }
84
+ }
85
+ exports.CanvasPool = CanvasPool;
@@ -0,0 +1,3 @@
1
+ import type { EffectDefinition, EffectDescriptor } from './effect-types.js';
2
+ export declare const defineEffect: <P, S>(definition: EffectDefinition<P, S>) => EffectDefinition<P, S>;
3
+ export declare const createDescriptor: <P, S>(definition: EffectDefinition<P, S>, params: P) => EffectDescriptor<unknown>;
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createDescriptor = exports.defineEffect = void 0;
4
+ // Identity helper for declaring an effect definition with proper type
5
+ // inference. Wrapping the literal in `defineEffect(...)` lets TypeScript infer
6
+ // `<P, S>` from the `setup` and `apply` signatures while still enforcing the
7
+ // shape of the definition.
8
+ const defineEffect = (definition) => definition;
9
+ exports.defineEffect = defineEffect;
10
+ // Factory helper for constructing per-frame descriptors from a definition.
11
+ // Effect authors typically expose a small wrapper (e.g.
12
+ // `export const blur = (params) => createDescriptor(blurDef, params)`) so users
13
+ // don't reach into the internal definition object.
14
+ //
15
+ // `params` is type-checked against `P` at the call site, but the returned
16
+ // descriptor erases both `P` and `S` to `unknown` so it can be freely
17
+ // composed in `EffectsProp` arrays alongside descriptors of other effects.
18
+ // Without this erasure the descriptor would be contravariant in `P` (via
19
+ // `apply`'s argument), which would prevent assigning a concrete
20
+ // `EffectDescriptor<MyParams>` into an `EffectDescriptor<unknown>` slot.
21
+ const createDescriptor = (definition, params) => {
22
+ return {
23
+ definition: definition,
24
+ params,
25
+ stack: new Error().stack,
26
+ };
27
+ };
28
+ exports.createDescriptor = createDescriptor;
@@ -0,0 +1,7 @@
1
+ import type { Backend, EffectDescriptor, EffectsProp } from './effect-types.js';
2
+ export declare const flattenEffects: (effects: EffectsProp) => EffectDescriptor<unknown>[];
3
+ export type Run = {
4
+ readonly backend: Backend;
5
+ readonly effects: ReadonlyArray<EffectDescriptor<unknown>>;
6
+ };
7
+ export declare const groupByBackend: (effects: readonly EffectDescriptor<unknown>[]) => Run[];
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.groupByBackend = exports.flattenEffects = void 0;
4
+ // Internal helpers for the chain runtime. Exported separately so they can be
5
+ // unit-tested without spinning up the React lifecycle / canvas mocking.
6
+ const flattenEffects = (effects) => {
7
+ const out = [];
8
+ for (const item of effects) {
9
+ if (Array.isArray(item)) {
10
+ for (const inner of item) {
11
+ out.push(inner);
12
+ }
13
+ }
14
+ else {
15
+ out.push(item);
16
+ }
17
+ }
18
+ return out;
19
+ };
20
+ exports.flattenEffects = flattenEffects;
21
+ const groupByBackend = (effects) => {
22
+ const runs = [];
23
+ let current = [];
24
+ let currentBackend = null;
25
+ for (const eff of effects) {
26
+ const { backend } = eff.definition;
27
+ if (currentBackend === null || backend === currentBackend) {
28
+ current.push(eff);
29
+ currentBackend = backend;
30
+ }
31
+ else {
32
+ runs.push({ backend: currentBackend, effects: current });
33
+ current = [eff];
34
+ currentBackend = backend;
35
+ }
36
+ }
37
+ if (currentBackend !== null && current.length > 0) {
38
+ runs.push({ backend: currentBackend, effects: current });
39
+ }
40
+ return runs;
41
+ };
42
+ exports.groupByBackend = groupByBackend;
@@ -0,0 +1,31 @@
1
+ import type { SequenceSchema } from '../internals';
2
+ export type Backend = '2d' | 'webgl2' | 'webgpu';
3
+ type AnyGpuDevice = unknown;
4
+ export type EffectApplyParams<P, S> = {
5
+ readonly source: CanvasImageSource;
6
+ readonly target: HTMLCanvasElement;
7
+ readonly state: S;
8
+ readonly params: P;
9
+ readonly frame: number;
10
+ readonly width: number;
11
+ readonly height: number;
12
+ readonly gpuDevice: AnyGpuDevice | null;
13
+ };
14
+ export type EffectDefinition<P, S = unknown> = {
15
+ readonly type: string;
16
+ readonly label: string;
17
+ readonly backend: Backend;
18
+ readonly setup: (target: HTMLCanvasElement) => S;
19
+ readonly apply: (params: EffectApplyParams<P, S>) => void;
20
+ readonly cleanup: (state: S) => void;
21
+ readonly schema: SequenceSchema | null;
22
+ };
23
+ export type EffectDefinitionAndStack<P = unknown> = {
24
+ readonly definition: EffectDefinition<P, unknown>;
25
+ readonly stack: string;
26
+ };
27
+ export type EffectDescriptor<P = unknown> = EffectDefinitionAndStack<P> & {
28
+ readonly params: P;
29
+ };
30
+ export type EffectsProp = ReadonlyArray<EffectDescriptor<unknown> | ReadonlyArray<EffectDescriptor<unknown>>>;
31
+ export {};
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ // Public types for the canvas-effects system.
3
+ //
4
+ // An effect is a description of how to transform an input image into an output
5
+ // image, executed inside a per-frame chain runtime owned by a source component
6
+ // (`<Solid>`, `<HtmlInCanvas>`, ...). The chain runtime owns scratch canvases
7
+ // and ping-pongs between them, so effects do not allocate per frame.
8
+ //
9
+ // Cross-backend contract: all canvases store premultiplied alpha and are
10
+ // sRGB-encoded. Effects that perform color math in linear space are responsible
11
+ // for converting to/from sRGB themselves.
12
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,2 @@
1
+ export declare const getGpuDevice: () => Promise<unknown>;
2
+ export declare const _resetGpuDeviceForTesting: () => void;
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ // Singleton accessor for the WebGPU device.
3
+ //
4
+ // `navigator.gpu.requestAdapter()` and `adapter.requestDevice()` are async and
5
+ // non-trivially expensive (~10-100ms on first call). The device is cached
6
+ // globally so every webgpu effect / chain shares the same one.
7
+ //
8
+ // `GPUDevice` is intentionally typed as `unknown` here to avoid pulling
9
+ // `@webgpu/types` into core; effects targeting webgpu narrow the type
10
+ // themselves.
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports._resetGpuDeviceForTesting = exports.getGpuDevice = void 0;
13
+ let devicePromise = null;
14
+ const getGpuDevice = () => {
15
+ if (devicePromise) {
16
+ return devicePromise;
17
+ }
18
+ devicePromise = (async () => {
19
+ if (typeof navigator === 'undefined' || !('gpu' in navigator)) {
20
+ throw new Error('WebGPU is not available in this environment');
21
+ }
22
+ const { gpu } = navigator;
23
+ const adapter = (await gpu.requestAdapter());
24
+ if (!adapter) {
25
+ throw new Error('No WebGPU adapter available');
26
+ }
27
+ return adapter.requestDevice();
28
+ })();
29
+ return devicePromise;
30
+ };
31
+ exports.getGpuDevice = getGpuDevice;
32
+ // Test-only: reset the cached device. Not exported from `remotion`.
33
+ const _resetGpuDeviceForTesting = () => {
34
+ devicePromise = null;
35
+ };
36
+ exports._resetGpuDeviceForTesting = _resetGpuDeviceForTesting;
@@ -0,0 +1 @@
1
+ export type { Backend, EffectApplyParams, EffectDefinition, EffectDefinitionAndStack, EffectDescriptor, EffectsProp, } from './effect-types.js';
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });