feat: add render graph driven renderer architecture

Amp-Thread-ID: https://ampcode.com/threads/T-019f9d91-77c1-7206-a60f-ed6554ce92ab

Co-authored-by: Heaust Azure <heaust.azure@gmail.com>
This commit is contained in:
Amp
2026-07-27 02:53:44 +00:00
co-authored by heaust
parent 8a8369706b
commit d4e8634f67
290 changed files with 48804 additions and 1995 deletions
+130
View File
@@ -0,0 +1,130 @@
import { compileFxNodeComposition } from "@lib/composition/index.js";
import { bindFxNodeHeadless } from "@lib/headless-runtime.js";
import { layoutGraph } from "@lib/layout/layout-graph.js";
import { worldToView } from "@lib/layout/geometry.js";
import { renderCanvas } from "@lib/render/canvas-renderer.js";
import { linkId } from "@lib/core/types.js";
const theme = {
background: "#010203",
grid: "#010203",
frame: "#070809",
frameHeader: "#0a0b0c",
body: "#0d0e0f",
control: "#101112",
controlFill: "#131415",
controlEditing: "#161718",
textSelection: "#191a1b",
outline: "#1c1d1e",
text: "#1f2021",
muted: "#222324",
shadow: "#00000000",
nodeSelected: "#28292a",
nodeActive: "#2b2c2d",
unknownHeader: "#2e2f30",
unknownSocket: "#313233",
linkMuted: "#343536",
knifeMuted: "#373839",
emphasis: "#3a3b3c",
focus: "#3d3e3f",
editOutline: "#404142",
resize: "#434445",
muteOverlay: "#464748",
boxSelectionFill: "#494a4b",
checkerLight: "#4c4d4e",
checkerDark: "#4f5051",
widgetBorder: "#525354",
rampBorder: "#555657",
resourceBackground: "#58595a",
} as const;
const base = () =>
({
version: 1,
behavior: "standard",
style: "custom",
parameters: {},
muteBypass: [],
migrations: [],
}) as const;
const composition = {
schemaVersion: 2,
id: "browser-render-proof",
version: 1,
compatibility: { wildcardInputTypes: [] },
theme,
socketTypes: { signal: { title: "Signal", color: "#a1b2c3", acceptsFrom: ["signal"] } },
nodeStyles: { custom: { header: "#7654ab" } },
resources: {},
nodes: {
source: {
...base(),
title: "Source",
sockets: {
out: {
title: "Out",
direction: "output",
type: "signal",
maxIncomingLinks: 0,
visible: true,
value: null,
showValue: false,
},
},
ui: [{ kind: "socket", socket: "out" }],
},
target: {
...base(),
title: "Target",
sockets: {
in: {
title: "In",
direction: "input",
type: "signal",
maxIncomingLinks: 1,
visible: true,
value: null,
showValue: false,
},
},
ui: [{ kind: "socket", socket: "in" }],
},
},
} as const;
const compiled = compileFxNodeComposition(composition),
runtime = bindFxNodeHeadless(compiled);
const source = runtime.materializeNode("source", "source", { x: -200, y: 80 }),
target = runtime.materializeNode("target", "target", { x: 100, y: 80 });
const link = {
id: linkId("wire"),
fromNodeId: source.id,
fromSocketId: source.sockets[0]!.id,
toNodeId: target.id,
toSocketId: target.sockets[0]!.id,
muted: false,
extensions: {},
};
const graph = { ...runtime.emptyDocument("render"), nodes: { source, target }, links: { wire: link } };
const transform = { center: { x: 0, y: 0 }, zoom: 1, viewport: { x: 500, y: 300 }, dpr: 1 },
layout = layoutGraph(compiled, graph, transform);
const canvas = document.querySelector<HTMLCanvasElement>("#presentation")!,
context = canvas.getContext("2d")!;
renderCanvas(context as unknown as OffscreenCanvasRenderingContext2D, layout, compiled.theme);
const pixel = (x: number, y: number) => Array.from(context.getImageData(Math.round(x), Math.round(y), 1, 1).data);
const contains = (x: number, y: number, rgba: readonly number[]) => {
const data = context.getImageData(Math.round(x) - 3, Math.round(y) - 3, 7, 7).data;
for (let i = 0; i < data.length; i += 4) if (rgba.every((v, j) => data[i + j] === v)) return true;
return false;
};
const node = layout.nodes.get(source.id)!,
socket = layout.sockets.get(source.sockets[0]!.id)!,
wire = layout.links.get("wire" as never)!,
header = worldToView({ x: node.bounds.x + 60, y: node.bounds.y - 12 }, transform),
socketPoint = worldToView(socket.anchor, transform),
middle = worldToView(wire.points[Math.floor(wire.points.length / 2)]!, transform);
const result = {
background: pixel(3, 3),
header: pixel(header.x, header.y),
socket: pixel(socketPoint.x, socketPoint.y),
linkContainsCustomColor: contains(middle.x, middle.y, [161, 178, 195, 255]),
};
(window as unknown as { presentationResult: typeof result }).presentationResult = result;