odori 0.0.7 → 0.0.8
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/effects.d.ts +12 -2
- package/dist/effects.js +64 -4
- package/dist/effects.js.map +1 -1
- package/package.json +1 -1
package/dist/effects.d.ts
CHANGED
|
@@ -72,13 +72,23 @@ type EffectInstance = {
|
|
|
72
72
|
type EffectFactory = (values?: Record<string, UniformValue>) => EffectInstance;
|
|
73
73
|
declare const defineShaderEffect: (definition: ShaderEffectDefinition) => EffectFactory;
|
|
74
74
|
/** Prepended to every effect, so a shader author writes only the body. */
|
|
75
|
-
declare const SHADER_PRELUDE = "#version 300 es\nprecision highp float;\nuniform sampler2D source;\nuniform vec2 resolution;\nuniform float frame;\nuniform float fps;\nuniform float seconds;\nuniform float progress;\nout vec4 odoriColour;\n// sample is a reserved word in GLSL ES 3.00, so the helper is tex.\nvec4 tex(vec2 uv) { return texture(source, uv); }\n";
|
|
75
|
+
declare const SHADER_PRELUDE = "#version 300 es\nprecision highp float;\nuniform sampler2D source;\nuniform vec2 resolution;\nuniform float frame;\nuniform float fps;\nuniform float seconds;\nuniform float progress;\nout vec4 odoriColour;\n// sample is a reserved word in GLSL ES 3.00, so the helper is tex.\nvec4 tex(vec2 uv) { return texture(source, uv); }\n\n/*\n * Randomness that is the same on every backend.\n *\n * The idiom everyone reaches for is fract(sin(dot(p, k)) * 43758.5453), and it\n * is not portable: sin() of a large argument is implementation defined, so\n * SwiftShader and Metal disagree. Measured on one frame of grain, that idiom\n * scored 0.75 SSIM between the two while this one scores 0.999993. Integer\n * arithmetic in GLSL ES 3.00 is exact, so it is the only randomness a renderer\n * that has to reproduce itself can use.\n *\n * Seed from the frame, never from a clock, and the same frame keeps the same\n * noise however it was rendered.\n */\nuint odoriPcg(uint v) {\n uint state = v * 747796405u + 2891336453u;\n uint word = ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u;\n return (word >> 22u) ^ word;\n}\nfloat hash(vec2 p) {\n uvec2 q = uvec2(ivec2(p));\n return float(odoriPcg(q.x ^ odoriPcg(q.y)) & 0xffffffu) / float(0xffffffu);\n}\n";
|
|
76
76
|
declare class ShaderCompileError extends Error {
|
|
77
77
|
readonly effect: string;
|
|
78
78
|
readonly log: string;
|
|
79
79
|
readonly source: string;
|
|
80
80
|
constructor(effect: string, log: string, source: string);
|
|
81
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* The one shader idiom that quietly breaks reproducibility.
|
|
84
|
+
*
|
|
85
|
+
* `fract(sin(dot(p, k)) * large)` is the hash in every shader tutorial, and it
|
|
86
|
+
* produces different noise on different backends because sin() of a large
|
|
87
|
+
* argument is implementation defined. A video written with it renders one way
|
|
88
|
+
* on a laptop and another in CI, with nothing to say why. The prelude ships a
|
|
89
|
+
* portable `hash(vec2)`, so this is refused rather than warned about.
|
|
90
|
+
*/
|
|
91
|
+
declare const assertPortableShader: (name: string, fragmentShader: string) => void;
|
|
82
92
|
type PipelineClock = {
|
|
83
93
|
frame: number;
|
|
84
94
|
fps: number;
|
|
@@ -199,4 +209,4 @@ declare const filmGrain: EffectFactory;
|
|
|
199
209
|
/** Magnify a circle of the picture, like a loupe held over it. */
|
|
200
210
|
declare const magnify: EffectFactory;
|
|
201
211
|
|
|
202
|
-
export { type CaptureMethod, type EffectFactory, type EffectInstance, EffectPipeline, EffectSurface, HtmlInCanvas, type HtmlInCanvasInit, type HtmlInCanvasPaint, SHADER_PRELUDE, ShaderCompileError, type ShaderEffectDefinition, type SurfaceCapabilities, type SurfaceClock, type UniformSpec, type UniformValue, barrelDistortion, captureElement, captureMethod, defineShaderEffect, filmGrain, magnify, numberUniform, pixelate, rgbSplit, scanlines, serializeElement, vec2Uniform, vec3Uniform, vec4Uniform };
|
|
212
|
+
export { type CaptureMethod, type EffectFactory, type EffectInstance, EffectPipeline, EffectSurface, HtmlInCanvas, type HtmlInCanvasInit, type HtmlInCanvasPaint, SHADER_PRELUDE, ShaderCompileError, type ShaderEffectDefinition, type SurfaceCapabilities, type SurfaceClock, type UniformSpec, type UniformValue, assertPortableShader, barrelDistortion, captureElement, captureMethod, defineShaderEffect, filmGrain, magnify, numberUniform, pixelate, rgbSplit, scanlines, serializeElement, vec2Uniform, vec3Uniform, vec4Uniform };
|
package/dist/effects.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import {
|
|
3
|
+
SceneContext,
|
|
3
4
|
useFrame,
|
|
4
5
|
useReadiness,
|
|
5
6
|
useVideo
|
|
@@ -40,6 +41,12 @@ var PAINTED = [
|
|
|
40
41
|
"opacity",
|
|
41
42
|
"overflow",
|
|
42
43
|
"padding",
|
|
44
|
+
"font-family",
|
|
45
|
+
"font-size",
|
|
46
|
+
"font-stretch",
|
|
47
|
+
"font-style",
|
|
48
|
+
"font-variant",
|
|
49
|
+
"font-weight",
|
|
43
50
|
"position",
|
|
44
51
|
"right",
|
|
45
52
|
"text-align",
|
|
@@ -73,9 +80,30 @@ var inlineStyles = (source, clone) => {
|
|
|
73
80
|
if (child) inlineStyles(sourceChildren[index], child);
|
|
74
81
|
}
|
|
75
82
|
};
|
|
83
|
+
var inlineCanvases = (source, clone) => {
|
|
84
|
+
const sourceCanvases = source.querySelectorAll("canvas");
|
|
85
|
+
const cloneCanvases = clone.querySelectorAll("canvas");
|
|
86
|
+
for (let index = 0; index < sourceCanvases.length; index += 1) {
|
|
87
|
+
const original = sourceCanvases[index];
|
|
88
|
+
const placeholder = cloneCanvases[index];
|
|
89
|
+
if (!placeholder) continue;
|
|
90
|
+
let url = "";
|
|
91
|
+
try {
|
|
92
|
+
url = original.toDataURL("image/png");
|
|
93
|
+
} catch {
|
|
94
|
+
}
|
|
95
|
+
const image = clone.ownerDocument.createElement("img");
|
|
96
|
+
image.setAttribute("src", url);
|
|
97
|
+
image.setAttribute("width", String(original.width));
|
|
98
|
+
image.setAttribute("height", String(original.height));
|
|
99
|
+
image.setAttribute("style", placeholder.getAttribute("style") ?? "");
|
|
100
|
+
placeholder.replaceWith(image);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
76
103
|
var serializeElement = (element) => {
|
|
77
104
|
const clone = element.cloneNode(true);
|
|
78
105
|
inlineStyles(element, clone);
|
|
106
|
+
inlineCanvases(element, clone);
|
|
79
107
|
clone.style.position = "static";
|
|
80
108
|
clone.style.left = "auto";
|
|
81
109
|
clone.style.top = "auto";
|
|
@@ -111,6 +139,7 @@ var vec4Uniform = (defaultValue) => ({
|
|
|
111
139
|
defaultValue
|
|
112
140
|
});
|
|
113
141
|
var defineShaderEffect = (definition) => {
|
|
142
|
+
assertPortableShader(definition.name, definition.fragmentShader);
|
|
114
143
|
return (values = {}) => ({ definition, values });
|
|
115
144
|
};
|
|
116
145
|
var VERTEX_SHADER = `#version 300 es
|
|
@@ -129,6 +158,29 @@ uniform float progress;
|
|
|
129
158
|
out vec4 odoriColour;
|
|
130
159
|
// sample is a reserved word in GLSL ES 3.00, so the helper is tex.
|
|
131
160
|
vec4 tex(vec2 uv) { return texture(source, uv); }
|
|
161
|
+
|
|
162
|
+
/*
|
|
163
|
+
* Randomness that is the same on every backend.
|
|
164
|
+
*
|
|
165
|
+
* The idiom everyone reaches for is fract(sin(dot(p, k)) * 43758.5453), and it
|
|
166
|
+
* is not portable: sin() of a large argument is implementation defined, so
|
|
167
|
+
* SwiftShader and Metal disagree. Measured on one frame of grain, that idiom
|
|
168
|
+
* scored 0.75 SSIM between the two while this one scores 0.999993. Integer
|
|
169
|
+
* arithmetic in GLSL ES 3.00 is exact, so it is the only randomness a renderer
|
|
170
|
+
* that has to reproduce itself can use.
|
|
171
|
+
*
|
|
172
|
+
* Seed from the frame, never from a clock, and the same frame keeps the same
|
|
173
|
+
* noise however it was rendered.
|
|
174
|
+
*/
|
|
175
|
+
uint odoriPcg(uint v) {
|
|
176
|
+
uint state = v * 747796405u + 2891336453u;
|
|
177
|
+
uint word = ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u;
|
|
178
|
+
return (word >> 22u) ^ word;
|
|
179
|
+
}
|
|
180
|
+
float hash(vec2 p) {
|
|
181
|
+
uvec2 q = uvec2(ivec2(p));
|
|
182
|
+
return float(odoriPcg(q.x ^ odoriPcg(q.y)) & 0xffffffu) / float(0xffffffu);
|
|
183
|
+
}
|
|
132
184
|
`;
|
|
133
185
|
var ShaderCompileError = class extends Error {
|
|
134
186
|
constructor(effect, log, source) {
|
|
@@ -145,6 +197,13 @@ var ShaderCompileError = class extends Error {
|
|
|
145
197
|
log;
|
|
146
198
|
source;
|
|
147
199
|
};
|
|
200
|
+
var assertPortableShader = (name, fragmentShader) => {
|
|
201
|
+
if (/fract\s*\(\s*sin\s*\(/.test(fragmentShader)) {
|
|
202
|
+
throw new Error(
|
|
203
|
+
`${name}: fract(sin(...)) is not reproducible. sin() of a large argument is implementation defined, so this renders differently on different graphics backends. Use the prelude's hash(vec2), which is exact everywhere.`
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
};
|
|
148
207
|
var compile = (gl, type, source, effect) => {
|
|
149
208
|
const shader = gl.createShader(type);
|
|
150
209
|
if (!shader) throw new Error(`${effect}: the browser refused to create a shader.`);
|
|
@@ -289,7 +348,7 @@ var EffectPipeline = class {
|
|
|
289
348
|
};
|
|
290
349
|
|
|
291
350
|
// src/effects/surface.tsx
|
|
292
|
-
import { useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
351
|
+
import { useContext, useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
293
352
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
294
353
|
var useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
|
|
295
354
|
var hidden = {
|
|
@@ -313,6 +372,7 @@ var HtmlInCanvas = ({
|
|
|
313
372
|
const video = useVideo();
|
|
314
373
|
const frame = useFrame();
|
|
315
374
|
const readiness = useReadiness();
|
|
375
|
+
const scene = useContext(SceneContext);
|
|
316
376
|
const surfaceWidth = width ?? video.width;
|
|
317
377
|
const surfaceHeight = height ?? video.height;
|
|
318
378
|
const source = useRef(null);
|
|
@@ -350,8 +410,8 @@ var HtmlInCanvas = ({
|
|
|
350
410
|
frame,
|
|
351
411
|
fps: video.fps,
|
|
352
412
|
seconds: frame / video.fps,
|
|
353
|
-
progress: frame / Math.max(1, video.durationInFrames - 1),
|
|
354
|
-
durationInFrames: video.durationInFrames,
|
|
413
|
+
progress: scene ? Math.min(1, Math.max(0, (frame - scene.start) / Math.max(1, scene.durationInFrames - 1))) : frame / Math.max(1, video.durationInFrames - 1),
|
|
414
|
+
durationInFrames: scene ? scene.durationInFrames : video.durationInFrames,
|
|
355
415
|
width: surfaceWidth,
|
|
356
416
|
height: surfaceHeight
|
|
357
417
|
};
|
|
@@ -515,7 +575,6 @@ var filmGrain = defineShaderEffect({
|
|
|
515
575
|
uniforms: { amount: numberUniform(0.06) },
|
|
516
576
|
fragmentShader: `
|
|
517
577
|
uniform float amount;
|
|
518
|
-
float hash(vec2 p) { return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123); }
|
|
519
578
|
void main() {
|
|
520
579
|
vec2 uv = gl_FragCoord.xy / resolution;
|
|
521
580
|
float noise = hash(gl_FragCoord.xy + frame) - 0.5;
|
|
@@ -550,6 +609,7 @@ export {
|
|
|
550
609
|
HtmlInCanvas,
|
|
551
610
|
SHADER_PRELUDE,
|
|
552
611
|
ShaderCompileError,
|
|
612
|
+
assertPortableShader,
|
|
553
613
|
barrelDistortion,
|
|
554
614
|
captureElement,
|
|
555
615
|
captureMethod,
|
package/dist/effects.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/effects/capture.ts","../src/effects/shader.ts","../src/effects/surface.tsx","../src/effects/library.ts"],"sourcesContent":["/**\n * Turn a live DOM subtree into an image, deterministically.\n *\n * Chromium can draw an SVG onto a canvas, and an SVG `foreignObject` can hold\n * ordinary markup, which is the whole trick. The reason it needs care is that\n * the foreignObject is a sealed document: nothing outside it reaches in, so a\n * subtree that looks right on the page rasterizes unstyled unless every rule\n * that applied to it is carried across as an inline declaration.\n *\n * There is a native alternative, `CanvasRenderingContext2D.drawElement`, which\n * would make all of this unnecessary. It is not in the pinned render browser,\n * measured rather than assumed, so this is the only path that produces the\n * same pixels in Studio and in an export. `captureMethod()` reports which one\n * is in use so Studio never has to guess.\n */\n\n/** Which capture strategy this browser can actually offer. */\nexport type CaptureMethod = \"native\" | \"foreign-object\";\n\ntype DrawElementContext = CanvasRenderingContext2D & {drawElement?: unknown};\n\nexport const captureMethod = (): CaptureMethod => {\n if (typeof document === \"undefined\") return \"foreign-object\";\n const probe = document.createElement(\"canvas\").getContext(\"2d\") as DrawElementContext | null;\n return probe && typeof probe.drawElement === \"function\" ? \"native\" : \"foreign-object\";\n};\n\n/*\n * Properties worth carrying, rather than everything getComputedStyle returns.\n *\n * A computed style has around 340 entries, and writing all of them onto every\n * node produces a document megabytes wide that the SVG parser then has to\n * read, once per frame. This is the set that changes how a box is painted;\n * anything absent is either inherited from a parent that also carries it or\n * has no effect on a static rasterization.\n */\nconst PAINTED = [\n \"align-items\", \"background\", \"border\", \"border-radius\", \"box-shadow\", \"box-sizing\", \"clip-path\",\n \"color\", \"display\", \"filter\", \"flex\", \"flex-direction\", \"flex-wrap\", \"font\", \"gap\", \"grid\",\n \"grid-template-columns\", \"grid-template-rows\", \"height\", \"justify-content\", \"left\",\n \"letter-spacing\", \"line-height\", \"margin\", \"mix-blend-mode\", \"opacity\", \"overflow\", \"padding\",\n \"position\", \"right\", \"text-align\", \"text-decoration\", \"text-shadow\", \"text-transform\", \"top\",\n \"transform\", \"transform-origin\", \"white-space\", \"width\", \"word-break\", \"writing-mode\",\n \"z-index\",\n] as const;\n\n/**\n * Copy the painted style of every node onto the node itself.\n *\n * The clone is walked beside the original because a clone has no computed\n * style of its own: it is not in the document, so the cascade never ran for\n * it. The two trees are the same shape, so one index walks both.\n */\nconst inlineStyles = (source: Element, clone: Element) => {\n const computed = window.getComputedStyle(source);\n const declarations: string[] = [];\n for (const property of PAINTED) {\n const value = computed.getPropertyValue(property);\n if (value && value !== \"none\" && value !== \"normal\" && value !== \"auto\") {\n declarations.push(`${property}:${value}`);\n }\n }\n clone.setAttribute(\"style\", declarations.join(\";\"));\n clone.removeAttribute(\"class\");\n\n const sourceChildren = source.children;\n const cloneChildren = clone.children;\n for (let index = 0; index < sourceChildren.length; index += 1) {\n const child = cloneChildren[index];\n if (child) inlineStyles(sourceChildren[index], child);\n }\n};\n\n/** The subtree as standalone XHTML, carrying everything it needs to paint. */\nexport const serializeElement = (element: HTMLElement): string => {\n const clone = element.cloneNode(true) as HTMLElement;\n inlineStyles(element, clone);\n\n /*\n * The root is placed by whatever was hiding it, and that placement must not\n * travel into the picture.\n *\n * A surface keeps the subtree it captures off screen, at `position: fixed;\n * left: -99999px`. Copying the computed style faithfully carried those two\n * declarations into the SVG, where they are not \"off screen\" but \"outside\n * the frame\", and every capture came back empty. Inside the foreignObject\n * the root is simply the whole picture, so it says so.\n */\n clone.style.position = \"static\";\n clone.style.left = \"auto\";\n clone.style.top = \"auto\";\n clone.style.right = \"auto\";\n clone.style.bottom = \"auto\";\n clone.style.transform = \"none\";\n clone.style.pointerEvents = \"auto\";\n\n // XHTML, because an SVG foreignObject is parsed as XML and unclosed tags\n // that a browser forgives in HTML are a parse error here.\n return new XMLSerializer().serializeToString(clone);\n};\n\n/**\n * Rasterize a subtree at a fixed size.\n *\n * Fonts first: a face that has not loaded when the SVG is parsed falls back,\n * and the fallback is what gets baked into the pixels. The decode is awaited\n * rather than assumed, so a caller holding the frame releases it only when\n * there is something to capture.\n */\nexport const captureElement = async (\n element: HTMLElement,\n options: {width: number; height: number},\n): Promise<HTMLImageElement> => {\n const {width, height} = options;\n if (document.fonts?.ready) await document.fonts.ready;\n\n const markup = serializeElement(element);\n const svg = [\n `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${width}\" height=\"${height}\">`,\n `<foreignObject width=\"100%\" height=\"100%\">`,\n `<div xmlns=\"http://www.w3.org/1999/xhtml\" style=\"width:${width}px;height:${height}px\">`,\n markup,\n `</div></foreignObject></svg>`,\n ].join(\"\");\n\n const image = new Image(width, height);\n // A data URL rather than a blob URL: a blob is revoked on a timer somebody\n // has to own, and the frame budget is not the place for that bookkeeping.\n image.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;\n await image.decode();\n return image;\n};\n","/**\n * A fragment shader, and everything around it that nobody should have to\n * write twice.\n *\n * An effect is one fragment shader and its named uniforms. The context, the\n * program, the full-screen triangle, the source texture, the framebuffers a\n * multi-pass chain ping-pongs between, and the teardown are all owned here,\n * because every video that reached for a shader was otherwise rebuilding the\n * same two hundred lines and getting the determinism details wrong.\n *\n * Time is not one of the things a shader may ask the browser for. Every\n * built-in uniform derives from the frame, so frame 90 is the same picture\n * whether it was rendered on its own or after eighty-nine others.\n */\n\nexport type UniformValue = number | readonly number[];\n\nexport type UniformSpec = {\n /** How many floats: 1, 2, 3, or 4. Decides which uniform call is made. */\n size: 1 | 2 | 3 | 4;\n defaultValue: UniformValue;\n};\n\nexport const numberUniform = (defaultValue: number): UniformSpec => ({size: 1, defaultValue});\nexport const vec2Uniform = (defaultValue: readonly [number, number]): UniformSpec => ({size: 2, defaultValue});\nexport const vec3Uniform = (defaultValue: readonly [number, number, number]): UniformSpec => ({size: 3, defaultValue});\nexport const vec4Uniform = (defaultValue: readonly [number, number, number, number]): UniformSpec => ({\n size: 4,\n defaultValue,\n});\n\nexport type ShaderEffectDefinition = {\n name: string;\n fragmentShader: string;\n uniforms: Record<string, UniformSpec>;\n};\n\n/** One configured use of an effect, which is what a surface is handed. */\nexport type EffectInstance = {\n definition: ShaderEffectDefinition;\n values: Record<string, UniformValue>;\n};\n\n/** A factory: call it with overrides to get something to put in `effects`. */\nexport type EffectFactory = (values?: Record<string, UniformValue>) => EffectInstance;\n\nexport const defineShaderEffect = (definition: ShaderEffectDefinition): EffectFactory => {\n return (values = {}) => ({definition, values});\n};\n\n/*\n * One triangle that covers the screen, not two that make a quad.\n *\n * A quad's diagonal is a seam the rasterizer visits twice, and any effect\n * reading its neighbourhood can show it. The triangle is bigger than the\n * viewport and clipped, which costs nothing and has no interior edge.\n */\nconst VERTEX_SHADER = `#version 300 es\nvoid main() {\n vec2 corners[3] = vec2[3](vec2(-1.0, -1.0), vec2(3.0, -1.0), vec2(-1.0, 3.0));\n gl_Position = vec4(corners[gl_VertexID], 0.0, 1.0);\n}`;\n\n/** Prepended to every effect, so a shader author writes only the body. */\nexport const SHADER_PRELUDE = `#version 300 es\nprecision highp float;\nuniform sampler2D source;\nuniform vec2 resolution;\nuniform float frame;\nuniform float fps;\nuniform float seconds;\nuniform float progress;\nout vec4 odoriColour;\n// sample is a reserved word in GLSL ES 3.00, so the helper is tex.\nvec4 tex(vec2 uv) { return texture(source, uv); }\n`;\n\nexport class ShaderCompileError extends Error {\n constructor(\n readonly effect: string,\n readonly log: string,\n readonly source: string,\n ) {\n /* The log names a line in the assembled source, and the assembled source\n is not what the author wrote, so the offending line is quoted here\n rather than left as a number to count to. */\n const line = Number(/(\\d+):(\\d+)/.exec(log)?.[2] ?? 0);\n const quoted = source.split(\"\\n\")[line - 1];\n super(`${effect}: ${log.trim()}${quoted ? `\\n ${line} | ${quoted.trim()}` : \"\"}`);\n this.name = \"ShaderCompileError\";\n }\n}\n\nconst compile = (gl: WebGL2RenderingContext, type: number, source: string, effect: string) => {\n const shader = gl.createShader(type);\n if (!shader) throw new Error(`${effect}: the browser refused to create a shader.`);\n gl.shaderSource(shader, source);\n gl.compileShader(shader);\n if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n const log = gl.getShaderInfoLog(shader) ?? \"compilation failed\";\n gl.deleteShader(shader);\n throw new ShaderCompileError(effect, log, source);\n }\n return shader;\n};\n\ntype Pass = {\n program: WebGLProgram;\n locations: Map<string, WebGLUniformLocation | null>;\n definition: ShaderEffectDefinition;\n};\n\n/** A texture and the framebuffer that draws into it. */\ntype Target = {texture: WebGLTexture; framebuffer: WebGLFramebuffer};\n\nconst createTarget = (gl: WebGL2RenderingContext, width: number, height: number): Target => {\n const texture = gl.createTexture()!;\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);\n // Clamped and linear: an effect that reaches past an edge should find the\n // edge repeated rather than the opposite side of the picture.\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);\n\n const framebuffer = gl.createFramebuffer()!;\n gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);\n gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);\n gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n return {texture, framebuffer};\n};\n\nexport type PipelineClock = {frame: number; fps: number; durationInFrames: number};\n\n/**\n * The compiled chain for one surface. Built once, painted every frame, and\n * disposed when the surface unmounts.\n */\nexport class EffectPipeline {\n private readonly gl: WebGL2RenderingContext;\n private readonly passes: Pass[] = [];\n private readonly sourceTexture: WebGLTexture;\n private targets: [Target, Target] | null = null;\n private width = 0;\n private height = 0;\n\n constructor(canvas: HTMLCanvasElement, effects: readonly EffectInstance[]) {\n const gl = canvas.getContext(\"webgl2\", {\n // The buffer is read back by odori test and by the screenshot, and a\n // composited buffer is cleared unless this is asked for.\n preserveDrawingBuffer: true,\n premultipliedAlpha: false,\n antialias: false,\n });\n if (!gl) {\n throw new Error(\n \"WebGL2 is unavailable. Studio needs hardware or software GL; the render worker enables ANGLE's software backend itself.\",\n );\n }\n this.gl = gl;\n this.sourceTexture = gl.createTexture()!;\n gl.bindTexture(gl.TEXTURE_2D, this.sourceTexture);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);\n\n const vertex = compile(gl, gl.VERTEX_SHADER, VERTEX_SHADER, \"odori\");\n for (const {definition} of effects) {\n const source = `${SHADER_PRELUDE}${definition.fragmentShader}`;\n const program = gl.createProgram()!;\n gl.attachShader(program, vertex);\n gl.attachShader(program, compile(gl, gl.FRAGMENT_SHADER, source, definition.name));\n gl.linkProgram(program);\n if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {\n throw new Error(`${definition.name}: ${gl.getProgramInfoLog(program) ?? \"link failed\"}`);\n }\n const locations = new Map<string, WebGLUniformLocation | null>();\n for (const name of [\"source\", \"resolution\", \"frame\", \"fps\", \"seconds\", \"progress\", ...Object.keys(definition.uniforms)]) {\n locations.set(name, gl.getUniformLocation(program, name));\n }\n this.passes.push({program, locations, definition});\n }\n }\n\n /** The renderer as Studio reports it, for the capability panel. */\n get renderer(): string {\n const debug = this.gl.getExtension(\"WEBGL_debug_renderer_info\");\n return String(this.gl.getParameter(debug ? debug.UNMASKED_RENDERER_WEBGL : this.gl.RENDERER));\n }\n\n private resize(width: number, height: number) {\n if (this.width === width && this.height === height && this.targets) return;\n this.disposeTargets();\n this.width = width;\n this.height = height;\n // Only needed when more than one pass has to hand its output on.\n this.targets = this.passes.length > 1 ? [createTarget(this.gl, width, height), createTarget(this.gl, width, height)] : null;\n }\n\n private setUniforms(pass: Pass, effect: EffectInstance, clock: PipelineClock) {\n const {gl} = this;\n const at = (name: string) => pass.locations.get(name) ?? null;\n gl.uniform1i(at(\"source\"), 0);\n gl.uniform2f(at(\"resolution\"), this.width, this.height);\n gl.uniform1f(at(\"frame\"), clock.frame);\n gl.uniform1f(at(\"fps\"), clock.fps);\n gl.uniform1f(at(\"seconds\"), clock.frame / clock.fps);\n gl.uniform1f(at(\"progress\"), clock.frame / Math.max(1, clock.durationInFrames - 1));\n\n for (const [name, spec] of Object.entries(pass.definition.uniforms)) {\n const value = effect.values[name] ?? spec.defaultValue;\n const location = at(name);\n if (location === null) continue;\n const numbers = typeof value === \"number\" ? [value] : [...value];\n if (spec.size === 1) gl.uniform1f(location, numbers[0]);\n else if (spec.size === 2) gl.uniform2f(location, numbers[0], numbers[1]);\n else if (spec.size === 3) gl.uniform3f(location, numbers[0], numbers[1], numbers[2]);\n else gl.uniform4f(location, numbers[0], numbers[1], numbers[2], numbers[3]);\n }\n }\n\n /** Upload the captured picture and run every pass over it. */\n paint(source: TexImageSource, effects: readonly EffectInstance[], clock: PipelineClock) {\n const {gl} = this;\n const canvas = gl.canvas as HTMLCanvasElement;\n this.resize(canvas.width, canvas.height);\n gl.viewport(0, 0, this.width, this.height);\n\n // Uploaded once per frame however many passes read it.\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, this.sourceTexture);\n gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, source);\n\n let input = this.sourceTexture;\n this.passes.forEach((pass, index) => {\n const last = index === this.passes.length - 1;\n const target = last || !this.targets ? null : this.targets[index % 2];\n gl.bindFramebuffer(gl.FRAMEBUFFER, target ? target.framebuffer : null);\n gl.useProgram(pass.program);\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, input);\n this.setUniforms(pass, effects[index], clock);\n gl.drawArrays(gl.TRIANGLES, 0, 3);\n if (target) input = target.texture;\n });\n\n gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n // The screenshot must not see a queued frame.\n gl.finish();\n }\n\n private disposeTargets() {\n if (!this.targets) return;\n for (const target of this.targets) {\n this.gl.deleteFramebuffer(target.framebuffer);\n this.gl.deleteTexture(target.texture);\n }\n this.targets = null;\n }\n\n dispose() {\n this.disposeTargets();\n this.gl.deleteTexture(this.sourceTexture);\n for (const pass of this.passes) this.gl.deleteProgram(pass.program);\n this.passes.length = 0;\n }\n}\n","\"use client\";\n\nimport {useEffect, useLayoutEffect, useRef, useState, type ReactNode} from \"react\";\nimport {useFrame, useReadiness, useVideo} from \"../context\";\nimport {captureElement, captureMethod, type CaptureMethod} from \"./capture\";\nimport {EffectPipeline, ShaderCompileError, type EffectInstance} from \"./shader\";\n\n/** `useLayoutEffect` warns during server rendering, where there is no DOM. */\nconst useIsomorphicLayoutEffect = typeof window === \"undefined\" ? useEffect : useLayoutEffect;\n\nexport type SurfaceClock = {\n frame: number;\n fps: number;\n seconds: number;\n /** 0 to 1 across the composition. */\n progress: number;\n durationInFrames: number;\n width: number;\n height: number;\n};\n\nexport type HtmlInCanvasPaint<Renderer> = (\n args: SurfaceClock & {renderer: Renderer; source: CanvasImageSource; canvas: HTMLCanvasElement},\n) => void;\n\nexport type HtmlInCanvasInit<Renderer> = (args: {canvas: HTMLCanvasElement; width: number; height: number}) => Renderer;\n\n/**\n * What Studio shows about a surface, so a difference between preview and\n * export is diagnosable without opening devtools.\n */\nexport type SurfaceCapabilities = {\n capture: CaptureMethod;\n backend: \"canvas-2d\" | \"webgl2\";\n renderer?: string;\n error?: string;\n};\n\nconst hidden: React.CSSProperties = {\n // Laid out at full size and painted, because an element with `display:none`\n // has no computed style worth copying, but kept off screen and out of the\n // way of hit testing.\n left: \"-99999px\",\n pointerEvents: \"none\",\n position: \"fixed\",\n top: 0,\n};\n\n/**\n * Capture a React subtree once per frame and hand the picture to a painter.\n *\n * The children are ordinary React, laid out by the browser, which is the whole\n * point: typography and product UI stay in the language they are written in\n * and only the compositing is different. They are rendered off screen rather\n * than into the canvas, captured, and then drawn.\n *\n * Determinism is the contract. The frame is held from before the capture\n * starts until after the paint has finished, so an export never screenshots a\n * surface that is halfway through, and every value a painter is given comes\n * from the frame rather than from a clock.\n */\nexport const HtmlInCanvas = <Renderer,>({\n children,\n width,\n height,\n onInit,\n onPaint,\n onDispose,\n onCapabilities,\n}: {\n children: ReactNode;\n /** Defaults to the composition's own size. */\n width?: number;\n height?: number;\n onInit?: HtmlInCanvasInit<Renderer>;\n onPaint?: HtmlInCanvasPaint<Renderer>;\n onDispose?: (renderer: Renderer) => void;\n onCapabilities?: (capabilities: SurfaceCapabilities) => void;\n}) => {\n const video = useVideo();\n const frame = useFrame();\n const readiness = useReadiness();\n const surfaceWidth = width ?? video.width;\n const surfaceHeight = height ?? video.height;\n\n const source = useRef<HTMLDivElement>(null);\n const canvas = useRef<HTMLCanvasElement>(null);\n const renderer = useRef<Renderer | null>(null);\n const [failure, setFailure] = useState<string | null>(null);\n\n /* Kept in refs so a new inline callback does not re-run the paint effect,\n which would repaint on every render rather than every frame. */\n const latest = useRef({onInit, onPaint, onDispose, onCapabilities});\n latest.current = {onInit, onPaint, onDispose, onCapabilities};\n\n useIsomorphicLayoutEffect(() => {\n const element = canvas.current;\n if (!element || !latest.current.onInit) return undefined;\n try {\n renderer.current = latest.current.onInit({canvas: element, width: surfaceWidth, height: surfaceHeight});\n } catch (error) {\n setFailure(error instanceof Error ? error.message : String(error));\n }\n return () => {\n if (renderer.current !== null) latest.current.onDispose?.(renderer.current);\n renderer.current = null;\n };\n }, [surfaceWidth, surfaceHeight]);\n\n useIsomorphicLayoutEffect(() => {\n const host = source.current;\n const element = canvas.current;\n if (!host || !element) return undefined;\n\n /*\n * A composition is mounted more than once.\n *\n * The runtime renders it a second time inside its audio pass, hidden, to\n * collect every cue without seeking the timeline. That copy is real React\n * with real refs, so this effect runs there too, and it held the frame\n * while trying to photograph an element with no layout. The hold never\n * came back and the export waited for a readiness that could not arrive.\n *\n * offsetWidth is layout, not paint, so it is zero exactly when there is\n * nothing to capture and unaffected by the scale Studio fits the stage\n * with. No layout, no picture, no hold.\n */\n if (host.offsetWidth === 0 || host.offsetHeight === 0) return undefined;\n\n const release = readiness.hold();\n let cancelled = false;\n\n void (async () => {\n try {\n const picture = await captureElement(host, {width: surfaceWidth, height: surfaceHeight});\n if (cancelled) return;\n const paint = latest.current.onPaint;\n const clock: SurfaceClock = {\n frame,\n fps: video.fps,\n seconds: frame / video.fps,\n progress: frame / Math.max(1, video.durationInFrames - 1),\n durationInFrames: video.durationInFrames,\n width: surfaceWidth,\n height: surfaceHeight,\n };\n if (paint) {\n paint({...clock, renderer: renderer.current as Renderer, source: picture, canvas: element});\n } else {\n // No painter: the surface is just a deterministic rasterization.\n const context = element.getContext(\"2d\");\n context?.clearRect(0, 0, element.width, element.height);\n context?.drawImage(picture, 0, 0, element.width, element.height);\n }\n } catch (error) {\n if (!cancelled) setFailure(error instanceof Error ? error.message : String(error));\n } finally {\n release();\n }\n })();\n\n return () => {\n cancelled = true;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [frame, surfaceWidth, surfaceHeight]);\n\n useEffect(() => {\n latest.current.onCapabilities?.({\n capture: captureMethod(),\n backend: latest.current.onInit ? \"webgl2\" : \"canvas-2d\",\n ...(failure ? {error: failure} : {}),\n });\n }, [failure]);\n\n return (\n <>\n {/* The subtree being captured. Off screen, not hidden: a display:none\n element has no layout and nothing to photograph. */}\n <div\n ref={source}\n /* Marked so the checks know this is the thing being photographed and\n not part of the picture. Without it, every word in the subtree is\n reported as escaping the canvas, which it does, on purpose. */\n data-odori-capture=\"\"\n style={{...hidden, height: surfaceHeight, width: surfaceWidth}}\n aria-hidden=\"true\"\n >\n {children}\n </div>\n <canvas\n ref={canvas}\n width={surfaceWidth}\n height={surfaceHeight}\n style={{display: \"block\", height: \"100%\", width: \"100%\"}}\n />\n {failure ? (\n <pre\n style={{\n background: \"#1a0b0b\",\n color: \"#ff9d9d\",\n font: \"13px ui-monospace, monospace\",\n inset: 0,\n margin: 0,\n padding: 24,\n position: \"absolute\",\n whiteSpace: \"pre-wrap\",\n }}\n >\n {failure}\n </pre>\n ) : null}\n </>\n );\n};\n\n/**\n * The same capture, with a chain of shaders over it.\n *\n * One surface runs every effect in a single pipeline. Nesting two surfaces\n * would capture and upload the DOM twice for one picture, which is why the\n * composition is a list here rather than something to wrap repeatedly.\n */\nexport const EffectSurface = ({\n children,\n effects,\n width,\n height,\n onCapabilities,\n}: {\n children: ReactNode;\n effects: readonly EffectInstance[];\n width?: number;\n height?: number;\n onCapabilities?: (capabilities: SurfaceCapabilities) => void;\n}) => {\n const pipeline = useRef<EffectPipeline | null>(null);\n const list = useRef(effects);\n list.current = effects;\n\n return (\n <HtmlInCanvas<EffectPipeline | null>\n width={width}\n height={height}\n onInit={({canvas}) => {\n try {\n pipeline.current = new EffectPipeline(canvas, list.current);\n } catch (error) {\n pipeline.current = null;\n throw error instanceof ShaderCompileError ? error : new Error(String(error));\n }\n return pipeline.current;\n }}\n onPaint={({renderer, source, frame, fps, durationInFrames}) => {\n if (!renderer) return;\n renderer.paint(source as TexImageSource, list.current, {frame, fps, durationInFrames});\n }}\n onDispose={(renderer) => renderer?.dispose()}\n onCapabilities={onCapabilities}\n >\n {children}\n </HtmlInCanvas>\n );\n};\n","import {defineShaderEffect, numberUniform, vec2Uniform} from \"./shader\";\n\n/**\n * The effects Odori ships, written the way a project would write its own.\n *\n * Each is one fragment shader against `SHADER_PRELUDE`, so `tex(uv)` reads\n * the captured picture and `resolution`, `frame`, `fps`, `seconds` and\n * `progress` are already in scope. Nothing here reaches for a clock, and\n * nothing carries state between frames, which is what lets a worker render\n * frame 200 without having rendered frame 199.\n */\n\n/** Pull the picture out through a lens, or push it in. */\nexport const barrelDistortion = defineShaderEffect({\n name: \"barrel-distortion\",\n uniforms: {\n amount: numberUniform(0.3),\n radius: numberUniform(0.6),\n centre: vec2Uniform([0.5, 0.5]),\n },\n fragmentShader: `\nuniform float amount;\nuniform float radius;\nuniform vec2 centre;\nvoid main() {\n vec2 uv = gl_FragCoord.xy / resolution;\n // Corrected for the frame's shape, or a wide composition bulges into an\n // oval instead of a circle.\n vec2 aspect = vec2(resolution.x / resolution.y, 1.0);\n vec2 offset = (uv - centre) * aspect;\n float distance = length(offset) / max(radius, 0.0001);\n float scale = 1.0 + amount * (1.0 - clamp(distance, 0.0, 1.0));\n odoriColour = tex(centre + offset / scale / aspect);\n}`,\n});\n\n/** Separate the channels, the way a cheap lens does at its edges. */\nexport const rgbSplit = defineShaderEffect({\n name: \"rgb-split\",\n uniforms: {amount: numberUniform(4.0), angle: numberUniform(0.0)},\n fragmentShader: `\nuniform float amount;\nuniform float angle;\nvoid main() {\n vec2 uv = gl_FragCoord.xy / resolution;\n vec2 shift = vec2(cos(angle), sin(angle)) * amount / resolution;\n odoriColour = vec4(tex(uv + shift).r, tex(uv).g, tex(uv - shift).b, tex(uv).a);\n}`,\n});\n\n/** Horizontal lines, restrained enough to read as a screen and not a costume. */\nexport const scanlines = defineShaderEffect({\n name: \"scanlines\",\n uniforms: {opacity: numberUniform(0.08), spacing: numberUniform(3.0)},\n fragmentShader: `\nuniform float opacity;\nuniform float spacing;\nvoid main() {\n vec2 uv = gl_FragCoord.xy / resolution;\n float line = step(0.5, fract(gl_FragCoord.y / max(spacing, 1.0)));\n odoriColour = tex(uv) * (1.0 - opacity * line);\n}`,\n});\n\n/** Quantise to blocks. Sizes in output pixels, so it reads the same at any scale. */\nexport const pixelate = defineShaderEffect({\n name: \"pixelate\",\n uniforms: {size: numberUniform(8.0)},\n fragmentShader: `\nuniform float size;\nvoid main() {\n float block = max(size, 1.0);\n vec2 snapped = (floor(gl_FragCoord.xy / block) + 0.5) * block;\n odoriColour = tex(snapped / resolution);\n}`,\n});\n\n/**\n * Grain. Seeded from the frame rather than from a random number, so the same\n * frame has the same grain every time it is rendered.\n */\nexport const filmGrain = defineShaderEffect({\n name: \"film-grain\",\n uniforms: {amount: numberUniform(0.06)},\n fragmentShader: `\nuniform float amount;\nfloat hash(vec2 p) { return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123); }\nvoid main() {\n vec2 uv = gl_FragCoord.xy / resolution;\n float noise = hash(gl_FragCoord.xy + frame) - 0.5;\n odoriColour = tex(uv) + vec4(vec3(noise * amount), 0.0);\n}`,\n});\n\n/** Magnify a circle of the picture, like a loupe held over it. */\nexport const magnify = defineShaderEffect({\n name: \"magnify\",\n uniforms: {\n amount: numberUniform(1.6),\n radius: numberUniform(0.22),\n centre: vec2Uniform([0.5, 0.5]),\n softness: numberUniform(0.06),\n },\n fragmentShader: `\nuniform float amount;\nuniform float radius;\nuniform vec2 centre;\nuniform float softness;\nvoid main() {\n vec2 uv = gl_FragCoord.xy / resolution;\n vec2 aspect = vec2(resolution.x / resolution.y, 1.0);\n vec2 offset = (uv - centre) * aspect;\n float inside = 1.0 - smoothstep(radius - softness, radius + softness, length(offset));\n float scale = mix(1.0, max(amount, 0.0001), inside);\n odoriColour = tex(centre + offset / scale / aspect);\n}`,\n});\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/effects/capture.ts","../src/effects/shader.ts","../src/effects/surface.tsx","../src/effects/library.ts"],"sourcesContent":["/**\n * Turn a live DOM subtree into an image, deterministically.\n *\n * Chromium can draw an SVG onto a canvas, and an SVG `foreignObject` can hold\n * ordinary markup, which is the whole trick. The reason it needs care is that\n * the foreignObject is a sealed document: nothing outside it reaches in, so a\n * subtree that looks right on the page rasterizes unstyled unless every rule\n * that applied to it is carried across as an inline declaration.\n *\n * There is a native alternative, `CanvasRenderingContext2D.drawElement`, which\n * would make all of this unnecessary. It is not in the pinned render browser,\n * measured rather than assumed, so this is the only path that produces the\n * same pixels in Studio and in an export. `captureMethod()` reports which one\n * is in use so Studio never has to guess.\n */\n\n/** Which capture strategy this browser can actually offer. */\nexport type CaptureMethod = \"native\" | \"foreign-object\";\n\ntype DrawElementContext = CanvasRenderingContext2D & {drawElement?: unknown};\n\nexport const captureMethod = (): CaptureMethod => {\n if (typeof document === \"undefined\") return \"foreign-object\";\n const probe = document.createElement(\"canvas\").getContext(\"2d\") as DrawElementContext | null;\n return probe && typeof probe.drawElement === \"function\" ? \"native\" : \"foreign-object\";\n};\n\n/*\n * Properties worth carrying, rather than everything getComputedStyle returns.\n *\n * A computed style has around 340 entries, and writing all of them onto every\n * node produces a document megabytes wide that the SVG parser then has to\n * read, once per frame. This is the set that changes how a box is painted;\n * anything absent is either inherited from a parent that also carries it or\n * has no effect on a static rasterization.\n */\n/*\n * The `font` shorthand is in here and is not enough on its own: Chrome returns\n * an empty string for it from a computed style, so a subtree captured with only\n * the shorthand loses its type entirely and rasterizes at the SVG default of\n * 16px serif. The longhands are what actually carry it.\n */\nconst PAINTED = [\n \"align-items\", \"background\", \"border\", \"border-radius\", \"box-shadow\", \"box-sizing\", \"clip-path\",\n \"color\", \"display\", \"filter\", \"flex\", \"flex-direction\", \"flex-wrap\", \"font\", \"gap\", \"grid\",\n \"grid-template-columns\", \"grid-template-rows\", \"height\", \"justify-content\", \"left\",\n \"letter-spacing\", \"line-height\", \"margin\", \"mix-blend-mode\", \"opacity\", \"overflow\", \"padding\",\n \"font-family\", \"font-size\", \"font-stretch\", \"font-style\", \"font-variant\", \"font-weight\",\n \"position\", \"right\", \"text-align\", \"text-decoration\", \"text-shadow\", \"text-transform\", \"top\",\n \"transform\", \"transform-origin\", \"white-space\", \"width\", \"word-break\", \"writing-mode\",\n \"z-index\",\n] as const;\n\n/**\n * Copy the painted style of every node onto the node itself.\n *\n * The clone is walked beside the original because a clone has no computed\n * style of its own: it is not in the document, so the cascade never ran for\n * it. The two trees are the same shape, so one index walks both.\n */\nconst inlineStyles = (source: Element, clone: Element) => {\n const computed = window.getComputedStyle(source);\n const declarations: string[] = [];\n for (const property of PAINTED) {\n const value = computed.getPropertyValue(property);\n if (value && value !== \"none\" && value !== \"normal\" && value !== \"auto\") {\n declarations.push(`${property}:${value}`);\n }\n }\n clone.setAttribute(\"style\", declarations.join(\";\"));\n clone.removeAttribute(\"class\");\n\n const sourceChildren = source.children;\n const cloneChildren = clone.children;\n for (let index = 0; index < sourceChildren.length; index += 1) {\n const child = cloneChildren[index];\n if (child) inlineStyles(sourceChildren[index], child);\n }\n};\n\n/**\n * Replace every canvas with a picture of itself.\n *\n * A canvas serializes to an empty element: its pixels live in a drawing buffer\n * that no amount of markup describes. So a chart, or another effect surface,\n * or a three.js scene sitting inside the subtree came out as a hole. Reading\n * each one back as a data URL and swapping in an `img` keeps them, and it is\n * what makes a surface composable with everything else that draws.\n *\n * A GL canvas only reads back if it was asked for with preserveDrawingBuffer,\n * which the pipeline here does. One that was not comes back blank rather than\n * throwing, so a foreign canvas degrades to a hole instead of a failure.\n */\nconst inlineCanvases = (source: Element, clone: Element) => {\n const sourceCanvases = source.querySelectorAll(\"canvas\");\n const cloneCanvases = clone.querySelectorAll(\"canvas\");\n for (let index = 0; index < sourceCanvases.length; index += 1) {\n const original = sourceCanvases[index];\n const placeholder = cloneCanvases[index];\n if (!placeholder) continue;\n let url = \"\";\n try {\n url = original.toDataURL(\"image/png\");\n } catch {\n /* Tainted by a cross-origin draw: nothing to be done, and losing one\n canvas is better than losing the frame. */\n }\n const image = clone.ownerDocument.createElement(\"img\");\n image.setAttribute(\"src\", url);\n image.setAttribute(\"width\", String(original.width));\n image.setAttribute(\"height\", String(original.height));\n image.setAttribute(\"style\", placeholder.getAttribute(\"style\") ?? \"\");\n placeholder.replaceWith(image);\n }\n};\n\n/** The subtree as standalone XHTML, carrying everything it needs to paint. */\nexport const serializeElement = (element: HTMLElement): string => {\n const clone = element.cloneNode(true) as HTMLElement;\n inlineStyles(element, clone);\n // After the styles, so the img inherits the box the canvas was laid out in.\n inlineCanvases(element, clone);\n\n /*\n * The root is placed by whatever was hiding it, and that placement must not\n * travel into the picture.\n *\n * A surface keeps the subtree it captures off screen, at `position: fixed;\n * left: -99999px`. Copying the computed style faithfully carried those two\n * declarations into the SVG, where they are not \"off screen\" but \"outside\n * the frame\", and every capture came back empty. Inside the foreignObject\n * the root is simply the whole picture, so it says so.\n */\n clone.style.position = \"static\";\n clone.style.left = \"auto\";\n clone.style.top = \"auto\";\n clone.style.right = \"auto\";\n clone.style.bottom = \"auto\";\n clone.style.transform = \"none\";\n clone.style.pointerEvents = \"auto\";\n\n // XHTML, because an SVG foreignObject is parsed as XML and unclosed tags\n // that a browser forgives in HTML are a parse error here.\n return new XMLSerializer().serializeToString(clone);\n};\n\n/**\n * Rasterize a subtree at a fixed size.\n *\n * Fonts first: a face that has not loaded when the SVG is parsed falls back,\n * and the fallback is what gets baked into the pixels. The decode is awaited\n * rather than assumed, so a caller holding the frame releases it only when\n * there is something to capture.\n */\nexport const captureElement = async (\n element: HTMLElement,\n options: {width: number; height: number},\n): Promise<HTMLImageElement> => {\n const {width, height} = options;\n if (document.fonts?.ready) await document.fonts.ready;\n\n const markup = serializeElement(element);\n const svg = [\n `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${width}\" height=\"${height}\">`,\n `<foreignObject width=\"100%\" height=\"100%\">`,\n `<div xmlns=\"http://www.w3.org/1999/xhtml\" style=\"width:${width}px;height:${height}px\">`,\n markup,\n `</div></foreignObject></svg>`,\n ].join(\"\");\n\n const image = new Image(width, height);\n // A data URL rather than a blob URL: a blob is revoked on a timer somebody\n // has to own, and the frame budget is not the place for that bookkeeping.\n image.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;\n await image.decode();\n return image;\n};\n","/**\n * A fragment shader, and everything around it that nobody should have to\n * write twice.\n *\n * An effect is one fragment shader and its named uniforms. The context, the\n * program, the full-screen triangle, the source texture, the framebuffers a\n * multi-pass chain ping-pongs between, and the teardown are all owned here,\n * because every video that reached for a shader was otherwise rebuilding the\n * same two hundred lines and getting the determinism details wrong.\n *\n * Time is not one of the things a shader may ask the browser for. Every\n * built-in uniform derives from the frame, so frame 90 is the same picture\n * whether it was rendered on its own or after eighty-nine others.\n */\n\nexport type UniformValue = number | readonly number[];\n\nexport type UniformSpec = {\n /** How many floats: 1, 2, 3, or 4. Decides which uniform call is made. */\n size: 1 | 2 | 3 | 4;\n defaultValue: UniformValue;\n};\n\nexport const numberUniform = (defaultValue: number): UniformSpec => ({size: 1, defaultValue});\nexport const vec2Uniform = (defaultValue: readonly [number, number]): UniformSpec => ({size: 2, defaultValue});\nexport const vec3Uniform = (defaultValue: readonly [number, number, number]): UniformSpec => ({size: 3, defaultValue});\nexport const vec4Uniform = (defaultValue: readonly [number, number, number, number]): UniformSpec => ({\n size: 4,\n defaultValue,\n});\n\nexport type ShaderEffectDefinition = {\n name: string;\n fragmentShader: string;\n uniforms: Record<string, UniformSpec>;\n};\n\n/** One configured use of an effect, which is what a surface is handed. */\nexport type EffectInstance = {\n definition: ShaderEffectDefinition;\n values: Record<string, UniformValue>;\n};\n\n/** A factory: call it with overrides to get something to put in `effects`. */\nexport type EffectFactory = (values?: Record<string, UniformValue>) => EffectInstance;\n\nexport const defineShaderEffect = (definition: ShaderEffectDefinition): EffectFactory => {\n // At definition time, so it fails when the module loads rather than on the\n // one frame somebody happens to render on the other backend.\n assertPortableShader(definition.name, definition.fragmentShader);\n return (values = {}) => ({definition, values});\n};\n\n/*\n * One triangle that covers the screen, not two that make a quad.\n *\n * A quad's diagonal is a seam the rasterizer visits twice, and any effect\n * reading its neighbourhood can show it. The triangle is bigger than the\n * viewport and clipped, which costs nothing and has no interior edge.\n */\nconst VERTEX_SHADER = `#version 300 es\nvoid main() {\n vec2 corners[3] = vec2[3](vec2(-1.0, -1.0), vec2(3.0, -1.0), vec2(-1.0, 3.0));\n gl_Position = vec4(corners[gl_VertexID], 0.0, 1.0);\n}`;\n\n/** Prepended to every effect, so a shader author writes only the body. */\nexport const SHADER_PRELUDE = `#version 300 es\nprecision highp float;\nuniform sampler2D source;\nuniform vec2 resolution;\nuniform float frame;\nuniform float fps;\nuniform float seconds;\nuniform float progress;\nout vec4 odoriColour;\n// sample is a reserved word in GLSL ES 3.00, so the helper is tex.\nvec4 tex(vec2 uv) { return texture(source, uv); }\n\n/*\n * Randomness that is the same on every backend.\n *\n * The idiom everyone reaches for is fract(sin(dot(p, k)) * 43758.5453), and it\n * is not portable: sin() of a large argument is implementation defined, so\n * SwiftShader and Metal disagree. Measured on one frame of grain, that idiom\n * scored 0.75 SSIM between the two while this one scores 0.999993. Integer\n * arithmetic in GLSL ES 3.00 is exact, so it is the only randomness a renderer\n * that has to reproduce itself can use.\n *\n * Seed from the frame, never from a clock, and the same frame keeps the same\n * noise however it was rendered.\n */\nuint odoriPcg(uint v) {\n uint state = v * 747796405u + 2891336453u;\n uint word = ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u;\n return (word >> 22u) ^ word;\n}\nfloat hash(vec2 p) {\n uvec2 q = uvec2(ivec2(p));\n return float(odoriPcg(q.x ^ odoriPcg(q.y)) & 0xffffffu) / float(0xffffffu);\n}\n`;\n\nexport class ShaderCompileError extends Error {\n constructor(\n readonly effect: string,\n readonly log: string,\n readonly source: string,\n ) {\n /* The log names a line in the assembled source, and the assembled source\n is not what the author wrote, so the offending line is quoted here\n rather than left as a number to count to. */\n const line = Number(/(\\d+):(\\d+)/.exec(log)?.[2] ?? 0);\n const quoted = source.split(\"\\n\")[line - 1];\n super(`${effect}: ${log.trim()}${quoted ? `\\n ${line} | ${quoted.trim()}` : \"\"}`);\n this.name = \"ShaderCompileError\";\n }\n}\n\n/**\n * The one shader idiom that quietly breaks reproducibility.\n *\n * `fract(sin(dot(p, k)) * large)` is the hash in every shader tutorial, and it\n * produces different noise on different backends because sin() of a large\n * argument is implementation defined. A video written with it renders one way\n * on a laptop and another in CI, with nothing to say why. The prelude ships a\n * portable `hash(vec2)`, so this is refused rather than warned about.\n */\nexport const assertPortableShader = (name: string, fragmentShader: string) => {\n if (/fract\\s*\\(\\s*sin\\s*\\(/.test(fragmentShader)) {\n throw new Error(\n `${name}: fract(sin(...)) is not reproducible. sin() of a large argument is implementation defined, so this renders differently on different graphics backends. Use the prelude's hash(vec2), which is exact everywhere.`,\n );\n }\n};\n\nconst compile = (gl: WebGL2RenderingContext, type: number, source: string, effect: string) => {\n const shader = gl.createShader(type);\n if (!shader) throw new Error(`${effect}: the browser refused to create a shader.`);\n gl.shaderSource(shader, source);\n gl.compileShader(shader);\n if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n const log = gl.getShaderInfoLog(shader) ?? \"compilation failed\";\n gl.deleteShader(shader);\n throw new ShaderCompileError(effect, log, source);\n }\n return shader;\n};\n\ntype Pass = {\n program: WebGLProgram;\n locations: Map<string, WebGLUniformLocation | null>;\n definition: ShaderEffectDefinition;\n};\n\n/** A texture and the framebuffer that draws into it. */\ntype Target = {texture: WebGLTexture; framebuffer: WebGLFramebuffer};\n\nconst createTarget = (gl: WebGL2RenderingContext, width: number, height: number): Target => {\n const texture = gl.createTexture()!;\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);\n // Clamped and linear: an effect that reaches past an edge should find the\n // edge repeated rather than the opposite side of the picture.\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);\n\n const framebuffer = gl.createFramebuffer()!;\n gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);\n gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);\n gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n return {texture, framebuffer};\n};\n\nexport type PipelineClock = {frame: number; fps: number; durationInFrames: number};\n\n/**\n * The compiled chain for one surface. Built once, painted every frame, and\n * disposed when the surface unmounts.\n */\nexport class EffectPipeline {\n private readonly gl: WebGL2RenderingContext;\n private readonly passes: Pass[] = [];\n private readonly sourceTexture: WebGLTexture;\n private targets: [Target, Target] | null = null;\n private width = 0;\n private height = 0;\n\n constructor(canvas: HTMLCanvasElement, effects: readonly EffectInstance[]) {\n const gl = canvas.getContext(\"webgl2\", {\n // The buffer is read back by odori test and by the screenshot, and a\n // composited buffer is cleared unless this is asked for.\n preserveDrawingBuffer: true,\n premultipliedAlpha: false,\n antialias: false,\n });\n if (!gl) {\n throw new Error(\n \"WebGL2 is unavailable. Studio needs hardware or software GL; the render worker enables ANGLE's software backend itself.\",\n );\n }\n this.gl = gl;\n this.sourceTexture = gl.createTexture()!;\n gl.bindTexture(gl.TEXTURE_2D, this.sourceTexture);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);\n\n const vertex = compile(gl, gl.VERTEX_SHADER, VERTEX_SHADER, \"odori\");\n for (const {definition} of effects) {\n const source = `${SHADER_PRELUDE}${definition.fragmentShader}`;\n const program = gl.createProgram()!;\n gl.attachShader(program, vertex);\n gl.attachShader(program, compile(gl, gl.FRAGMENT_SHADER, source, definition.name));\n gl.linkProgram(program);\n if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {\n throw new Error(`${definition.name}: ${gl.getProgramInfoLog(program) ?? \"link failed\"}`);\n }\n const locations = new Map<string, WebGLUniformLocation | null>();\n for (const name of [\"source\", \"resolution\", \"frame\", \"fps\", \"seconds\", \"progress\", ...Object.keys(definition.uniforms)]) {\n locations.set(name, gl.getUniformLocation(program, name));\n }\n this.passes.push({program, locations, definition});\n }\n }\n\n /** The renderer as Studio reports it, for the capability panel. */\n get renderer(): string {\n const debug = this.gl.getExtension(\"WEBGL_debug_renderer_info\");\n return String(this.gl.getParameter(debug ? debug.UNMASKED_RENDERER_WEBGL : this.gl.RENDERER));\n }\n\n private resize(width: number, height: number) {\n if (this.width === width && this.height === height && this.targets) return;\n this.disposeTargets();\n this.width = width;\n this.height = height;\n // Only needed when more than one pass has to hand its output on.\n this.targets = this.passes.length > 1 ? [createTarget(this.gl, width, height), createTarget(this.gl, width, height)] : null;\n }\n\n private setUniforms(pass: Pass, effect: EffectInstance, clock: PipelineClock) {\n const {gl} = this;\n const at = (name: string) => pass.locations.get(name) ?? null;\n gl.uniform1i(at(\"source\"), 0);\n gl.uniform2f(at(\"resolution\"), this.width, this.height);\n gl.uniform1f(at(\"frame\"), clock.frame);\n gl.uniform1f(at(\"fps\"), clock.fps);\n gl.uniform1f(at(\"seconds\"), clock.frame / clock.fps);\n gl.uniform1f(at(\"progress\"), clock.frame / Math.max(1, clock.durationInFrames - 1));\n\n for (const [name, spec] of Object.entries(pass.definition.uniforms)) {\n const value = effect.values[name] ?? spec.defaultValue;\n const location = at(name);\n if (location === null) continue;\n const numbers = typeof value === \"number\" ? [value] : [...value];\n if (spec.size === 1) gl.uniform1f(location, numbers[0]);\n else if (spec.size === 2) gl.uniform2f(location, numbers[0], numbers[1]);\n else if (spec.size === 3) gl.uniform3f(location, numbers[0], numbers[1], numbers[2]);\n else gl.uniform4f(location, numbers[0], numbers[1], numbers[2], numbers[3]);\n }\n }\n\n /** Upload the captured picture and run every pass over it. */\n paint(source: TexImageSource, effects: readonly EffectInstance[], clock: PipelineClock) {\n const {gl} = this;\n const canvas = gl.canvas as HTMLCanvasElement;\n this.resize(canvas.width, canvas.height);\n gl.viewport(0, 0, this.width, this.height);\n\n // Uploaded once per frame however many passes read it.\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, this.sourceTexture);\n gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, source);\n\n let input = this.sourceTexture;\n this.passes.forEach((pass, index) => {\n const last = index === this.passes.length - 1;\n const target = last || !this.targets ? null : this.targets[index % 2];\n gl.bindFramebuffer(gl.FRAMEBUFFER, target ? target.framebuffer : null);\n gl.useProgram(pass.program);\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, input);\n this.setUniforms(pass, effects[index], clock);\n gl.drawArrays(gl.TRIANGLES, 0, 3);\n if (target) input = target.texture;\n });\n\n gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n // The screenshot must not see a queued frame.\n gl.finish();\n }\n\n private disposeTargets() {\n if (!this.targets) return;\n for (const target of this.targets) {\n this.gl.deleteFramebuffer(target.framebuffer);\n this.gl.deleteTexture(target.texture);\n }\n this.targets = null;\n }\n\n dispose() {\n this.disposeTargets();\n this.gl.deleteTexture(this.sourceTexture);\n for (const pass of this.passes) this.gl.deleteProgram(pass.program);\n this.passes.length = 0;\n }\n}\n","\"use client\";\n\nimport {useContext, useEffect, useLayoutEffect, useRef, useState, type ReactNode} from \"react\";\nimport {SceneContext, useFrame, useReadiness, useVideo} from \"../context\";\nimport {captureElement, captureMethod, type CaptureMethod} from \"./capture\";\nimport {EffectPipeline, ShaderCompileError, type EffectInstance} from \"./shader\";\n\n/** `useLayoutEffect` warns during server rendering, where there is no DOM. */\nconst useIsomorphicLayoutEffect = typeof window === \"undefined\" ? useEffect : useLayoutEffect;\n\nexport type SurfaceClock = {\n frame: number;\n fps: number;\n seconds: number;\n /** 0 to 1 across the composition. */\n progress: number;\n durationInFrames: number;\n width: number;\n height: number;\n};\n\nexport type HtmlInCanvasPaint<Renderer> = (\n args: SurfaceClock & {renderer: Renderer; source: CanvasImageSource; canvas: HTMLCanvasElement},\n) => void;\n\nexport type HtmlInCanvasInit<Renderer> = (args: {canvas: HTMLCanvasElement; width: number; height: number}) => Renderer;\n\n/**\n * What Studio shows about a surface, so a difference between preview and\n * export is diagnosable without opening devtools.\n */\nexport type SurfaceCapabilities = {\n capture: CaptureMethod;\n backend: \"canvas-2d\" | \"webgl2\";\n renderer?: string;\n error?: string;\n};\n\nconst hidden: React.CSSProperties = {\n // Laid out at full size and painted, because an element with `display:none`\n // has no computed style worth copying, but kept off screen and out of the\n // way of hit testing.\n left: \"-99999px\",\n pointerEvents: \"none\",\n position: \"fixed\",\n top: 0,\n};\n\n/**\n * Capture a React subtree once per frame and hand the picture to a painter.\n *\n * The children are ordinary React, laid out by the browser, which is the whole\n * point: typography and product UI stay in the language they are written in\n * and only the compositing is different. They are rendered off screen rather\n * than into the canvas, captured, and then drawn.\n *\n * Determinism is the contract. The frame is held from before the capture\n * starts until after the paint has finished, so an export never screenshots a\n * surface that is halfway through, and every value a painter is given comes\n * from the frame rather than from a clock.\n */\nexport const HtmlInCanvas = <Renderer,>({\n children,\n width,\n height,\n onInit,\n onPaint,\n onDispose,\n onCapabilities,\n}: {\n children: ReactNode;\n /** Defaults to the composition's own size. */\n width?: number;\n height?: number;\n onInit?: HtmlInCanvasInit<Renderer>;\n onPaint?: HtmlInCanvasPaint<Renderer>;\n onDispose?: (renderer: Renderer) => void;\n onCapabilities?: (capabilities: SurfaceCapabilities) => void;\n}) => {\n const video = useVideo();\n const frame = useFrame();\n const readiness = useReadiness();\n /*\n * Progress is through the scene, not through the video.\n *\n * A component placed in a scene means its motion to finish when that scene\n * does. Measuring against the whole cut instead made a three second reveal\n * inside a twelve second video arrive already over, which is not a setting\n * anybody would choose. Read from context rather than useScene() so a\n * surface used outside a scene still works, falling back to the video.\n */\n const scene = useContext(SceneContext);\n const surfaceWidth = width ?? video.width;\n const surfaceHeight = height ?? video.height;\n\n const source = useRef<HTMLDivElement>(null);\n const canvas = useRef<HTMLCanvasElement>(null);\n const renderer = useRef<Renderer | null>(null);\n const [failure, setFailure] = useState<string | null>(null);\n\n /* Kept in refs so a new inline callback does not re-run the paint effect,\n which would repaint on every render rather than every frame. */\n const latest = useRef({onInit, onPaint, onDispose, onCapabilities});\n latest.current = {onInit, onPaint, onDispose, onCapabilities};\n\n useIsomorphicLayoutEffect(() => {\n const element = canvas.current;\n if (!element || !latest.current.onInit) return undefined;\n try {\n renderer.current = latest.current.onInit({canvas: element, width: surfaceWidth, height: surfaceHeight});\n } catch (error) {\n setFailure(error instanceof Error ? error.message : String(error));\n }\n return () => {\n if (renderer.current !== null) latest.current.onDispose?.(renderer.current);\n renderer.current = null;\n };\n }, [surfaceWidth, surfaceHeight]);\n\n useIsomorphicLayoutEffect(() => {\n const host = source.current;\n const element = canvas.current;\n if (!host || !element) return undefined;\n\n /*\n * A composition is mounted more than once.\n *\n * The runtime renders it a second time inside its audio pass, hidden, to\n * collect every cue without seeking the timeline. That copy is real React\n * with real refs, so this effect runs there too, and it held the frame\n * while trying to photograph an element with no layout. The hold never\n * came back and the export waited for a readiness that could not arrive.\n *\n * offsetWidth is layout, not paint, so it is zero exactly when there is\n * nothing to capture and unaffected by the scale Studio fits the stage\n * with. No layout, no picture, no hold.\n */\n if (host.offsetWidth === 0 || host.offsetHeight === 0) return undefined;\n\n const release = readiness.hold();\n let cancelled = false;\n\n void (async () => {\n try {\n const picture = await captureElement(host, {width: surfaceWidth, height: surfaceHeight});\n if (cancelled) return;\n const paint = latest.current.onPaint;\n const clock: SurfaceClock = {\n frame,\n fps: video.fps,\n seconds: frame / video.fps,\n progress: scene\n ? Math.min(1, Math.max(0, (frame - scene.start) / Math.max(1, scene.durationInFrames - 1)))\n : frame / Math.max(1, video.durationInFrames - 1),\n durationInFrames: scene ? scene.durationInFrames : video.durationInFrames,\n width: surfaceWidth,\n height: surfaceHeight,\n };\n if (paint) {\n paint({...clock, renderer: renderer.current as Renderer, source: picture, canvas: element});\n } else {\n // No painter: the surface is just a deterministic rasterization.\n const context = element.getContext(\"2d\");\n context?.clearRect(0, 0, element.width, element.height);\n context?.drawImage(picture, 0, 0, element.width, element.height);\n }\n } catch (error) {\n if (!cancelled) setFailure(error instanceof Error ? error.message : String(error));\n } finally {\n release();\n }\n })();\n\n return () => {\n cancelled = true;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [frame, surfaceWidth, surfaceHeight]);\n\n useEffect(() => {\n latest.current.onCapabilities?.({\n capture: captureMethod(),\n backend: latest.current.onInit ? \"webgl2\" : \"canvas-2d\",\n ...(failure ? {error: failure} : {}),\n });\n }, [failure]);\n\n return (\n <>\n {/* The subtree being captured. Off screen, not hidden: a display:none\n element has no layout and nothing to photograph. */}\n <div\n ref={source}\n /* Marked so the checks know this is the thing being photographed and\n not part of the picture. Without it, every word in the subtree is\n reported as escaping the canvas, which it does, on purpose. */\n data-odori-capture=\"\"\n style={{...hidden, height: surfaceHeight, width: surfaceWidth}}\n aria-hidden=\"true\"\n >\n {children}\n </div>\n <canvas\n ref={canvas}\n width={surfaceWidth}\n height={surfaceHeight}\n style={{display: \"block\", height: \"100%\", width: \"100%\"}}\n />\n {failure ? (\n <pre\n style={{\n background: \"#1a0b0b\",\n color: \"#ff9d9d\",\n font: \"13px ui-monospace, monospace\",\n inset: 0,\n margin: 0,\n padding: 24,\n position: \"absolute\",\n whiteSpace: \"pre-wrap\",\n }}\n >\n {failure}\n </pre>\n ) : null}\n </>\n );\n};\n\n/**\n * The same capture, with a chain of shaders over it.\n *\n * One surface runs every effect in a single pipeline. Nesting two surfaces\n * would capture and upload the DOM twice for one picture, which is why the\n * composition is a list here rather than something to wrap repeatedly.\n */\nexport const EffectSurface = ({\n children,\n effects,\n width,\n height,\n onCapabilities,\n}: {\n children: ReactNode;\n effects: readonly EffectInstance[];\n width?: number;\n height?: number;\n onCapabilities?: (capabilities: SurfaceCapabilities) => void;\n}) => {\n const pipeline = useRef<EffectPipeline | null>(null);\n const list = useRef(effects);\n list.current = effects;\n\n return (\n <HtmlInCanvas<EffectPipeline | null>\n width={width}\n height={height}\n onInit={({canvas}) => {\n try {\n pipeline.current = new EffectPipeline(canvas, list.current);\n } catch (error) {\n pipeline.current = null;\n throw error instanceof ShaderCompileError ? error : new Error(String(error));\n }\n return pipeline.current;\n }}\n onPaint={({renderer, source, frame, fps, durationInFrames}) => {\n if (!renderer) return;\n renderer.paint(source as TexImageSource, list.current, {frame, fps, durationInFrames});\n }}\n onDispose={(renderer) => renderer?.dispose()}\n onCapabilities={onCapabilities}\n >\n {children}\n </HtmlInCanvas>\n );\n};\n","import {defineShaderEffect, numberUniform, vec2Uniform} from \"./shader\";\n\n/**\n * The effects Odori ships, written the way a project would write its own.\n *\n * Each is one fragment shader against `SHADER_PRELUDE`, so `tex(uv)` reads\n * the captured picture and `resolution`, `frame`, `fps`, `seconds` and\n * `progress` are already in scope. Nothing here reaches for a clock, and\n * nothing carries state between frames, which is what lets a worker render\n * frame 200 without having rendered frame 199.\n */\n\n/** Pull the picture out through a lens, or push it in. */\nexport const barrelDistortion = defineShaderEffect({\n name: \"barrel-distortion\",\n uniforms: {\n amount: numberUniform(0.3),\n radius: numberUniform(0.6),\n centre: vec2Uniform([0.5, 0.5]),\n },\n fragmentShader: `\nuniform float amount;\nuniform float radius;\nuniform vec2 centre;\nvoid main() {\n vec2 uv = gl_FragCoord.xy / resolution;\n // Corrected for the frame's shape, or a wide composition bulges into an\n // oval instead of a circle.\n vec2 aspect = vec2(resolution.x / resolution.y, 1.0);\n vec2 offset = (uv - centre) * aspect;\n float distance = length(offset) / max(radius, 0.0001);\n float scale = 1.0 + amount * (1.0 - clamp(distance, 0.0, 1.0));\n odoriColour = tex(centre + offset / scale / aspect);\n}`,\n});\n\n/** Separate the channels, the way a cheap lens does at its edges. */\nexport const rgbSplit = defineShaderEffect({\n name: \"rgb-split\",\n uniforms: {amount: numberUniform(4.0), angle: numberUniform(0.0)},\n fragmentShader: `\nuniform float amount;\nuniform float angle;\nvoid main() {\n vec2 uv = gl_FragCoord.xy / resolution;\n vec2 shift = vec2(cos(angle), sin(angle)) * amount / resolution;\n odoriColour = vec4(tex(uv + shift).r, tex(uv).g, tex(uv - shift).b, tex(uv).a);\n}`,\n});\n\n/** Horizontal lines, restrained enough to read as a screen and not a costume. */\nexport const scanlines = defineShaderEffect({\n name: \"scanlines\",\n uniforms: {opacity: numberUniform(0.08), spacing: numberUniform(3.0)},\n fragmentShader: `\nuniform float opacity;\nuniform float spacing;\nvoid main() {\n vec2 uv = gl_FragCoord.xy / resolution;\n float line = step(0.5, fract(gl_FragCoord.y / max(spacing, 1.0)));\n odoriColour = tex(uv) * (1.0 - opacity * line);\n}`,\n});\n\n/** Quantise to blocks. Sizes in output pixels, so it reads the same at any scale. */\nexport const pixelate = defineShaderEffect({\n name: \"pixelate\",\n uniforms: {size: numberUniform(8.0)},\n fragmentShader: `\nuniform float size;\nvoid main() {\n float block = max(size, 1.0);\n vec2 snapped = (floor(gl_FragCoord.xy / block) + 0.5) * block;\n odoriColour = tex(snapped / resolution);\n}`,\n});\n\n/**\n * Grain. Seeded from the frame rather than from a random number, so the same\n * frame has the same grain every time it is rendered.\n */\nexport const filmGrain = defineShaderEffect({\n name: \"film-grain\",\n uniforms: {amount: numberUniform(0.06)},\n fragmentShader: `\nuniform float amount;\nvoid main() {\n vec2 uv = gl_FragCoord.xy / resolution;\n float noise = hash(gl_FragCoord.xy + frame) - 0.5;\n odoriColour = tex(uv) + vec4(vec3(noise * amount), 0.0);\n}`,\n});\n\n/** Magnify a circle of the picture, like a loupe held over it. */\nexport const magnify = defineShaderEffect({\n name: \"magnify\",\n uniforms: {\n amount: numberUniform(1.6),\n radius: numberUniform(0.22),\n centre: vec2Uniform([0.5, 0.5]),\n softness: numberUniform(0.06),\n },\n fragmentShader: `\nuniform float amount;\nuniform float radius;\nuniform vec2 centre;\nuniform float softness;\nvoid main() {\n vec2 uv = gl_FragCoord.xy / resolution;\n vec2 aspect = vec2(resolution.x / resolution.y, 1.0);\n vec2 offset = (uv - centre) * aspect;\n float inside = 1.0 - smoothstep(radius - softness, radius + softness, length(offset));\n float scale = mix(1.0, max(amount, 0.0001), inside);\n odoriColour = tex(centre + offset / scale / aspect);\n}`,\n});\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "odori",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "An independent React video framework with a first-class videos source root, deterministic frame runtime, timeline compiler, player, and render manifest.",
|
|
6
6
|
"license": "Apache-2.0",
|