Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions registry/blocks/flowing-gradient/flowing-gradient.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<style>
[data-composition-id="flowing-gradient"] canvas[data-shader] {
position: absolute;
inset: 0;
width: 1920px;
height: 1080px;
display: block;
background: #000;
}
</style>
</head>
<body>
<div
data-composition-id="flowing-gradient"
data-start="0"
data-duration="2"
data-width="1920"
data-height="1080"
data-requires-webgpu
data-no-timeline
style="position: relative; width: 1920px; height: 1080px; overflow: hidden; background: #000"
>
<canvas data-shader="FlowingGradient" width="1920" height="1080"></canvas>
</div>
<script src="lib/shaders.iife.js"></script>
</body>
</html>
314 changes: 314 additions & 0 deletions registry/blocks/flowing-gradient/lib/shaders.THIRD-PARTY-LICENSES.txt

Large diffs are not rendered by default.

266 changes: 266 additions & 0 deletions registry/blocks/flowing-gradient/lib/shaders.iife.js

Large diffs are not rendered by default.

34 changes: 34 additions & 0 deletions registry/blocks/flowing-gradient/registry-item.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
"name": "flowing-gradient",
"type": "hyperframes:block",
"title": "Flowing Gradient",
"description": "Bands of orange and violet flow across the frame in a smooth gradient. A WebGPU shader from the Shaders library (shaders.com, MIT).",
"tags": ["webgpu", "webgpu-shader", "background", "gradient", "color", "flow"],
"license": "Apache-2.0 AND MIT",
"dimensions": {
"width": 1920,
"height": 1080
},
"duration": 2,
"files": [
{
"path": "flowing-gradient.html",
"target": "compositions/flowing-gradient.html",
"type": "hyperframes:composition"
},
{
"path": "lib/shaders.iife.js",
"target": "compositions/lib/shaders.iife.js",
"type": "hyperframes:asset"
},
{
"path": "lib/shaders.THIRD-PARTY-LICENSES.txt",
"target": "compositions/lib/shaders.THIRD-PARTY-LICENSES.txt",
"type": "hyperframes:asset"
}
],
"preview": {
"video": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/flowing-gradient.mp4"
}
}
31 changes: 31 additions & 0 deletions registry/blocks/godrays/godrays.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<style>
[data-composition-id="godrays"] canvas[data-shader] {
position: absolute;
inset: 0;
width: 1920px;
height: 1080px;
display: block;
background: #000;
}
</style>
</head>
<body>
<div
data-composition-id="godrays"
data-start="0"
data-duration="2"
data-width="1920"
data-height="1080"
data-requires-webgpu
data-no-timeline
style="position: relative; width: 1920px; height: 1080px; overflow: hidden; background: #000"
>
<canvas data-shader="Godrays" width="1920" height="1080"></canvas>
</div>
<script src="lib/shaders.iife.js"></script>
</body>
</html>
314 changes: 314 additions & 0 deletions registry/blocks/godrays/lib/shaders.THIRD-PARTY-LICENSES.txt

Large diffs are not rendered by default.

266 changes: 266 additions & 0 deletions registry/blocks/godrays/lib/shaders.iife.js

Large diffs are not rendered by default.

34 changes: 34 additions & 0 deletions registry/blocks/godrays/registry-item.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
"name": "godrays",
"type": "hyperframes:block",
"title": "Godrays",
"description": "Rays of light fan out from a corner and drift across a deep blue field. A WebGPU shader from the Shaders library (shaders.com, MIT).",
"tags": ["webgpu", "webgpu-shader", "background", "light", "rays"],
"license": "Apache-2.0 AND MIT",
"dimensions": {
"width": 1920,
"height": 1080
},
"duration": 2,
"files": [
{
"path": "godrays.html",
"target": "compositions/godrays.html",
"type": "hyperframes:composition"
},
{
"path": "lib/shaders.iife.js",
"target": "compositions/lib/shaders.iife.js",
"type": "hyperframes:asset"
},
{
"path": "lib/shaders.THIRD-PARTY-LICENSES.txt",
"target": "compositions/lib/shaders.THIRD-PARTY-LICENSES.txt",
"type": "hyperframes:asset"
}
],
"preview": {
"video": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/godrays.mp4"
}
}
20 changes: 20 additions & 0 deletions registry/blocks/godrays/source/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Source for lib/shaders.iife.js

One bundle drives all six shader blocks (`flowing-gradient`, `godrays`, `liquid-metal`, `marble`,
`mesh-gradient`, `nebula`). Each installs it into the project's shared compositions/lib folder, and the canvas's
`data-shader` attribute picks the shader.

- `driver.js` renders the canvas from HyperFrames time: on every `hf-seek` it steps the library's
clock to that exact time, so preview and render draw the same frame.
- `build.mjs` bundles the driver with `shaders@4.0.0` and only these six shaders, then copies the
result into each block's `lib/`.

## Build

```sh
npm ci
npm run build
```

