Strip core to render data and render graphs

Move glTF, picking, camera controls, and conventional handles into addons. Keep camera and material mutations in SIMD-aligned shared SOA rows and synchronize material updates directly into GPU buffers.

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 09:41:41 +00:00
co-authored by heaust
parent 0e44917f9e
commit 6bbf8039e4
67 changed files with 3152 additions and 3669 deletions
@@ -11,9 +11,3 @@ export async function compileAndSwitch(core, graph) {
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);
}
+2 -4
View File
@@ -1,8 +1,6 @@
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, {
export function pickNearest(meshHandles, origin, direction) {
return meshHandles.pickRay(origin, direction, {
maxDistance: 10_000,
maxHits: 1,
});
-1
View File
@@ -6,7 +6,6 @@ export async function connectFromWorker(port, options = {}) {
worker: port,
memory: options.memory,
ringPtr: options.ringPtr,
pickingWorkerFactory: options.pickingWorkerFactory,
free: options.free,
});
await core.ready;
+3 -12
View File
@@ -1,19 +1,10 @@
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";
import { compileAndSwitch } 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 meshes = await importGltf(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;
}
return { compiled, meshes };
}
+122
View File
@@ -0,0 +1,122 @@
const MIN_DISTANCE = 0.1;
const MAX_PITCH = Math.PI / 2 - 0.01;
// The camera is ordinary render data, not a core API. This example maps browser
// controls straight onto its packed SOA row without sending input messages.
export function installCameraRenderDataControls(core, canvas) {
const camera = core.array("camera.state");
const abort = new AbortController();
const options = { signal: abort.signal };
const write = (state) => camera.write(0, state);
canvas.addEventListener(
"pointerdown",
(event) => {
if (event.pointerType === "mouse" && (event.button === 1 || event.button === 2)) {
event.preventDefault();
canvas.setPointerCapture(event.pointerId);
}
},
options,
);
canvas.addEventListener(
"pointermove",
(event) => {
if (
event.pointerType !== "mouse" ||
!canvas.hasPointerCapture(event.pointerId) ||
(event.buttons & 6) === 0
) {
return;
}
event.preventDefault();
const state = camera.read(0);
const offset = [state[0] - state[4], state[1] - state[5], state[2] - state[6]];
const distance = Math.hypot(...offset);
if ((event.buttons & 4) !== 0) {
const yaw = Math.atan2(offset[0], offset[2]) + event.movementX * 0.005;
const pitch = Math.max(
-MAX_PITCH,
Math.min(
MAX_PITCH,
Math.asin(offset[1] / distance) + event.movementY * 0.005,
),
);
const horizontal = Math.cos(pitch) * distance;
state[0] = state[4] + Math.sin(yaw) * horizontal;
state[1] = state[5] + Math.sin(pitch) * distance;
state[2] = state[6] + Math.cos(yaw) * horizontal;
} else {
const forward = [
(state[4] - state[0]) / distance,
(state[5] - state[1]) / distance,
(state[6] - state[2]) / distance,
];
const right = [
forward[1] * state[10] - forward[2] * state[9],
forward[2] * state[8] - forward[0] * state[10],
forward[0] * state[9] - forward[1] * state[8],
];
const rightLength = Math.hypot(...right);
right.forEach((value, index) => (right[index] = value / rightLength));
const up = [
right[1] * forward[2] - right[2] * forward[1],
right[2] * forward[0] - right[0] * forward[2],
right[0] * forward[1] - right[1] * forward[0],
];
const units =
(2 * distance * Math.tan(state[12] * 0.5)) /
Math.max(1, canvas.clientHeight);
const translation = right.map(
(value, index) =>
-value * event.movementX * units + up[index] * event.movementY * units,
);
for (let index = 0; index < 3; index++) {
state[index] += translation[index];
state[index + 4] += translation[index];
}
}
write(state);
},
options,
);
for (const type of ["pointerup", "pointercancel"]) {
canvas.addEventListener(
type,
(event) => {
if (canvas.hasPointerCapture(event.pointerId)) {
canvas.releasePointerCapture(event.pointerId);
}
},
options,
);
}
canvas.addEventListener(
"wheel",
(event) => {
event.preventDefault();
const delta =
event.deltaMode === WheelEvent.DOM_DELTA_LINE
? event.deltaY * 16
: event.deltaMode === WheelEvent.DOM_DELTA_PAGE
? event.deltaY * canvas.clientHeight
: event.deltaY;
if (!Number.isFinite(delta) || delta === 0) return;
const state = camera.read(0);
const offset = [state[0] - state[4], state[1] - state[5], state[2] - state[6]];
const distance = Math.hypot(...offset);
const nextDistance = Math.max(
MIN_DISTANCE,
Math.min(state[15] * 0.95, distance * Math.exp(0.002 * delta)),
);
for (let index = 0; index < 3; index++) {
state[index] = state[index + 4] + offset[index] * (nextDistance / distance);
}
write(state);
},
{ ...options, passive: false },
);
canvas.addEventListener("contextmenu", (event) => event.preventDefault(), options);
return () => abort.abort();
}
@@ -0,0 +1,19 @@
import { CameraHandle, MaterialHandles, MeshHandles } from "@yawn/mesh-handles";
/** Wrap one import result in the conventional object API without hiding core. */
export function conventionalSceneHandles(core, imported) {
return {
camera: new CameraHandle(core),
meshes: new MeshHandles(core).fromImportedScene(imported),
materials: new MaterialHandles(core).fromImportedScene(imported),
};
}
/** Property assignments remain direct SAB writes; no camera/material message is sent. */
export function restyleScene({ camera, materials }) {
camera.lookAt([4, 3, 6], [0, 0, 0]);
if (materials[0]) {
materials[0].baseColor = [0.2, 0.55, 1, 1];
materials[0].roughness = 0.35;
}
}
+3 -1
View File
@@ -12,7 +12,7 @@ Import the function you need and pass the same `YawnCore` instance to every addo
| `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 |
| `08-compile-and-switch.js` | Compiling and transactionally activating 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 |
@@ -20,6 +20,8 @@ Import the function you need and pass the same `YawnCore` instance to every addo
| `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 |
| `16-camera-render-data.js` | Treating camera input as direct render-data SAB writes |
| `17-conventional-handles.js` | Conventional camera and material properties over SAB rows |
Recipes 17 isolate graph authoring concepts, so their ASTs are intentionally
fragments rather than complete renderable loadouts. Recipe 15 uses the complete
+2
View File
@@ -13,3 +13,5 @@ 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";
export * from "./16-camera-render-data.js";
export * from "./17-conventional-handles.js";