Add tutorial docs and interactive playgrounds

Amp-Thread-ID: https://ampcode.com/threads/T-01a01380-b478-77d0-84a0-102880a5c5ae
Co-authored-by: Heaust Azure <heaust.azure@gmail.com>
This commit is contained in:
Amp
2026-08-19 14:20:59 +00:00
co-authored by heaust
parent 6bbf8039e4
commit d34722d66d
57 changed files with 5063 additions and 767 deletions
+134
View File
@@ -0,0 +1,134 @@
import { YawnCore } from "@yawn/core";
import { GltfImporter } from "@yawn/gltf-import";
import {
CameraHandle,
MaterialHandles,
MeshHandles,
} from "@yawn/mesh-handles";
import { loadGraph, graphFromObject, RenderGraph, ref } from "@yawn/render-graph-js";
import {
createGraphAst,
reference,
serializeGraphAst,
} from "@yawn/render-graph-ast";
import { defaultPipelines } from "@yawn/default-pipelines";
import { loadDemoLoadout } from "../render-graph-studio/demo-loadouts.js";
import { culling } from "../render-graph-studio/render-graph/presets.js";
import { installCameraRenderDataControls } from "../shared/camera-controls.js";
import { createWorkerTransport } from "../shared/create-worker-transport.js";
const waitForFrame = (core, predicate, timeout = 30_000) =>
new Promise((resolve, reject) => {
const current = core.telemetry;
if (current && predicate(current)) {
resolve(current);
return;
}
const timer = setTimeout(() => {
core.removeEventListener("renderer-frame", frame);
reject(new Error("Renderer confirmation timed out"));
}, timeout);
const frame = (event) => {
if (!predicate(event.detail)) return;
clearTimeout(timer);
core.removeEventListener("renderer-frame", frame);
resolve(event.detail);
};
core.addEventListener("renderer-frame", frame);
});
export function rotationY(angle) {
const cosine = Math.cos(angle);
const sine = Math.sin(angle);
return [
cosine, 0, -sine, 0,
0, 1, 0, 0,
sine, 0, cosine, 0,
0, 0, 0, 1,
];
}
export function createPlaygroundRuntime(canvas, report) {
let activeScene;
const status = (message) => {
report(String(message));
};
async function createScene({ loadout = "cubes", graph = culling } = {}) {
activeScene?.dispose();
const core = new YawnCore(createWorkerTransport(canvas));
const handles = new MeshHandles(core);
const importer = new GltfImporter(core);
let stopControls = () => {};
try {
status("Starting render worker…");
await core.ready;
const compiled = await loadGraph(core, graph);
const targetRevision = (core.telemetry?.revision ?? 0) + 1;
const glb = await loadDemoLoadout(loadout);
const url = URL.createObjectURL(
new Blob([glb], { type: "model/gltf-binary" }),
);
let imported;
try {
imported = await importer.load(url);
} finally {
URL.revokeObjectURL(url);
}
const meshes = handles.fromImportedScene(imported);
const materials = new MaterialHandles(core).fromImportedScene(imported);
const camera = new CameraHandle(core);
stopControls = installCameraRenderDataControls(core, canvas);
await core.switchCompiledGraph(compiled.compiledId);
await waitForFrame(
core,
(frame) =>
frame.revision === targetRevision &&
frame.activeCompiledGraph === graph.id &&
frame.draws > 0 &&
frame.gpuError === false,
);
activeScene = {
core,
handles,
meshes,
materials,
camera,
compiled: { ...compiled, graphId: graph.id },
dispose() {
stopControls();
handles.dispose();
core.dispose();
activeScene = undefined;
},
};
return activeScene;
} catch (error) {
stopControls();
handles.dispose();
core.dispose();
throw error;
} finally {
importer.dispose();
}
}
return Object.freeze({
createScene,
status,
rotationY,
graphs: Object.freeze({ culling }),
packages: Object.freeze({
createGraphAst,
defaultPipelines,
graphFromObject,
reference,
ref,
RenderGraph,
serializeGraphAst,
}),
dispose() {
activeScene?.dispose();
},
});
}