`package-lock.json` pins every bundled package; their licenses are in
`../lib/shaders.THIRD-PARTY-LICENSES.txt`.
50 changes: 50 additions & 0 deletions registry/blocks/godrays/source/build.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Builds lib/shaders.iife.js, shared by the six shader blocks: the driver plus only their six shaders. shaders/core's
// index imports every shader for side effects and the registry for media sizing and presets, which these never use.
import { build } from "esbuild";
import { copyFile, readFile } from "node:fs/promises";

const SHADERS = ["FlowingGradient", "Godrays", "LiquidMetal", "Marble", "MeshGradient", "Nebula"];
const BLOCKS = ["flowing-gradient", "godrays", "liquid-metal", "marble", "mesh-gradient", "nebula"];

const onlyTheseShaders = {
name: "only-these-shaders",
setup(b) {
b.onLoad({ filter: /node_modules\/shaders\/dist\/core\/index\.js$/ }, async ({ path }) => {
let src = await readFile(path, "utf8");
const before = src.length;
src = src.replace(/^import "\.\/[A-Z][\w-]*\.js";\n/gm, "");
src = src.replace(
/import \{ n as getShaderByName, t as getAllShaders \} from "\.\/shaderRegistry-[\w-]+\.js";/,
`${SHADERS.map((n) => `import { componentDefinition as ${n} } from "shaders/core/${n}";`).join("\n")}
const __defs = [${SHADERS.join(", ")}].map((definition) => ({ definition }));
const getShaderByName = (n) => __defs.find((s) => s.definition.name === n);
const getAllShaders = () => __defs;`,
);
if (src.length === before)
throw new Error("shaders/core index.js changed shape; this build needs a look");
return { contents: src, loader: "js", resolveDir: path.replace(/\/[^/]+$/, "") };
});
},
};

const out = "../lib/shaders.iife.js";
await build({
entryPoints: ["driver.js"],
bundle: true,
minify: true,
format: "iife",
legalComments: "eof",
outfile: out,
plugins: [onlyTheseShaders],
logLevel: "error",
});
for (const block of BLOCKS.filter((b) => b !== "godrays")) {
await copyFile(out, `../../${block}/lib/shaders.iife.js`);
}
console.log(
"lib/shaders.iife.js",
(await readFile(out)).length,
"bytes, copied to",
BLOCKS.length - 1,
"blocks",
);
71 changes: 71 additions & 0 deletions registry/blocks/godrays/source/driver.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Drives the page's Shaders-library canvas (shaders@4.0.0, WebGPU) from HyperFrames time, with no clock of its own.
import {
shaderRendererGPU,
createGpuUniformsMap,
rootPassthrough,
resolveBoundingBox,
} from "shaders/core";
import { componentDefinition as FlowingGradient } from "shaders/core/FlowingGradient";
import { componentDefinition as Godrays } from "shaders/core/Godrays";
import { componentDefinition as LiquidMetal } from "shaders/core/LiquidMetal";
import { componentDefinition as Marble } from "shaders/core/Marble";
import { componentDefinition as MeshGradient } from "shaders/core/MeshGradient";
import { componentDefinition as Nebula } from "shaders/core/Nebula";

const SHADERS = { FlowingGradient, Godrays, LiquidMetal, Marble, MeshGradient, Nebula };

const canvas = document.querySelector("canvas[data-shader]");
const def = SHADERS[canvas.dataset.shader];
const renderer = shaderRendererGPU();
let chain = Promise.resolve();

// The library's clock only accumulates the deltas it is given, so each frame steps it to the time asked for. Read
// the clock rather than tracking it: initialize() already drew one frame and moved it by 16 ms.
function renderAt(t) {
chain = chain.then(() =>
renderer.renderSyntheticFrame(t - renderer.__testing.getFrameDiagnostics().globalElapsedTime),
);
return chain;
}

const ready = (async () => {
if (!def) throw new Error(`shaders: no shader named "${canvas.dataset.shader}" in this bundle`);
await renderer.initialize({ canvas, observeElement: false, colorSpace: "srgb" });
const reason = renderer.getFailureReason();
if (reason) throw new Error(`shaders: WebGPU unavailable (${reason})`);
// Synchronous from here to stopAnimation, so the library's own loop draws no frame in between.
renderer.registerNode("root", rootPassthrough.fragment, null, null, {}, rootPassthrough);
const props = JSON.parse(canvas.dataset.shaderProps || "{}");
const values = Object.fromEntries(
Object.entries(def.props).map(([name, prop]) => [
name,
name in props ? props[name] : prop.default,
]),
);
const at = {
blendMode: "normal",
renderOrder: 1,
id: "fx",
boundingBox: resolveBoundingBox(undefined),
};
renderer.registerNode(
"fx",
def.fragment,
"root",
at,
createGpuUniformsMap(def, values, "fx"),
def,
);
renderer.stopAnimation();
await renderAt(window.__hfTypegpuTime ?? 0);
})();

window.__hf = window.__hf || {};
window.__hf.buildReady = window.__hf.buildReady || {};
window.__hf.buildReady.shaders = ready;
ready.catch((err) => console.error(String(err)));

window.addEventListener("hf-seek", (e) => {
const done = ready.then(() => renderAt(e.detail.time));
if (typeof e.detail.waitUntil === "function") e.detail.waitUntil(done);
});
Loading
Loading