checks.ts
examples/fxedit/checks.ts · raw · open the example · all examples · site home
Exports: runGraphfiniteVALUE_CHECKS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
// GPU checks of fx graphs on the current backend: one known splat through a
// graph's modifier in a fragment
// pass (GpuArrayWriter, so both backends), stored as an ext splat and read
// back, plus graphs with known results.
import type { WebGPURenderer } from "three/webgpu";
import { GpuArrays, GpuArrayWriter, GpuSplats, Splats, TSL, type Splat } from "@sparkjsdev/radix";
import { buildGraphModifier, type Graph } from "./fx/index.js";
const { float, uint, vec3, vec4 } = TSL;
const SOURCE = { center: [0.1, 0.2, 0.3], scales: [0.02, 0.03, 0.04], rgb: [0.5, 0.25, 0.75], opacity: 0.8 };
const SENTINEL = 0xdeadbeef;
// SOURCE through `graph`; undefined if the pass did not run (the arrays
// still hold the sentinel)
export async function runGraph(renderer: WebGPURenderer, graph: Graph): Promise<Splat | undefined> {
const { modifier } = buildGraphModifier(graph);
const init = Splats.make(1);
init.bundles.extSplats.channels.extA.data.fill(SENTINEL);
init.bundles.extSplats.channels.extB.data.fill(SENTINEL);
const arrays = GpuArrays.fromData(renderer, init);
const writer = new GpuArrayWriter({
outputs: { extSplats: Splats.EXT },
write: (_, out) => {
const splat = GpuSplats.Splat(vec3(...SOURCE.center), vec3(...SOURCE.scales), vec4(0, 0, 0, 1), vec3(...SOURCE.rgb), float(SOURCE.opacity), uint(7));
GpuSplats.storeExt(out.extSplats, modifier(splat));
},
});
try {
arrays.write(writer, { at: 0, count: 1 });
const data = await arrays.read({ count: 1 });
const { extA, extB } = data.bundles.extSplats.channels;
if ([...extA.data.slice(0, 4), ...extB.data.slice(0, 4)].every((word) => word === SENTINEL)) {
return undefined;
}
return Splats.load(Splats.get(data).bundle, 0, Splats.makeSplat());
} finally {
writer.dispose();
arrays.dispose();
}
}
export const finite = (s: Splat) => [s.center.x, s.center.y, s.center.z, s.scales.x, s.scales.y, s.scales.z, s.rgb.r, s.rgb.g, s.rgb.b, s.opacity].every(Number.isFinite);
const near = (a: number, b: number, tolerance: number) => Math.abs(a - b) <= tolerance;
// Graphs with known results: [name, graph, check]
export const VALUE_CHECKS: [string, Graph, (s: Splat) => string | undefined][] = [
["pass-through (no writes)", {
version: 1, nodes: { out: { type: "splat.write" } }, links: [],
}, (s) => (near(s.center.x, 0.1, 1e-5) && near(s.rgb.b, 0.75, 0.01) && near(s.opacity, 0.8, 0.01) ? undefined : "splat changed")],
["offset from input.vec3", {
version: 1, nodes: { v: { type: "input.vec3", inputs: { value: [0.5, 0, -0.25] } }, out: { type: "splat.write" } },
links: [{ from: "v.value", to: "out.offset" }],
}, (s) => (near(s.center.x, 0.6, 1e-5) && near(s.center.z, 0.05, 1e-5) ? undefined : `center ${s.center.x}, ${s.center.y}, ${s.center.z}`)],
// splat.write moves the center before it writes rgb: rgb must still see the old center
["reads see the splat before writes", {
version: 1, nodes: { c: { type: "input.center" }, out: { type: "splat.write" } },
links: [{ from: "c.value", to: "out.offset" }, { from: "c.value", to: "out.rgb" }],
}, (s) => (near(s.center.y, 0.4, 1e-5) && near(s.rgb.g, 0.2, 0.01) ? undefined : `center y ${s.center.y}, rgb g ${s.rgb.g}`)],
["tint", {
version: 1,
nodes: { rgb: { type: "input.rgb" }, tint: { type: "color.tint", inputs: { color: [0, 1, 0], amount: 0.5 } }, out: { type: "splat.write" } },
links: [{ from: "rgb.value", to: "tint.rgb" }, { from: "tint.value", to: "out.rgb" }],
}, (s) => (near(s.rgb.r, 0.25, 0.01) && near(s.rgb.g, 0.625, 0.01) ? undefined : `rgb ${s.rgb.r}, ${s.rgb.g}, ${s.rgb.b}`)],
["opacity scale from input.float", {
version: 1, nodes: { f: { type: "input.float", inputs: { value: 0.5 } }, out: { type: "splat.write" } },
links: [{ from: "f.value", to: "out.opacity" }],
}, (s) => (near(s.opacity, 0.4, 0.01) ? undefined : `opacity ${s.opacity}`)],
["scale", {
version: 1, nodes: { out: { type: "splat.write", inputs: { scale: 2 } } }, links: [],
}, (s) => (near(s.scales.z, 0.08, 0.002) ? undefined : `scale z ${s.scales.z}`)],
["keep false hides the splat", {
version: 1, nodes: { out: { type: "splat.write", inputs: { keep: false } } }, links: [],
}, (s) => (s.opacity === 0 ? undefined : `opacity ${s.opacity}`)],
];