feat: add variadic render graph logic nodes
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:
@@ -446,10 +446,10 @@ export function adaptFxNodeSnapshot(raw, revision = 1) {
|
||||
linkSources.set(link.id, linkSource);
|
||||
if (!link.muted) {
|
||||
incoming.set(link.toSocketId, (incoming.get(link.toSocketId) ?? 0) + 1);
|
||||
nodes.get(to.node).value.inputs[to.key] = {
|
||||
(nodes.get(to.node).value.inputs[to.key] ??= []).push({
|
||||
node: from.node,
|
||||
socket: from.key,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
const ordered = [...nodes.values()].sort((a, b) =>
|
||||
@@ -548,13 +548,13 @@ export function adaptFxNodeSnapshot(raw, revision = 1) {
|
||||
for (const key of Object.keys(
|
||||
descriptors[item.value.executor.key].inputs,
|
||||
)) {
|
||||
const link = raw.links.find(
|
||||
const links = raw.links.filter(
|
||||
(x) =>
|
||||
!x.muted &&
|
||||
x.toNodeId === item.value.id &&
|
||||
sockets.get(x.toSocketId)?.key === key,
|
||||
);
|
||||
const source = linkSources.get(link?.id) ?? {
|
||||
const source = linkSources.get(links[0]?.id) ?? {
|
||||
kind: "input",
|
||||
nodeId: item.value.id,
|
||||
input: key,
|
||||
@@ -562,9 +562,11 @@ export function adaptFxNodeSnapshot(raw, revision = 1) {
|
||||
unconnected: true,
|
||||
};
|
||||
paths[`${base}.inputs.${key}`] = source;
|
||||
if (link) {
|
||||
paths[`${base}.inputs.${key}.node`] = source;
|
||||
paths[`${base}.inputs.${key}.socket`] = {
|
||||
for (const [index, link] of links.entries()) {
|
||||
const linkSource = linkSources.get(link.id);
|
||||
paths[`${base}.inputs.${key}[${index}]`] = linkSource;
|
||||
paths[`${base}.inputs.${key}[${index}].node`] = linkSource;
|
||||
paths[`${base}.inputs.${key}[${index}].socket`] = {
|
||||
kind: "socket",
|
||||
nodeId: link.fromNodeId,
|
||||
socketId: link.fromSocketId,
|
||||
@@ -576,7 +578,7 @@ export function adaptFxNodeSnapshot(raw, revision = 1) {
|
||||
paths[`${base}.inputs`] = nodeSource;
|
||||
}
|
||||
const ir = {
|
||||
schemaVersion: 2,
|
||||
schemaVersion: 3,
|
||||
graphId: GRAPH_ID,
|
||||
revision,
|
||||
nodes: ordered.map((item) => item.value),
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
export const GRAPH_ID = "authored_gpu_culling";
|
||||
export const CATALOG_VERSION = 10;
|
||||
export const CATALOG_VERSION = 11;
|
||||
const exact = (type) => ({ kind: "exact", types: [type] });
|
||||
const i = (type, required = true, authoringType, defaultPolicy = required ? "none" : "parameter_literal") => ({
|
||||
const i = (type, minimum = 1, authoringType, defaultPolicy = minimum ? "none" : "parameter_literal", maximum = 1) => ({
|
||||
accepted: typeof type === "string" ? exact(type) : type,
|
||||
required,
|
||||
cardinality: { minimum, maximum },
|
||||
...(authoringType ? { authoringType } : {}),
|
||||
defaultPolicy,
|
||||
});
|
||||
@@ -11,18 +11,18 @@ const o = (type) => ({ type });
|
||||
const expression = (inputs, outputs) => ({
|
||||
version: 1,
|
||||
execution: "expression",
|
||||
inputs: Object.fromEntries(Object.entries(inputs).map(([name, type]) => [name, i(type, false)])),
|
||||
inputs: Object.fromEntries(Object.entries(inputs).map(([name, type]) => [name, i(type, 0)])),
|
||||
outputs: Object.fromEntries(Object.entries(outputs).map(([name, type]) => [name, o(type)])),
|
||||
parameters: {},
|
||||
});
|
||||
const numbered = (prefix, count, type) =>
|
||||
Object.fromEntries(Array.from({ length: count }, (_, index) => [`${prefix}${index}`, type]));
|
||||
const expressionCatalog = {
|
||||
and: expression({ left: "bool", right: "bool" }, { value: "bool" }),
|
||||
or: expression({ left: "bool", right: "bool" }, { value: "bool" }),
|
||||
and: { ...expression({ inputs: "bool" }, { value: "bool" }), version: 2, inputs: { inputs: i("bool", 0, undefined, "none", 8) } },
|
||||
or: { ...expression({ inputs: "bool" }, { value: "bool" }), version: 2, inputs: { inputs: i("bool", 0, undefined, "none", 8) } },
|
||||
not: expression({ operand: "bool" }, { value: "bool" }),
|
||||
xor: expression({ left: "bool", right: "bool" }, { value: "bool" }),
|
||||
xnor: expression({ left: "bool", right: "bool" }, { value: "bool" }),
|
||||
xor: { ...expression({ inputs: "bool" }, { value: "bool" }), version: 2, inputs: { inputs: i("bool", 0, undefined, "none", 8) } },
|
||||
xnor: { ...expression({ inputs: "bool" }, { value: "bool" }), version: 2, inputs: { inputs: i("bool", 0, undefined, "none", 8) } },
|
||||
greater_than_f32: expression({ left: "f32", right: "f32" }, { value: "bool" }),
|
||||
less_than_f32: expression({ left: "f32", right: "f32" }, { value: "bool" }),
|
||||
equals_f32: expression({ left: "f32", right: "f32" }, { value: "bool" }),
|
||||
@@ -112,9 +112,9 @@ export const semanticCatalog = Object.freeze({
|
||||
execution: "render",
|
||||
inputs: {
|
||||
mesh: i("mesh_data"),
|
||||
predicate: i("bool", false),
|
||||
colorTarget: i("texture", false, undefined, "compiler_texture"),
|
||||
depthTarget: i("texture", false, undefined, "compiler_texture"),
|
||||
predicate: i("bool", 0),
|
||||
colorTarget: i("texture", 0, undefined, "compiler_texture"),
|
||||
depthTarget: i("texture", 0, undefined, "compiler_texture"),
|
||||
},
|
||||
outputs: { color: o("texture"), depth: o("texture") },
|
||||
parameters: { pipeline: "gltf_standard", depthCompare: "less_equal", depthWriteEnabled: true, clearDepth: 1, clearColor: [0.015, 0.02, 0.03, 1] },
|
||||
@@ -271,11 +271,11 @@ export const styles = {
|
||||
render: { header: "#426b43" },
|
||||
frame: { header: "#a75d37" },
|
||||
};
|
||||
const socket = (title, direction, type, value = null) => ({
|
||||
const socket = (title, direction, type, value = null, capacity = 1) => ({
|
||||
title,
|
||||
direction,
|
||||
type,
|
||||
maxIncomingLinks: direction === "input" ? 1 : 0,
|
||||
maxIncomingLinks: direction === "input" ? capacity : 0,
|
||||
visible: true,
|
||||
value,
|
||||
showValue: value !== null,
|
||||
@@ -429,6 +429,7 @@ export const nodeDefinitions = Object.fromEntries(
|
||||
"input",
|
||||
v.authoringType ?? v.accepted.types[0],
|
||||
v.defaultPolicy === "parameter_literal" ? socketDefault(v.accepted.types[0], defaultForInput(key, n, v.accepted.types[0])) : null,
|
||||
v.cardinality.maximum,
|
||||
),
|
||||
]),
|
||||
),
|
||||
|
||||
@@ -16,10 +16,10 @@ async function seed(root) {
|
||||
for (const [index, item] of culling.nodes.entries())
|
||||
await root.dispatch({ type: "node.add", nodeId: item.id, nodeType: item.executor.key,
|
||||
position: { x: 40 + (index % 6) * 280, y: 120 + Math.floor(index / 6) * 260 } });
|
||||
const links = culling.nodes.flatMap((item) => Object.entries(item.inputs).map(([socket, from]) =>
|
||||
[from.node, from.socket, item.id, socket]));
|
||||
for (const [a, as, b, bs] of links) {
|
||||
const id = `${a}_${as}_${b}_${bs}`;
|
||||
const links = culling.nodes.flatMap((item) => Object.entries(item.inputs).flatMap(([socket, sources]) =>
|
||||
sources.map((from, index) => [from.node, from.socket, item.id, socket, index])));
|
||||
for (const [a, as, b, bs, index] of links) {
|
||||
const id = `${a}_${as}_${b}_${bs}_${index}`;
|
||||
await root.dispatch({
|
||||
type: "link.add",
|
||||
link: {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { descriptors } from "./catalog.js";
|
||||
|
||||
const input = (node, socket) => ({ node, socket });
|
||||
const input = (node, socket) => [{ node, socket }];
|
||||
const node = (id, key, parameters = {}, inputs = {}) => ({
|
||||
id, state: "enabled", executor: { key, version: descriptors[key].version }, parameters, inputs,
|
||||
});
|
||||
@@ -17,20 +17,19 @@ const predicates = (withCulling = false) => {
|
||||
const result = [
|
||||
node("type_words", "separate_u32x16", { valueDefault: Array(16).fill(0) }, { value: input("mesh", "type") }),
|
||||
node("type_bits", "separate_u32_bits", { valueDefault: 0 }, { value: input("type_words", "word0") }),
|
||||
node("ground_class", "and", { leftDefault: true, rightDefault: true }, { left: input("type_bits", "bit0"), right: input("type_bits", "bit1") }),
|
||||
node("visible_pbr", "and", { leftDefault: true, rightDefault: true }, { left: input("type_bits", "bit0"), right: input("type_bits", "bit2") }),
|
||||
node("ground_class", "and", {}, { inputs: [...input("type_bits", "bit0"), ...input("type_bits", "bit1")] }),
|
||||
node("not_double", "not", { operandDefault: false }, { operand: input("type_bits", "bit3") }),
|
||||
node("standard_class", "and", { leftDefault: true, rightDefault: true }, { left: input("visible_pbr", "value"), right: input("not_double", "value") }),
|
||||
node("double_class", "and", { leftDefault: true, rightDefault: true }, { left: input("type_bits", "bit0"), right: input("type_bits", "bit3") }),
|
||||
node("standard_class", "and", {}, { inputs: [...input("type_bits", "bit0"), ...input("type_bits", "bit2"), ...input("not_double", "value")] }),
|
||||
node("double_class", "and", {}, { inputs: [...input("type_bits", "bit0"), ...input("type_bits", "bit3")] }),
|
||||
];
|
||||
if (!withCulling) return { nodes: result, classes: { ground: "ground_class", pbr: "standard_class", pbr_double: "double_class" } };
|
||||
result.push(
|
||||
node("cull", "frustum_cull", { camera: "active" }, { mesh: input("mesh", "mesh"), localAabb: input("mesh", "localAabb") }),
|
||||
node("not_culled", "not", { operandDefault: false }, { operand: input("cull", "isFrustumCulled") }),
|
||||
...[["ground", "ground_class"], ["pbr", "standard_class"], ["pbr_double", "double_class"]].map(([name, classification]) =>
|
||||
node(`${name}_final`, "and", { leftDefault: true, rightDefault: true }, { left: input(classification, "value"), right: input("not_culled", "value") })),
|
||||
);
|
||||
return { nodes: result, classes: { ground: "ground_final", pbr: "pbr_final", pbr_double: "pbr_double_final" } };
|
||||
for (const id of ["ground_class", "standard_class", "double_class"])
|
||||
result.find((item) => item.id === id).inputs.inputs.push(...input("not_culled", "value"));
|
||||
return { nodes: result, classes: { ground: "ground_class", pbr: "standard_class", pbr_double: "double_class" } };
|
||||
};
|
||||
const scene = (colorTarget, clearColor = [0.015, 0.02, 0.03, 1], heightScale = 1, withCulling = false) => {
|
||||
const classification = predicates(withCulling);
|
||||
@@ -45,7 +44,7 @@ const scene = (colorTarget, clearColor = [0.015, 0.02, 0.03, 1], heightScale = 1
|
||||
node("pbr_double", "pipeline", { pipeline: "gltf_standard_double_sided", depthCompare: "less_equal", depthWriteEnabled: true, clearDepth: 1, clearColor, predicateDefault: true }, { mesh: input("mesh", "mesh"), predicate: input(classification.classes.pbr_double, "value"), colorTarget: input("pbr", "color"), depthTarget: input("pbr", "depth") }),
|
||||
];
|
||||
};
|
||||
const graph = (graphId, nodes) => Object.freeze({ schemaVersion: 2, graphId, revision: 1, nodes });
|
||||
const graph = (graphId, nodes) => Object.freeze({ schemaVersion: 3, graphId, revision: 2, nodes });
|
||||
const direct = (graphId, clearColor) => graph(graphId, [
|
||||
node("ldr", "texture", texture("rgba8_unorm")),
|
||||
...scene("ldr", clearColor),
|
||||
|
||||
Reference in New Issue
Block a user