Expand render graph playground editing
Amp-Thread-ID: https://ampcode.com/threads/T-01a033d4-0e44-708e-af0e-6166283416ff Co-authored-by: Heaust Azure <heaust.azure@gmail.com>
This commit is contained in:
@@ -5,7 +5,16 @@ import {
|
||||
renderGraphNodeGroups,
|
||||
renderGraphPresets,
|
||||
} from "../src/catalog";
|
||||
import { compileFxNodeRenderGraph, encodeRenderGraph } from "../src/compiler";
|
||||
import {
|
||||
buildFxNodeRenderGraph,
|
||||
compileFxNodeRenderGraph,
|
||||
encodeRenderGraph,
|
||||
} from "../src/compiler";
|
||||
import {
|
||||
isPhysicalRenderGraph,
|
||||
materializeRenderGraph,
|
||||
reconstructRenderGraph,
|
||||
} from "../src/physical";
|
||||
import { bindFxNodeHeadless } from "../../../vendor/fxnode/src/headless-runtime";
|
||||
import { compileFxNodeComposition } from "../../../vendor/fxnode/src/composition/compile";
|
||||
|
||||
@@ -115,13 +124,66 @@ const baseGraph = {
|
||||
};
|
||||
|
||||
test("catalog exposes grouped, materially rich render graph nodes", () => {
|
||||
assert.equal(renderGraphNodeGroups.length, 6);
|
||||
assert.ok(renderGraphNodeGroups.flatMap((group) => group.items).length >= 25);
|
||||
assert.equal(renderGraphNodeGroups.length, 9);
|
||||
assert.ok(renderGraphNodeGroups.flatMap((group) => group.items).length >= 32);
|
||||
for (const item of renderGraphNodeGroups.flatMap((group) => group.items))
|
||||
assert.ok(renderGraphComposition.nodes[item.typeId], item.typeId);
|
||||
assert.doesNotThrow(() => compileFxNodeComposition(renderGraphComposition));
|
||||
});
|
||||
|
||||
test("prepared intent expands into the complete physical render graph", () => {
|
||||
const standard = renderGraphPresets.find((preset) => preset.id === "beauty")!;
|
||||
const concrete = buildFxNodeRenderGraph(standard.layout, baseGraph);
|
||||
const layout = materializeRenderGraph(concrete, "beauty");
|
||||
assert.equal(isPhysicalRenderGraph(layout), true);
|
||||
assert.ok(layout.nodes.length >= 14);
|
||||
for (const typeId of [
|
||||
"yawn.rg.physical.frame-in",
|
||||
"yawn.rg.physical.frame-out",
|
||||
"yawn.rg.physical.buffer",
|
||||
"yawn.rg.physical.texture",
|
||||
"yawn.rg.physical.sampler",
|
||||
"yawn.rg.physical.render-pipeline",
|
||||
"yawn.rg.physical.render-pass",
|
||||
]) {
|
||||
assert.ok(
|
||||
layout.nodes.some((node) => node.typeId === typeId),
|
||||
typeId,
|
||||
);
|
||||
}
|
||||
assert.ok(
|
||||
layout.links.some((link) => link.extensions.relation === "pipeline"),
|
||||
);
|
||||
assert.ok(
|
||||
layout.links.some((link) => link.extensions.relation === "binding"),
|
||||
);
|
||||
assert.ok(layout.links.some((link) => link.extensions.relation === "color"));
|
||||
assert.ok(layout.links.some((link) => link.extensions.relation === "depth"));
|
||||
assert.ok(layout.links.some((link) => link.extensions.relation === "vertex"));
|
||||
assert.deepEqual(reconstructRenderGraph(layout), concrete);
|
||||
});
|
||||
|
||||
test("physical pipeline shader edits compile into the S-expression", () => {
|
||||
const concrete = buildFxNodeRenderGraph(
|
||||
renderGraphPresets[0]!.layout,
|
||||
baseGraph,
|
||||
);
|
||||
const layout = structuredClone(materializeRenderGraph(concrete, "beauty"));
|
||||
const pipeline = layout.nodes.find(
|
||||
(node) =>
|
||||
node.typeId === "yawn.rg.physical.render-pipeline" &&
|
||||
node.label.includes("forward-pbr-0"),
|
||||
)!;
|
||||
pipeline.parameters.code = {
|
||||
kind: "string",
|
||||
value:
|
||||
"@fragment fn edited_shader() -> @location(0) vec4<f32> { return vec4<f32>(0.25); }",
|
||||
};
|
||||
const serialized = compileFxNodeRenderGraph(layout, baseGraph);
|
||||
assert.match(serialized, /edited_shader/u);
|
||||
assert.doesNotMatch(serialized, /original pbr shader/u);
|
||||
});
|
||||
|
||||
test("Color Grade exposes Blender-style grading modes and migrates version 1 nodes", () => {
|
||||
const definition = renderGraphComposition.nodes["yawn.rg.post.color-grade"];
|
||||
assert.equal(definition.version, 2);
|
||||
@@ -200,24 +262,108 @@ test("wire encoder is stable and rejects unsupported numbers", () => {
|
||||
|
||||
test("normals loadout replaces scene forward shading without mutating the base", () => {
|
||||
const normals = renderGraphPresets.find((preset) => preset.id === "normals")!;
|
||||
const serialized = compileFxNodeRenderGraph(normals.layout, baseGraph);
|
||||
const graph = buildFxNodeRenderGraph(normals.layout, baseGraph) as any;
|
||||
const serialized = encodeRenderGraph(graph);
|
||||
assert.match(serialized, /scene-fxnode-normals/u);
|
||||
assert.match(serialized, /rg-normals-detailed/u);
|
||||
assert.match(serialized, /normalize\(input\.normal\) \* 0\.5 \+ 0\.5/u);
|
||||
assert.match(serialized, /camera-matrices/u);
|
||||
assert.deepEqual(
|
||||
graph.passes.map((pass: any) => pass.id),
|
||||
["forward-1--1"],
|
||||
);
|
||||
assert.deepEqual(graph.passes[0].color, [
|
||||
{ resource: "canvas", clear: [0, 0, 0, 1] },
|
||||
]);
|
||||
assert.deepEqual(
|
||||
graph.pipelines.render.map((pipeline: any) => pipeline.id),
|
||||
["rg-normals-detailed"],
|
||||
);
|
||||
assert.deepEqual(
|
||||
graph.resources.textures.map((texture: any) => texture.id),
|
||||
["depth"],
|
||||
);
|
||||
assert.deepEqual(graph.resources.samplers, []);
|
||||
assert.equal(baseGraph.passes[0].pipeline, "forward-pbr-0");
|
||||
assert.equal(baseGraph.pipelines.render.length, 2);
|
||||
});
|
||||
|
||||
test("debug loadouts remove detached clustered lighting work", () => {
|
||||
const graph = structuredClone(baseGraph) as any;
|
||||
graph.resources.buffers.push(
|
||||
{ id: "point-lights", array: "pointLights", usage: ["storage"] },
|
||||
{ id: "clusters", array: "clusters", usage: ["storage"] },
|
||||
);
|
||||
graph.pipelines.compute.push({
|
||||
id: "cluster-lights",
|
||||
code: "@compute @workgroup_size(1) fn main() {}",
|
||||
entry: "main",
|
||||
});
|
||||
graph.passes.unshift({
|
||||
id: "cluster-lights",
|
||||
type: "compute",
|
||||
pipeline: "cluster-lights",
|
||||
bindings: [
|
||||
{ group: 0, binding: 0, resource: "point-lights" },
|
||||
{ group: 0, binding: 1, resource: "clusters" },
|
||||
],
|
||||
dispatch: [1, 1, 1],
|
||||
});
|
||||
graph.passes[1].after = ["cluster-lights"];
|
||||
const normals = renderGraphPresets.find((preset) => preset.id === "normals")!;
|
||||
const result = buildFxNodeRenderGraph(normals.layout, graph) as any;
|
||||
assert.equal(
|
||||
result.passes.some((pass: any) => pass.type === "compute"),
|
||||
false,
|
||||
);
|
||||
assert.deepEqual(result.pipelines.compute, []);
|
||||
assert.equal(
|
||||
result.resources.buffers.some((buffer: any) =>
|
||||
["point-lights", "clusters"].includes(buffer.id),
|
||||
),
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
test("depth loadout uses position-only geometry and renders to canvas", () => {
|
||||
const depth = renderGraphPresets.find((preset) => preset.id === "depth")!;
|
||||
const graph = buildFxNodeRenderGraph(depth.layout, baseGraph) as any;
|
||||
assert.deepEqual(
|
||||
graph.pipelines.render.map((pipeline: any) => pipeline.id),
|
||||
["rg-depth-basic"],
|
||||
);
|
||||
assert.match(graph.pipelines.render[0].code, /0\.2040/u);
|
||||
assert.deepEqual(graph.pipelines.render[0].vertex.buffers.length, 1);
|
||||
assert.deepEqual(graph.passes[0].vertexBuffers, [
|
||||
{ slot: 0, resource: "geometry-1-positions" },
|
||||
]);
|
||||
assert.deepEqual(graph.passes[0].color, [
|
||||
{ resource: "canvas", clear: [0, 0, 0, 1] },
|
||||
]);
|
||||
assert.equal(
|
||||
graph.resources.buffers.some(
|
||||
(buffer: any) => buffer.id === "geometry-1-normals",
|
||||
),
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
test("cinematic loadout inserts ordered sampled post passes", () => {
|
||||
const cinematic = renderGraphPresets.find(
|
||||
(preset) => preset.id === "cinematic",
|
||||
)!;
|
||||
const serialized = compileFxNodeRenderGraph(cinematic.layout, baseGraph);
|
||||
const graph = buildFxNodeRenderGraph(cinematic.layout, baseGraph) as any;
|
||||
const serialized = encodeRenderGraph(graph);
|
||||
assert.match(serialized, /rg-effect-pass-0-bloom/u);
|
||||
assert.match(serialized, /rg-effect-pass-1-grade/u);
|
||||
assert.match(serialized, /rg-effect-pass-2-vignette/u);
|
||||
assert.match(serialized, /rg-effect-2-vignette/u);
|
||||
assert.doesNotMatch(serialized, /\(field "id" "rg-effect-2-vignette"\)/u);
|
||||
assert.doesNotMatch(serialized, /\(field "id" "present"\)/u);
|
||||
assert.deepEqual(
|
||||
graph.passes.find((pass: any) => pass.id === "rg-effect-pass-2-vignette")
|
||||
.color,
|
||||
[{ resource: "canvas", clear: [0, 0, 0, 1] }],
|
||||
);
|
||||
assert.match(serialized, /let liftBalance = vec3<f32>/u);
|
||||
assert.match(serialized, /let gammaBalance = vec3<f32>/u);
|
||||
assert.match(serialized, /let gainBalance = vec3<f32>/u);
|
||||
|
||||
Reference in New Issue
Block a user