Rebuild core around render graph AST and shared memory
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:
@@ -0,0 +1,27 @@
|
||||
import {
|
||||
createGraphAst,
|
||||
reference,
|
||||
serializeGraphAst,
|
||||
} from "@yawn/render-graph-ast";
|
||||
|
||||
const expression = (id, inputs = {}) => ({
|
||||
id,
|
||||
state: "enabled",
|
||||
executor: { key: "and", version: 2 },
|
||||
parameters: {},
|
||||
inputs,
|
||||
});
|
||||
|
||||
/** Build one DAG whose shared output fans out to two consumers. */
|
||||
export function canonicalAstExample() {
|
||||
const ast = createGraphAst({
|
||||
id: "shared_dag",
|
||||
revision: 1,
|
||||
nodes: [
|
||||
expression("source"),
|
||||
expression("left", { inputs: [reference("source", "value")] }),
|
||||
expression("right", { inputs: [reference("source", "value")] }),
|
||||
],
|
||||
});
|
||||
return { ast, source: serializeGraphAst(ast) };
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { graphFromObject } from "@yawn/render-graph-js";
|
||||
|
||||
/** Author a graph with an ordinary JavaScript object and receive canonical AST. */
|
||||
export function jsoGraphExample() {
|
||||
return graphFromObject({
|
||||
id: "jso_graph",
|
||||
revision: 1,
|
||||
nodes: [
|
||||
{
|
||||
id: "mesh",
|
||||
state: "enabled",
|
||||
executor: { key: "mesh", version: 2 },
|
||||
parameters: {},
|
||||
inputs: {},
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { RenderGraph, ref } from "@yawn/render-graph-js";
|
||||
|
||||
/** Build the same sort of DAG with the small mutable authoring facade. */
|
||||
export function fluentGraphExample() {
|
||||
return new RenderGraph("fluent_graph", 1)
|
||||
.node("source", "and", { version: 2 })
|
||||
.node("consumer", "not", {
|
||||
version: 1,
|
||||
inputs: { operand: [ref("source", "value")] },
|
||||
})
|
||||
.ast();
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { defaultPipelines } from "@yawn/default-pipelines";
|
||||
import { adaptFxNodeSnapshot } from "@yawn/render-graph-fxnode";
|
||||
import { CATALOG_VERSION, GRAPH_ID } from "@yawn/render-graph-fxnode/catalog";
|
||||
|
||||
/** Export a minimal FXNode authoring document through the shared AST boundary. */
|
||||
export function fxNodeExportExample() {
|
||||
return adaptFxNodeSnapshot(
|
||||
{
|
||||
graphId: GRAPH_ID,
|
||||
catalogVersion: CATALOG_VERSION,
|
||||
nodes: [],
|
||||
links: [],
|
||||
metadata: {},
|
||||
version: 1,
|
||||
},
|
||||
1,
|
||||
{ pipelines: defaultPipelines },
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { defaultPipelines } from "@yawn/default-pipelines";
|
||||
import { RenderGraph } from "@yawn/render-graph-js";
|
||||
|
||||
/** Copy the optional package's external programs into a graph AST. */
|
||||
export function defaultPipelineExample() {
|
||||
const graph = new RenderGraph("default_programs", 1);
|
||||
for (const pipeline of defaultPipelines.render)
|
||||
graph.renderPipeline(pipeline);
|
||||
for (const pipeline of defaultPipelines.compute)
|
||||
graph.computePipeline(pipeline);
|
||||
return graph.ast();
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { RenderGraph } from "@yawn/render-graph-js";
|
||||
|
||||
export const simpleSceneShader = /* wgsl */ `
|
||||
@group(1) @binding(0) var<uniform> view_projection: mat4x4<f32>;
|
||||
struct Input {
|
||||
@location(0) position: vec3<f32>,
|
||||
@location(3) model_0: vec4<f32>,
|
||||
@location(4) model_1: vec4<f32>,
|
||||
@location(5) model_2: vec4<f32>,
|
||||
@location(6) model_3: vec4<f32>,
|
||||
}
|
||||
@vertex fn vertex_main(input: Input) -> @builtin(position) vec4<f32> {
|
||||
let model = mat4x4<f32>(input.model_0, input.model_1, input.model_2, input.model_3);
|
||||
return view_projection * model * vec4(input.position, 1.0);
|
||||
}
|
||||
@fragment fn fragment_main() -> @location(0) vec4<f32> {
|
||||
return vec4(0.2, 0.7, 1.0, 1.0);
|
||||
}`;
|
||||
|
||||
/** Supply scene WGSL and entry points as graph data, never as core source. */
|
||||
export function customRenderPipelineExample() {
|
||||
return new RenderGraph("custom_render_program", 1)
|
||||
.renderPipeline({
|
||||
name: "ground_plane",
|
||||
shader: simpleSceneShader,
|
||||
vertexEntry: "vertex_main",
|
||||
fragmentEntry: "fragment_main",
|
||||
doubleSided: false,
|
||||
})
|
||||
.ast();
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { RenderGraph } from "@yawn/render-graph-js";
|
||||
|
||||
export const initializeShader = /* wgsl */ `
|
||||
@compute @workgroup_size(8, 1, 1)
|
||||
fn initialize() {}
|
||||
`;
|
||||
|
||||
/** Declare a binding-free compute pass that runs before graph render passes. */
|
||||
export function computePipelineExample() {
|
||||
return new RenderGraph("compute_program", 1)
|
||||
.computePipeline({
|
||||
name: "initialize",
|
||||
shader: initializeShader,
|
||||
entry: "initialize",
|
||||
dispatch: [4, 1, 1],
|
||||
})
|
||||
.ast();
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { loadGraph } from "@yawn/render-graph-js";
|
||||
|
||||
/** Compile a complete AST/JSO and make its prepared loadout active. */
|
||||
export async function compileAndSwitch(core, graph) {
|
||||
const compiled = await loadGraph(core, graph);
|
||||
try {
|
||||
await core.switchCompiledGraph(compiled.compiledId);
|
||||
return compiled;
|
||||
} catch (error) {
|
||||
await core.dropCompiledGraph(compiled.compiledId).catch(() => {});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/** Return to clear-only mode before releasing an active compiled loadout. */
|
||||
export async function dropActiveGraph(core, compiled) {
|
||||
await core.switchToImmediate();
|
||||
await core.dropCompiledGraph(compiled.compiledId);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { GltfImporter } from "@yawn/gltf-import";
|
||||
import { MeshHandles } from "@yawn/mesh-handles";
|
||||
|
||||
/** Fetch a glTF URL in the import worker and wrap the resulting protocol handles. */
|
||||
export async function importGltf(core, url, options) {
|
||||
const importer = new GltfImporter(core);
|
||||
try {
|
||||
const imported = await importer.load(url, options);
|
||||
return new MeshHandles(core).fromImportedScene(imported);
|
||||
} finally {
|
||||
importer.dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
export const identityTransform = Object.freeze([
|
||||
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1,
|
||||
]);
|
||||
|
||||
/** Create a conventional instance object from an imported mesh handle. */
|
||||
export function createInstance(mesh, transform = identityTransform) {
|
||||
return mesh.createInstance(transform);
|
||||
}
|
||||
|
||||
/** Frequent mutations stay on the shared-memory path exposed by the handle addon. */
|
||||
export function updateInstance(instance, transform, typeWords) {
|
||||
instance.setTransform(transform);
|
||||
instance.setType(typeWords);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/** Allocate one SIMD-aligned velocity row for every live instance slot. */
|
||||
export function createVelocityColumn(core) {
|
||||
return core.allocateArray({
|
||||
name: "instance.velocity",
|
||||
domain: "instance",
|
||||
scalar: "f32",
|
||||
lanes: 4,
|
||||
});
|
||||
}
|
||||
|
||||
/** Mutate an existing row directly; no renderer command message is emitted. */
|
||||
export function setVelocity(column, instance, xyz) {
|
||||
column.write(instance.handle[0], [xyz[0], xyz[1], xyz[2], 0]);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/** Write a new model matrix through core's generation-guarded shared SOA column. */
|
||||
export function animateInstance(core, instance, transform) {
|
||||
core.setInstanceTransform(instance.handle, transform);
|
||||
}
|
||||
|
||||
/** Write the opaque 512-bit instance classification used by graph predicates. */
|
||||
export function classifyInstance(core, instance, words) {
|
||||
core.setInstanceType(instance.handle, words);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { MeshHandles } from "@yawn/mesh-handles";
|
||||
|
||||
/** Query the optional snapshot/BVH worker and receive wrapped instance handles. */
|
||||
export function pickNearest(core, origin, direction) {
|
||||
return new MeshHandles(core).pickRay(origin, direction, {
|
||||
maxDistance: 10_000,
|
||||
maxHits: 1,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { YawnCore } from "@yawn/core";
|
||||
|
||||
/** Connect from any worker using a MessagePort with the Worker-like transport API. */
|
||||
export async function connectFromWorker(port, options = {}) {
|
||||
const core = new YawnCore({
|
||||
worker: port,
|
||||
memory: options.memory,
|
||||
ringPtr: options.ringPtr,
|
||||
pickingWorkerFactory: options.pickingWorkerFactory,
|
||||
free: options.free,
|
||||
});
|
||||
await core.ready;
|
||||
return core;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { culling } from "../render-graph-studio/render-graph/presets.js";
|
||||
import { importGltf } from "./09-gltf-import-worker.js";
|
||||
import { compileAndSwitch, dropActiveGraph } from "./08-compile-and-switch.js";
|
||||
|
||||
/** Combine the complete JSO graph, shared glTF import, and mesh-handle facade. */
|
||||
export async function loadCompleteScene(core, gltfUrl) {
|
||||
const compiled = await compileAndSwitch(core, culling);
|
||||
try {
|
||||
const meshes = await importGltf(core, gltfUrl);
|
||||
return {
|
||||
compiled,
|
||||
meshes,
|
||||
dispose: () => dropActiveGraph(core, compiled),
|
||||
};
|
||||
} catch (error) {
|
||||
await dropActiveGraph(core, compiled).catch(() => {});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
# Yawn addon cookbook
|
||||
|
||||
These recipes deliberately avoid another application framework or renderer wrapper.
|
||||
Import the function you need and pass the same `YawnCore` instance to every addon.
|
||||
|
||||
| Recipe | Demonstrates |
|
||||
| --- | --- |
|
||||
| `01-canonical-ast.js` | A shared DAG output and canonical S-expression serialization |
|
||||
| `02-jso-graph.js` | Plain JavaScript object authoring |
|
||||
| `03-fluent-builder.js` | Fluent render-graph authoring |
|
||||
| `04-fxnode-export.js` | Exporting an FXNode snapshot to the canonical AST |
|
||||
| `05-default-pipelines.js` | Attaching optional external WGSL declarations |
|
||||
| `06-custom-render-pipeline.js` | Supplying a custom scene render shader |
|
||||
| `07-compute-pipeline.js` | Supplying a binding-free compute pass |
|
||||
| `08-compile-and-switch.js` | Compiling, activating, and safely cleaning up a graph |
|
||||
| `09-gltf-import-worker.js` | Fetching a glTF URL into shared memory from a worker |
|
||||
| `10-mesh-instances.js` | Creating and mutating conventional instance handles |
|
||||
| `11-custom-soa-column.js` | Allocating an instance-sized shared SOA column |
|
||||
| `12-direct-sab-animation.js` | Updating transforms through generation-guarded SAB writes |
|
||||
| `13-bvh-picking.js` | Ray picking through the mesh-handles addon |
|
||||
| `14-worker-to-worker.js` | Using core from another worker through a `MessagePort` |
|
||||
| `15-complete-scene.js` | Combining the graph, glTF, and mesh addons |
|
||||
|
||||
Recipes 1–7 isolate graph authoring concepts, so their ASTs are intentionally
|
||||
fragments rather than complete renderable loadouts. Recipe 15 uses the complete
|
||||
scene graph from `render-graph-studio` when an end-to-end example is needed.
|
||||
@@ -0,0 +1,15 @@
|
||||
export * from "./01-canonical-ast.js";
|
||||
export * from "./02-jso-graph.js";
|
||||
export * from "./03-fluent-builder.js";
|
||||
export * from "./04-fxnode-export.js";
|
||||
export * from "./05-default-pipelines.js";
|
||||
export * from "./06-custom-render-pipeline.js";
|
||||
export * from "./07-compute-pipeline.js";
|
||||
export * from "./08-compile-and-switch.js";
|
||||
export * from "./09-gltf-import-worker.js";
|
||||
export * from "./10-mesh-instances.js";
|
||||
export * from "./11-custom-soa-column.js";
|
||||
export * from "./12-direct-sab-animation.js";
|
||||
export * from "./13-bvh-picking.js";
|
||||
export * from "./14-worker-to-worker.js";
|
||||
export * from "./15-complete-scene.js";
|
||||
Reference in New Issue
Block a user