refactor: redesign render graph 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:
+267
-41
@@ -56,14 +56,20 @@ const sourceMaps = new WeakMap();
|
||||
export const getSourceMap = (ir) => sourceMaps.get(ir);
|
||||
export const mapAuthoringDiagnostic = (ir, diagnostic) => {
|
||||
const details = diagnostic?.details;
|
||||
const path = [details?.path, diagnostic?.path, details?.field, diagnostic?.field]
|
||||
.find((value) => typeof value === "string");
|
||||
const path = [
|
||||
details?.path,
|
||||
diagnostic?.path,
|
||||
details?.field,
|
||||
diagnostic?.field,
|
||||
].find((value) => typeof value === "string");
|
||||
const map = getSourceMap(ir);
|
||||
let match;
|
||||
if (path && map)
|
||||
for (const key of Object.keys(map))
|
||||
if (
|
||||
(path === key || path.startsWith(`${key}.`) || path.startsWith(`${key}[`)) &&
|
||||
(path === key ||
|
||||
path.startsWith(`${key}.`) ||
|
||||
path.startsWith(`${key}[`)) &&
|
||||
(!match || key.length > match.length)
|
||||
)
|
||||
match = key;
|
||||
@@ -80,33 +86,49 @@ export const mapAuthoringDiagnostic = (ir, diagnostic) => {
|
||||
const mapValuePaths = (paths, path, source, value) => {
|
||||
paths[path] = source;
|
||||
if (Array.isArray(value))
|
||||
value.forEach((child, index) => mapValuePaths(paths, `${path}[${index}]`, source, child));
|
||||
value.forEach((child, index) =>
|
||||
mapValuePaths(paths, `${path}[${index}]`, source, child),
|
||||
);
|
||||
else if (object(value))
|
||||
for (const key of Object.keys(value))
|
||||
mapValuePaths(paths, `${path}.${key}`, source, value[key]);
|
||||
};
|
||||
|
||||
function parameterValue(raw, schema, nodeId, key) {
|
||||
const expected = schema.type === "json" ? "json" : schema.type;
|
||||
if (
|
||||
!exactKeys(raw, ["kind", "value"]) ||
|
||||
raw.kind !== expected ||
|
||||
!finiteJson(raw.value)
|
||||
)
|
||||
fail("AUTHORING_PARAMETER", { nodeId, parameter: key });
|
||||
if (
|
||||
(expected === "number" && typeof raw.value !== "number") ||
|
||||
(expected === "string" && typeof raw.value !== "string") ||
|
||||
(expected === "boolean" && typeof raw.value !== "boolean") ||
|
||||
(expected === "json" && !finiteJson(raw.value))
|
||||
)
|
||||
if (!exactKeys(raw, ["kind", "value"]) || raw.kind !== schema.type)
|
||||
fail("AUTHORING_PARAMETER", { nodeId, parameter: key });
|
||||
const value = raw.value;
|
||||
const bounded = (number) =>
|
||||
Number.isFinite(number) &&
|
||||
(schema.minimum === undefined || number >= schema.minimum) &&
|
||||
(schema.maximum === undefined || number <= schema.maximum);
|
||||
const valid =
|
||||
schema.type === "number"
|
||||
? bounded(value) && (!schema.integer || Number.isSafeInteger(value))
|
||||
: schema.type === "string"
|
||||
? typeof value === "string" &&
|
||||
(!schema.enum || schema.enum.includes(value))
|
||||
: schema.type === "boolean"
|
||||
? typeof value === "boolean"
|
||||
: schema.type === "vector" || schema.type === "color"
|
||||
? Array.isArray(value) &&
|
||||
value.length === (schema.type === "vector" ? 3 : 4) &&
|
||||
value.every(bounded)
|
||||
: schema.type === "json" && finiteJson(value);
|
||||
if (!valid) fail("AUTHORING_PARAMETER", { nodeId, parameter: key });
|
||||
return canonical(structuredClone(raw.value));
|
||||
}
|
||||
|
||||
export function adaptFxNodeSnapshot(raw, revision = 1) {
|
||||
try {
|
||||
const rootKeys = ["graphId", "catalogVersion", "nodes", "links", "metadata", "version"];
|
||||
const rootKeys = [
|
||||
"graphId",
|
||||
"catalogVersion",
|
||||
"nodes",
|
||||
"links",
|
||||
"metadata",
|
||||
"version",
|
||||
];
|
||||
if (
|
||||
!exactKeys(raw, rootKeys) ||
|
||||
!Array.isArray(raw.nodes) ||
|
||||
@@ -117,9 +139,7 @@ export function adaptFxNodeSnapshot(raw, revision = 1) {
|
||||
fail("AUTHORING_SHAPE");
|
||||
if (raw.graphId !== GRAPH_ID || raw.catalogVersion !== CATALOG_VERSION)
|
||||
fail("AUTHORING_CATALOG");
|
||||
if (
|
||||
!Number.isSafeInteger(raw.version) || raw.version < 0
|
||||
)
|
||||
if (!Number.isSafeInteger(raw.version) || raw.version < 0)
|
||||
fail("AUTHORING_SHAPE");
|
||||
if (!Number.isInteger(revision) || revision < 1 || revision > 0xffffffff)
|
||||
fail("AUTHORING_REVISION");
|
||||
@@ -134,7 +154,20 @@ export function adaptFxNodeSnapshot(raw, revision = 1) {
|
||||
definition = nodeDefinitions[n.typeId];
|
||||
if (!descriptor)
|
||||
fail("AUTHORING_NODE_TYPE", { nodeId: n.id, typeId: n.typeId });
|
||||
const nodeKeys = ["id", "typeId", "typeVersion", "position", "size", "label", "parameters", "sockets", "muted", "collapsed", "extensions", "known"];
|
||||
const nodeKeys = [
|
||||
"id",
|
||||
"typeId",
|
||||
"typeVersion",
|
||||
"position",
|
||||
"size",
|
||||
"label",
|
||||
"parameters",
|
||||
"sockets",
|
||||
"muted",
|
||||
"collapsed",
|
||||
"extensions",
|
||||
"known",
|
||||
];
|
||||
if (Object.hasOwn(n, "parentId")) nodeKeys.push("parentId");
|
||||
if (
|
||||
!exactKeys(n, nodeKeys) ||
|
||||
@@ -143,10 +176,17 @@ export function adaptFxNodeSnapshot(raw, revision = 1) {
|
||||
typeof n.muted !== "boolean" ||
|
||||
typeof n.collapsed !== "boolean" ||
|
||||
typeof n.label !== "string" ||
|
||||
!exactKeys(n.position, ["x", "y"]) || !Number.isFinite(n.position.x) || !Number.isFinite(n.position.y) ||
|
||||
!exactKeys(n.size, ["x", "y"]) || !Number.isFinite(n.size.x) || !Number.isFinite(n.size.y) || n.size.x <= 0 || n.size.y <= 0 ||
|
||||
!exactKeys(n.position, ["x", "y"]) ||
|
||||
!Number.isFinite(n.position.x) ||
|
||||
!Number.isFinite(n.position.y) ||
|
||||
!exactKeys(n.size, ["x", "y"]) ||
|
||||
!Number.isFinite(n.size.x) ||
|
||||
!Number.isFinite(n.size.y) ||
|
||||
n.size.x <= 0 ||
|
||||
n.size.y <= 0 ||
|
||||
(Object.hasOwn(n, "parentId") && !identifier(n.parentId)) ||
|
||||
!object(n.extensions) || !finiteJson(n.extensions) ||
|
||||
!object(n.extensions) ||
|
||||
!finiteJson(n.extensions) ||
|
||||
!Array.isArray(n.sockets) ||
|
||||
!object(n.parameters)
|
||||
)
|
||||
@@ -168,6 +208,48 @@ export function adaptFxNodeSnapshot(raw, revision = 1) {
|
||||
),
|
||||
]),
|
||||
);
|
||||
if (n.typeId === "bloom_blur")
|
||||
parameters.direction =
|
||||
parameters.direction === "horizontal" ? [1, 0] : [0, 1];
|
||||
if (n.typeId === "frustum_cull") {
|
||||
parameters.camera = parameters.cameraSelection;
|
||||
delete parameters.cameraSelection;
|
||||
}
|
||||
if (n.typeId === "texture") {
|
||||
const extent =
|
||||
parameters.extentMode === "absolute"
|
||||
? {
|
||||
kind: "absolute",
|
||||
width: parameters.absoluteWidth,
|
||||
height: parameters.absoluteHeight,
|
||||
depthOrArrayLayers: parameters.depthOrArrayLayers,
|
||||
}
|
||||
: {
|
||||
kind: "surface_relative",
|
||||
width: {
|
||||
numerator: parameters.relativeWidthNumerator,
|
||||
denominator: parameters.relativeWidthDenominator,
|
||||
},
|
||||
height: {
|
||||
numerator: parameters.relativeHeightNumerator,
|
||||
denominator: parameters.relativeHeightDenominator,
|
||||
},
|
||||
depthOrArrayLayers: parameters.depthOrArrayLayers,
|
||||
};
|
||||
const flat = structuredClone(parameters);
|
||||
Object.keys(parameters).forEach((key) => delete parameters[key]);
|
||||
Object.assign(parameters, {
|
||||
residency: flat.residency,
|
||||
texture: {
|
||||
dimension: flat.dimension,
|
||||
format: flat.format,
|
||||
extent,
|
||||
mipLevelCount: flat.mipLevelCount,
|
||||
sampleCount: Number(flat.sampleCount),
|
||||
viewFormats: flat.viewFormat === "none" ? [] : [flat.viewFormat],
|
||||
},
|
||||
});
|
||||
}
|
||||
const expected = [
|
||||
...Object.keys(descriptor.inputs),
|
||||
...Object.keys(descriptor.outputs),
|
||||
@@ -181,17 +263,49 @@ export function adaptFxNodeSnapshot(raw, revision = 1) {
|
||||
socketDefinition = definition.sockets[s.key],
|
||||
direction = input ? "input" : "output",
|
||||
dataType = socketDefinition.type,
|
||||
socketKeys = ["id", "key", "label", "direction", "dataType", "accepts", "maxIncomingLinks", ...(socketDefinition.value ? ["defaultValue"] : []), "visible"];
|
||||
socketKeys = [
|
||||
"id",
|
||||
"key",
|
||||
"label",
|
||||
"direction",
|
||||
"dataType",
|
||||
"accepts",
|
||||
"maxIncomingLinks",
|
||||
...(socketDefinition.value ? ["defaultValue"] : []),
|
||||
"visible",
|
||||
];
|
||||
if (
|
||||
!exactKeys(s, socketKeys) ||
|
||||
s.id !== `${n.id}:${s.key}` ||
|
||||
s.label !== socketDefinition.title ||
|
||||
s.direction !== direction ||
|
||||
s.dataType !== dataType ||
|
||||
!Array.isArray(s.accepts) || s.accepts.length !== (direction === "input" ? socketTypes[dataType].acceptsFrom.length : 0) ||
|
||||
!s.accepts.every((v, i) => v === (direction === "input" ? socketTypes[dataType].acceptsFrom[i] : undefined)) ||
|
||||
!Array.isArray(s.accepts) ||
|
||||
s.accepts.length !==
|
||||
(direction === "input"
|
||||
? socketTypes[dataType].acceptsFrom.length
|
||||
: 0) ||
|
||||
!s.accepts.every(
|
||||
(v, i) =>
|
||||
v ===
|
||||
(direction === "input"
|
||||
? socketTypes[dataType].acceptsFrom[i]
|
||||
: undefined),
|
||||
) ||
|
||||
(socketDefinition.value
|
||||
? !exactKeys(s.defaultValue, ["kind", "value"]) || !finiteJson(s.defaultValue.value)
|
||||
? (() => {
|
||||
try {
|
||||
parameterValue(
|
||||
s.defaultValue,
|
||||
socketDefinition.value,
|
||||
n.id,
|
||||
s.key,
|
||||
);
|
||||
return false;
|
||||
} catch {
|
||||
return true;
|
||||
}
|
||||
})()
|
||||
: s.defaultValue !== undefined) ||
|
||||
s.visible !== socketDefinition.visible ||
|
||||
s.maxIncomingLinks !== socketDefinition.maxIncomingLinks
|
||||
@@ -206,10 +320,21 @@ export function adaptFxNodeSnapshot(raw, revision = 1) {
|
||||
: descriptor.outputs[s.key].type,
|
||||
authoringType: s.dataType,
|
||||
maxIncomingLinks: s.maxIncomingLinks,
|
||||
defaultValue: socketDefinition.value
|
||||
? structuredClone(s.defaultValue)
|
||||
: undefined,
|
||||
});
|
||||
}
|
||||
if (new Set(n.sockets.map((s) => s.key)).size !== expected.length)
|
||||
fail("AUTHORING_SOCKET_SET", { nodeId: n.id });
|
||||
if (n.typeId === "mesh_query") {
|
||||
parameters.visibleDefault = sockets.get(
|
||||
`${n.id}:isVisible`,
|
||||
).defaultValue.value;
|
||||
parameters.frustumCulledDefault = sockets.get(
|
||||
`${n.id}:isFrustumCulled`,
|
||||
).defaultValue.value;
|
||||
}
|
||||
nodes.set(n.id, {
|
||||
ordinal,
|
||||
value: {
|
||||
@@ -230,8 +355,18 @@ export function adaptFxNodeSnapshot(raw, revision = 1) {
|
||||
!object(link) ||
|
||||
!identifier(link.id) ||
|
||||
linkIds.has(link.id) ||
|
||||
!exactKeys(link, ["id", "fromNodeId", "fromSocketId", "toNodeId", "toSocketId", "muted", "extensions"]) ||
|
||||
typeof link.muted !== "boolean" || !object(link.extensions) || !finiteJson(link.extensions)
|
||||
!exactKeys(link, [
|
||||
"id",
|
||||
"fromNodeId",
|
||||
"fromSocketId",
|
||||
"toNodeId",
|
||||
"toSocketId",
|
||||
"muted",
|
||||
"extensions",
|
||||
]) ||
|
||||
typeof link.muted !== "boolean" ||
|
||||
!object(link.extensions) ||
|
||||
!finiteJson(link.extensions)
|
||||
)
|
||||
fail("AUTHORING_LINK", { linkId: link?.id });
|
||||
linkIds.add(link.id);
|
||||
@@ -244,21 +379,33 @@ export function adaptFxNodeSnapshot(raw, revision = 1) {
|
||||
link.toNodeId !== to.node ||
|
||||
from.direction !== "output" ||
|
||||
to.direction !== "input" ||
|
||||
(!link.muted && (incoming.get(link.toSocketId) ?? 0) >= to.maxIncomingLinks)
|
||||
(!link.muted &&
|
||||
(incoming.get(link.toSocketId) ?? 0) >= to.maxIncomingLinks)
|
||||
)
|
||||
fail(
|
||||
!link.muted && (incoming.get(link.toSocketId) ?? 0) >= (to?.maxIncomingLinks ?? Infinity)
|
||||
!link.muted &&
|
||||
(incoming.get(link.toSocketId) ?? 0) >=
|
||||
(to?.maxIncomingLinks ?? Infinity)
|
||||
? "AUTHORING_LINK_INCOMING"
|
||||
: "AUTHORING_LINK",
|
||||
!link.muted && (incoming.get(link.toSocketId) ?? 0) >= (to?.maxIncomingLinks ?? Infinity)
|
||||
!link.muted &&
|
||||
(incoming.get(link.toSocketId) ?? 0) >=
|
||||
(to?.maxIncomingLinks ?? Infinity)
|
||||
? { socketId: link.toSocketId }
|
||||
: { linkId: link.id },
|
||||
);
|
||||
const accepted =
|
||||
descriptors[nodes.get(to.node).value.executor.key].inputs[to.key]
|
||||
.accepted.types;
|
||||
const authoringAccepted = socketTypes[nodeDefinitions[nodes.get(to.node).value.executor.key].sockets[to.key].type].acceptsFrom;
|
||||
if (!accepted.includes(from.semanticType) || !authoringAccepted.includes(from.authoringType))
|
||||
const authoringAccepted =
|
||||
socketTypes[
|
||||
nodeDefinitions[nodes.get(to.node).value.executor.key].sockets[to.key]
|
||||
.type
|
||||
].acceptsFrom;
|
||||
if (
|
||||
!accepted.includes(from.semanticType) ||
|
||||
!authoringAccepted.includes(from.authoringType)
|
||||
)
|
||||
fail("AUTHORING_LINK_TYPE", { linkId: link.id });
|
||||
const linkSource = {
|
||||
kind: "link",
|
||||
@@ -299,13 +446,92 @@ export function adaptFxNodeSnapshot(raw, revision = 1) {
|
||||
const base = `nodes[${wireOrdinal}]`;
|
||||
const nodeSource = { kind: "node", nodeId: item.value.id };
|
||||
paths[base] = nodeSource;
|
||||
for (const field of ["id", "state", "executor", "executor.key", "executor.version"])
|
||||
for (const field of [
|
||||
"id",
|
||||
"state",
|
||||
"executor",
|
||||
"executor.key",
|
||||
"executor.version",
|
||||
])
|
||||
paths[`${base}.${field}`] = nodeSource;
|
||||
paths[`${base}.parameters`] = nodeSource;
|
||||
for (const key of Object.keys(item.value.parameters))
|
||||
mapValuePaths(paths, `${base}.parameters.${key}`, { kind: "parameter", nodeId: item.value.id, parameter: key }, item.value.parameters[key]);
|
||||
for (const key of Object.keys(descriptors[item.value.executor.key].inputs)) {
|
||||
const link = raw.links.find((x) => !x.muted && x.toNodeId === item.value.id && sockets.get(x.toSocketId)?.key === key);
|
||||
const parameterSource = (parameter) => ({
|
||||
kind: "parameter",
|
||||
nodeId: item.value.id,
|
||||
parameter,
|
||||
});
|
||||
if (item.value.executor.key === "texture") {
|
||||
const root = `${base}.parameters`;
|
||||
const texture = item.value.parameters.texture;
|
||||
paths[`${root}.residency`] = parameterSource("residency");
|
||||
paths[`${root}.texture`] = nodeSource;
|
||||
paths[`${root}.texture.dimension`] = parameterSource("dimension");
|
||||
paths[`${root}.texture.format`] = parameterSource("format");
|
||||
paths[`${root}.texture.extent`] = parameterSource("extentMode");
|
||||
paths[`${root}.texture.extent.kind`] = parameterSource("extentMode");
|
||||
paths[`${root}.texture.extent.depthOrArrayLayers`] =
|
||||
parameterSource("depthOrArrayLayers");
|
||||
if (texture.extent.kind === "absolute") {
|
||||
paths[`${root}.texture.extent.width`] =
|
||||
parameterSource("absoluteWidth");
|
||||
paths[`${root}.texture.extent.height`] =
|
||||
parameterSource("absoluteHeight");
|
||||
} else {
|
||||
paths[`${root}.texture.extent.width`] = parameterSource("extentMode");
|
||||
paths[`${root}.texture.extent.width.numerator`] = parameterSource(
|
||||
"relativeWidthNumerator",
|
||||
);
|
||||
paths[`${root}.texture.extent.width.denominator`] = parameterSource(
|
||||
"relativeWidthDenominator",
|
||||
);
|
||||
paths[`${root}.texture.extent.height`] =
|
||||
parameterSource("extentMode");
|
||||
paths[`${root}.texture.extent.height.numerator`] = parameterSource(
|
||||
"relativeHeightNumerator",
|
||||
);
|
||||
paths[`${root}.texture.extent.height.denominator`] = parameterSource(
|
||||
"relativeHeightDenominator",
|
||||
);
|
||||
}
|
||||
paths[`${root}.texture.mipLevelCount`] =
|
||||
parameterSource("mipLevelCount");
|
||||
paths[`${root}.texture.sampleCount`] = parameterSource("sampleCount");
|
||||
mapValuePaths(
|
||||
paths,
|
||||
`${root}.texture.viewFormats`,
|
||||
parameterSource("viewFormat"),
|
||||
texture.viewFormats,
|
||||
);
|
||||
} else
|
||||
for (const key of Object.keys(item.value.parameters))
|
||||
mapValuePaths(
|
||||
paths,
|
||||
`${base}.parameters.${key}`,
|
||||
item.value.executor.key === "mesh_query" && key.endsWith("Default")
|
||||
? {
|
||||
kind: "input",
|
||||
nodeId: item.value.id,
|
||||
input:
|
||||
key === "visibleDefault" ? "isVisible" : "isFrustumCulled",
|
||||
socketId: `${item.value.id}:${key === "visibleDefault" ? "isVisible" : "isFrustumCulled"}`,
|
||||
unconnected: true,
|
||||
}
|
||||
: parameterSource(
|
||||
item.value.executor.key === "frustum_cull" && key === "camera"
|
||||
? "cameraSelection"
|
||||
: key,
|
||||
),
|
||||
item.value.parameters[key],
|
||||
);
|
||||
for (const key of Object.keys(
|
||||
descriptors[item.value.executor.key].inputs,
|
||||
)) {
|
||||
const link = raw.links.find(
|
||||
(x) =>
|
||||
!x.muted &&
|
||||
x.toNodeId === item.value.id &&
|
||||
sockets.get(x.toSocketId)?.key === key,
|
||||
);
|
||||
const source = linkSources.get(link?.id) ?? {
|
||||
kind: "input",
|
||||
nodeId: item.value.id,
|
||||
|
||||
@@ -3,8 +3,9 @@ import { semanticCatalog } from "./catalog.js";
|
||||
const GROUPS = Object.freeze([
|
||||
["source", "Source"],
|
||||
["compute", "Compute"],
|
||||
["cpu_preparation", "CPU preparation"],
|
||||
["render", "Render / post"],
|
||||
["present", "Present"],
|
||||
["frame", "Frame"],
|
||||
]);
|
||||
|
||||
const title = (typeId) => typeId.replaceAll("_", " ");
|
||||
|
||||
+186
-97
@@ -1,7 +1,6 @@
|
||||
export const GRAPH_ID = "authored_gpu_culling";
|
||||
export const CATALOG_VERSION = 2;
|
||||
export const CATALOG_VERSION = 4;
|
||||
const exact = (type) => ({ kind: "exact", types: [type] });
|
||||
const oneOf = (...types) => ({ kind: "one_of", types });
|
||||
const i = (type, required = true, authoringType) => ({
|
||||
accepted: typeof type === "string" ? exact(type) : type,
|
||||
required,
|
||||
@@ -25,104 +24,91 @@ const texture = {
|
||||
},
|
||||
};
|
||||
export const semanticCatalog = Object.freeze({
|
||||
surface_target: {
|
||||
mesh: {
|
||||
execution: "source",
|
||||
inputs: {},
|
||||
outputs: { surface: o("surface_target") },
|
||||
parameters: {},
|
||||
},
|
||||
texture_spec: {
|
||||
execution: "source",
|
||||
inputs: {},
|
||||
outputs: { spec: o("texture_spec") },
|
||||
parameters: structuredClone(texture),
|
||||
},
|
||||
scene_table: {
|
||||
execution: "source",
|
||||
inputs: {},
|
||||
outputs: { scene: o("scene_table") },
|
||||
parameters: {},
|
||||
},
|
||||
local_aabb_buffer: {
|
||||
execution: "source",
|
||||
inputs: { scene: i("scene_table") },
|
||||
outputs: { localAabbs: o("local_aabb_buffer") },
|
||||
parameters: {},
|
||||
},
|
||||
camera_frustum: {
|
||||
execution: "source",
|
||||
inputs: {},
|
||||
outputs: { frustum: o("camera_frustum") },
|
||||
parameters: {},
|
||||
},
|
||||
visibility_flags: {
|
||||
execution: "source",
|
||||
inputs: { scene: i("scene_table") },
|
||||
outputs: {
|
||||
flags: {
|
||||
mesh: o("mesh_data"),
|
||||
localAabbs: o("local_aabb_buffer"),
|
||||
isVisible: {
|
||||
...o("boolean_flag_buffer"),
|
||||
authoringType: "visibility_flag_buffer",
|
||||
},
|
||||
pipelineIndices: o("pipeline_index_stream"),
|
||||
},
|
||||
parameters: {},
|
||||
},
|
||||
texture: {
|
||||
execution: "source",
|
||||
inputs: {},
|
||||
outputs: { texture: o("texture") },
|
||||
parameters: {
|
||||
residency: "transient",
|
||||
format: "rgba16_float",
|
||||
dimension: "d2",
|
||||
extentMode: "surface_relative",
|
||||
absoluteWidth: 1,
|
||||
absoluteHeight: 1,
|
||||
relativeWidthNumerator: 1,
|
||||
relativeWidthDenominator: 1,
|
||||
relativeHeightNumerator: 1,
|
||||
relativeHeightDenominator: 1,
|
||||
depthOrArrayLayers: 1,
|
||||
mipLevelCount: 1,
|
||||
sampleCount: "1",
|
||||
viewFormat: "none",
|
||||
},
|
||||
},
|
||||
frustum_cull: {
|
||||
execution: "compute",
|
||||
inputs: {
|
||||
scene: i("scene_table"),
|
||||
mesh: i("mesh_data"),
|
||||
localAabbs: i("local_aabb_buffer"),
|
||||
frustum: i("camera_frustum"),
|
||||
},
|
||||
outputs: {
|
||||
flags: {
|
||||
isFrustumCulled: {
|
||||
...o("boolean_flag_buffer"),
|
||||
authoringType: "frustum_flag_buffer",
|
||||
},
|
||||
},
|
||||
parameters: {},
|
||||
parameters: { cameraSelection: "active" },
|
||||
},
|
||||
mesh_query: {
|
||||
execution: "compute",
|
||||
inputs: {
|
||||
scene: i("scene_table"),
|
||||
mesh: i("mesh_data"),
|
||||
isVisible: i("boolean_flag_buffer", false, "visibility_flag_buffer"),
|
||||
isFrustumCulled: i("boolean_flag_buffer", false, "frustum_flag_buffer"),
|
||||
},
|
||||
outputs: { draws: o("draw_stream") },
|
||||
parameters: {
|
||||
filters: [
|
||||
{ flag: "isVisible", predicate: "required_true" },
|
||||
{ flag: "isFrustumCulled", predicate: "required_false" },
|
||||
],
|
||||
visiblePredicate: "required_true",
|
||||
frustumCulledPredicate: "required_false",
|
||||
},
|
||||
},
|
||||
depth_stencil_config: {
|
||||
execution: "source",
|
||||
inputs: {},
|
||||
outputs: { config: o("depth_stencil_config") },
|
||||
parameters: {
|
||||
depthCompare: "less_equal",
|
||||
depthWriteEnabled: true,
|
||||
clearDepth: 1,
|
||||
},
|
||||
pipeline_registry: {
|
||||
execution: "cpu_preparation",
|
||||
inputs: { pipelineIndices: i("pipeline_index_stream") },
|
||||
outputs: { activation: o("pipeline_activation") },
|
||||
parameters: {},
|
||||
},
|
||||
legacy_forward: {
|
||||
pipeline: {
|
||||
execution: "render",
|
||||
inputs: {
|
||||
scene: i("scene_table"),
|
||||
mesh: i("mesh_data"),
|
||||
draws: i("draw_stream"),
|
||||
colorTarget: i(oneOf("surface_target", "texture_spec", "texture")),
|
||||
depthTarget: i(oneOf("texture_spec", "texture")),
|
||||
depthStencil: i("depth_stencil_config"),
|
||||
activation: i("pipeline_activation"),
|
||||
colorTarget: i("texture"),
|
||||
depthTarget: i("texture"),
|
||||
},
|
||||
outputs: { color: o("texture"), depth: o("texture") },
|
||||
parameters: { clearColor: [0.015, 0.02, 0.03, 1] },
|
||||
parameters: { pipeline: "gltf_standard", depthCompare: "less_equal", depthWriteEnabled: true, clearDepth: 1, clearColor: [0.015, 0.02, 0.03, 1] },
|
||||
},
|
||||
fullscreen_copy: {
|
||||
execution: "render",
|
||||
inputs: {
|
||||
source: i("texture"),
|
||||
colorTarget: i(oneOf("surface_target", "texture_spec", "texture")),
|
||||
colorTarget: i("texture"),
|
||||
},
|
||||
outputs: { color: o("texture") },
|
||||
parameters: {},
|
||||
@@ -131,7 +117,7 @@ export const semanticCatalog = Object.freeze({
|
||||
execution: "render",
|
||||
inputs: {
|
||||
source: i("texture"),
|
||||
colorTarget: i(oneOf("surface_target", "texture_spec", "texture")),
|
||||
colorTarget: i("texture"),
|
||||
},
|
||||
outputs: { color: o("texture") },
|
||||
parameters: { exposure: 1 },
|
||||
@@ -140,7 +126,7 @@ export const semanticCatalog = Object.freeze({
|
||||
execution: "render",
|
||||
inputs: {
|
||||
source: i("texture"),
|
||||
colorTarget: i(oneOf("surface_target", "texture_spec", "texture")),
|
||||
colorTarget: i("texture"),
|
||||
},
|
||||
outputs: { color: o("texture") },
|
||||
parameters: { threshold: 1, knee: 0.5 },
|
||||
@@ -149,7 +135,7 @@ export const semanticCatalog = Object.freeze({
|
||||
execution: "render",
|
||||
inputs: {
|
||||
source: i("texture"),
|
||||
colorTarget: i(oneOf("surface_target", "texture_spec", "texture")),
|
||||
colorTarget: i("texture"),
|
||||
},
|
||||
outputs: { color: o("texture") },
|
||||
parameters: { direction: [1, 0], radius: 1 },
|
||||
@@ -159,7 +145,7 @@ export const semanticCatalog = Object.freeze({
|
||||
inputs: {
|
||||
source: i("texture"),
|
||||
bloom: i("texture"),
|
||||
colorTarget: i(oneOf("surface_target", "texture_spec", "texture")),
|
||||
colorTarget: i("texture"),
|
||||
},
|
||||
outputs: { color: o("texture") },
|
||||
parameters: { intensity: 1 },
|
||||
@@ -168,14 +154,14 @@ export const semanticCatalog = Object.freeze({
|
||||
execution: "render",
|
||||
inputs: {
|
||||
source: i("texture"),
|
||||
colorTarget: i(oneOf("surface_target", "texture_spec", "texture")),
|
||||
colorTarget: i("texture"),
|
||||
},
|
||||
outputs: { color: o("texture") },
|
||||
parameters: { strength: 2 },
|
||||
},
|
||||
present: {
|
||||
execution: "present",
|
||||
inputs: { surface: i("texture") },
|
||||
frame_out: {
|
||||
execution: "frame",
|
||||
inputs: { color: i("texture") },
|
||||
outputs: {},
|
||||
parameters: {},
|
||||
},
|
||||
@@ -193,15 +179,13 @@ const socketColors = [
|
||||
];
|
||||
export const socketTypes = Object.fromEntries(
|
||||
[
|
||||
"surface_target",
|
||||
"texture_spec",
|
||||
"texture",
|
||||
"scene_table",
|
||||
"mesh_data",
|
||||
"local_aabb_buffer",
|
||||
"camera_frustum",
|
||||
"boolean_flag_buffer",
|
||||
"pipeline_index_stream",
|
||||
"draw_stream",
|
||||
"depth_stencil_config",
|
||||
"pipeline_activation",
|
||||
"visibility_flag_buffer",
|
||||
"frustum_flag_buffer",
|
||||
].map((type, index) => [
|
||||
@@ -213,12 +197,6 @@ export const socketTypes = Object.fromEntries(
|
||||
},
|
||||
]),
|
||||
);
|
||||
socketTypes.surface_target.acceptsFrom = [
|
||||
"surface_target",
|
||||
"texture_spec",
|
||||
"texture",
|
||||
];
|
||||
socketTypes.texture_spec.acceptsFrom = ["texture_spec", "texture"];
|
||||
socketTypes.boolean_flag_buffer.acceptsFrom = [
|
||||
"boolean_flag_buffer",
|
||||
"visibility_flag_buffer",
|
||||
@@ -259,33 +237,138 @@ export const theme = {
|
||||
export const styles = {
|
||||
source: { header: "#3977a8" },
|
||||
compute: { header: "#725a9b" },
|
||||
cpu_preparation: { header: "#8a6d3b" },
|
||||
render: { header: "#426b43" },
|
||||
present: { header: "#a75d37" },
|
||||
frame: { header: "#a75d37" },
|
||||
};
|
||||
const socket = (title, direction, type) => ({
|
||||
const socket = (title, direction, type, value = null) => ({
|
||||
title,
|
||||
direction,
|
||||
type,
|
||||
maxIncomingLinks: direction === "input" ? 1 : 0,
|
||||
visible: true,
|
||||
value: null,
|
||||
showValue: false,
|
||||
value,
|
||||
showValue: value !== null,
|
||||
});
|
||||
const parameterSchema = (value) =>
|
||||
typeof value === "number"
|
||||
? { type: "number", default: { kind: "number", value } }
|
||||
: typeof value === "string"
|
||||
? { type: "string", default: { kind: "string", value } }
|
||||
: typeof value === "boolean"
|
||||
? { type: "boolean", default: { kind: "boolean", value } }
|
||||
: { type: "json", default: { kind: "json", value } };
|
||||
const tagged = (kind, value) => ({ kind, value: structuredClone(value) });
|
||||
const number = (value, minimum, maximum) => ({
|
||||
type: "number",
|
||||
default: tagged("number", value),
|
||||
minimum,
|
||||
maximum,
|
||||
});
|
||||
const enumeration = (value, values) => ({
|
||||
type: "string",
|
||||
default: tagged("string", value),
|
||||
enum: values,
|
||||
});
|
||||
const string = (value) => ({ type: "string", default: tagged("string", value) });
|
||||
const boolean = (value) => ({
|
||||
type: "boolean",
|
||||
default: tagged("boolean", value),
|
||||
});
|
||||
const color = (value) => ({
|
||||
type: "color",
|
||||
default: tagged("color", value),
|
||||
minimum: 0,
|
||||
maximum: 1,
|
||||
});
|
||||
const json = (value) => ({ type: "json", default: tagged("json", value) });
|
||||
const parameterSchemas = {
|
||||
texture: {
|
||||
residency: enumeration("transient", ["transient", "persistent"]),
|
||||
format: enumeration("rgba16_float", [
|
||||
"rgba8_unorm",
|
||||
"rgba8_unorm_srgb",
|
||||
"bgra8_unorm",
|
||||
"bgra8_unorm_srgb",
|
||||
"rgba16_float",
|
||||
"r32_float",
|
||||
"depth32_float",
|
||||
]),
|
||||
dimension: enumeration("d2", ["d1", "d2", "d3"]),
|
||||
extentMode: enumeration("surface_relative", [
|
||||
"surface_relative",
|
||||
"absolute",
|
||||
]),
|
||||
absoluteWidth: { ...number(1, 1, 0xffffffff), integer: true },
|
||||
absoluteHeight: { ...number(1, 1, 0xffffffff), integer: true },
|
||||
relativeWidthNumerator: { ...number(1, 1, 0xffffffff), integer: true },
|
||||
relativeWidthDenominator: { ...number(1, 1, 0xffffffff), integer: true },
|
||||
relativeHeightNumerator: { ...number(1, 1, 0xffffffff), integer: true },
|
||||
relativeHeightDenominator: { ...number(1, 1, 0xffffffff), integer: true },
|
||||
depthOrArrayLayers: { ...number(1, 1, 0xffffffff), integer: true },
|
||||
mipLevelCount: { ...number(1, 1, 0xffffffff), integer: true },
|
||||
sampleCount: enumeration("1", ["1", "4"]),
|
||||
viewFormat: enumeration("none", [
|
||||
"none",
|
||||
"rgba8_unorm",
|
||||
"rgba8_unorm_srgb",
|
||||
"bgra8_unorm",
|
||||
"bgra8_unorm_srgb",
|
||||
"rgba16_float",
|
||||
"r32_float",
|
||||
"depth32_float",
|
||||
]),
|
||||
},
|
||||
mesh: {},
|
||||
frustum_cull: { cameraSelection: enumeration("active", ["active"]) },
|
||||
mesh_query: {
|
||||
visiblePredicate: enumeration("required_true", [
|
||||
"any",
|
||||
"required_true",
|
||||
"required_false",
|
||||
]),
|
||||
frustumCulledPredicate: enumeration("required_false", [
|
||||
"any",
|
||||
"required_true",
|
||||
"required_false",
|
||||
]),
|
||||
},
|
||||
pipeline_registry: {},
|
||||
pipeline: {
|
||||
pipeline: string("gltf_standard"),
|
||||
depthCompare: enumeration("less_equal", [
|
||||
"never",
|
||||
"less",
|
||||
"equal",
|
||||
"less_equal",
|
||||
"greater",
|
||||
"not_equal",
|
||||
"greater_equal",
|
||||
"always",
|
||||
]),
|
||||
depthWriteEnabled: boolean(true),
|
||||
clearDepth: number(1, 0, 1),
|
||||
clearColor: color([0.015, 0.02, 0.03, 1]),
|
||||
},
|
||||
fullscreen_copy: {},
|
||||
tone_map: { exposure: number(1, 0, 32) },
|
||||
bloom_extract: { threshold: number(1, 0, 64), knee: number(0.5, 0, 1) },
|
||||
bloom_blur: {
|
||||
direction: enumeration("horizontal", ["horizontal", "vertical"]),
|
||||
radius: number(1, 1, 16),
|
||||
},
|
||||
bloom_composite: { intensity: number(1, 0, 16) },
|
||||
luminance_edge: { strength: number(2, 0, 16) },
|
||||
frame_out: {},
|
||||
};
|
||||
export const nodeDefinitions = Object.fromEntries(
|
||||
Object.entries(semanticCatalog).map(([key, c]) => {
|
||||
const sockets = {
|
||||
...Object.fromEntries(
|
||||
Object.entries(c.inputs).map(([n, v]) => [
|
||||
n,
|
||||
socket(n, "input", v.authoringType ?? v.accepted.types[0]),
|
||||
socket(
|
||||
n,
|
||||
"input",
|
||||
v.authoringType ?? v.accepted.types[0],
|
||||
key === "mesh_query" && n === "isVisible"
|
||||
? boolean(true)
|
||||
: key === "mesh_query" && n === "isFrustumCulled"
|
||||
? boolean(false)
|
||||
: null,
|
||||
),
|
||||
]),
|
||||
),
|
||||
...Object.fromEntries(
|
||||
@@ -295,12 +378,15 @@ export const nodeDefinitions = Object.fromEntries(
|
||||
]),
|
||||
),
|
||||
},
|
||||
parameters = Object.fromEntries(
|
||||
Object.entries(c.parameters).map(([name, value]) => [
|
||||
name,
|
||||
parameterSchema(value),
|
||||
]),
|
||||
);
|
||||
parameters = parameterSchemas[key];
|
||||
if (
|
||||
!parameters ||
|
||||
Object.keys(parameters).length !== Object.keys(c.parameters).length ||
|
||||
!Object.keys(c.parameters).every((name) =>
|
||||
Object.hasOwn(parameters, name),
|
||||
)
|
||||
)
|
||||
throw new Error(`parameter schema mismatch for ${key}`);
|
||||
return [
|
||||
key,
|
||||
{
|
||||
@@ -314,6 +400,9 @@ export const nodeDefinitions = Object.fromEntries(
|
||||
...Object.keys(parameters).map((parameter) => ({
|
||||
kind: "parameter",
|
||||
parameter,
|
||||
...(key === "frustum_cull" && parameter === "cameraSelection"
|
||||
? { title: "Camera" }
|
||||
: {}),
|
||||
})),
|
||||
...Object.keys(sockets).map((socket) => ({ kind: "socket", socket })),
|
||||
],
|
||||
|
||||
@@ -5,19 +5,16 @@ import { createAddNodeMenu } from "./add-node-menu.js";
|
||||
import { createNodeIdAllocator, spawnRequestedNode } from "./node-spawn.js";
|
||||
|
||||
const spec = [
|
||||
["surface", "surface_target", { x: 40, y: 40 }],
|
||||
["hdr", "texture_spec", { x: 40, y: 170 }],
|
||||
["depth", "texture_spec", { x: 40, y: 300 }],
|
||||
["scene", "scene_table", { x: 40, y: 470 }],
|
||||
["aabbs", "local_aabb_buffer", { x: 290, y: 430 }],
|
||||
["frustum", "camera_frustum", { x: 290, y: 590 }],
|
||||
["visible", "visibility_flags", { x: 290, y: 300 }],
|
||||
["hdr", "texture", { x: 40, y: 170 }],
|
||||
["depth", "texture", { x: 40, y: 300 }],
|
||||
["mesh", "mesh", { x: 40, y: 470 }],
|
||||
["cull", "frustum_cull", { x: 540, y: 480 }],
|
||||
["query", "mesh_query", { x: 790, y: 330 }],
|
||||
["depth_config", "depth_stencil_config", { x: 790, y: 620 }],
|
||||
["forward", "legacy_forward", { x: 1040, y: 290 }],
|
||||
["copy", "fullscreen_copy", { x: 1300, y: 250 }],
|
||||
["present", "present", { x: 1540, y: 250 }],
|
||||
["registry", "pipeline_registry", { x: 790, y: 620 }],
|
||||
["ground", "pipeline", { x: 1040, y: 290 }],
|
||||
["pbr", "pipeline", { x: 1300, y: 290 }],
|
||||
["pbr_double", "pipeline", { x: 1560, y: 290 }],
|
||||
["frame_out", "frame_out", { x: 1820, y: 250 }],
|
||||
];
|
||||
async function seed(root) {
|
||||
await root.setState({
|
||||
@@ -30,22 +27,24 @@ async function seed(root) {
|
||||
for (const [nodeId, nodeType, position] of spec)
|
||||
await root.dispatch({ type: "node.add", nodeId, nodeType, position });
|
||||
const links = [
|
||||
["scene", "scene", "aabbs", "scene"],
|
||||
["scene", "scene", "visible", "scene"],
|
||||
["scene", "scene", "cull", "scene"],
|
||||
["aabbs", "localAabbs", "cull", "localAabbs"],
|
||||
["frustum", "frustum", "cull", "frustum"],
|
||||
["scene", "scene", "query", "scene"],
|
||||
["visible", "flags", "query", "isVisible"],
|
||||
["cull", "flags", "query", "isFrustumCulled"],
|
||||
["scene", "scene", "forward", "scene"],
|
||||
["query", "draws", "forward", "draws"],
|
||||
["hdr", "spec", "forward", "colorTarget"],
|
||||
["depth", "spec", "forward", "depthTarget"],
|
||||
["depth_config", "config", "forward", "depthStencil"],
|
||||
["forward", "color", "copy", "source"],
|
||||
["surface", "surface", "copy", "colorTarget"],
|
||||
["copy", "color", "present", "surface"],
|
||||
["mesh", "mesh", "cull", "mesh"],
|
||||
["mesh", "localAabbs", "cull", "localAabbs"],
|
||||
["mesh", "mesh", "query", "mesh"],
|
||||
["mesh", "isVisible", "query", "isVisible"],
|
||||
["cull", "isFrustumCulled", "query", "isFrustumCulled"],
|
||||
["mesh", "pipelineIndices", "registry", "pipelineIndices"],
|
||||
...["ground", "pbr", "pbr_double"].flatMap((pipeline) => [
|
||||
["mesh", "mesh", pipeline, "mesh"],
|
||||
["query", "draws", pipeline, "draws"],
|
||||
["registry", "activation", pipeline, "activation"],
|
||||
]),
|
||||
["hdr", "texture", "ground", "colorTarget"],
|
||||
["depth", "texture", "ground", "depthTarget"],
|
||||
["ground", "color", "pbr", "colorTarget"],
|
||||
["ground", "depth", "pbr", "depthTarget"],
|
||||
["pbr", "color", "pbr_double", "colorTarget"],
|
||||
["pbr", "depth", "pbr_double", "depthTarget"],
|
||||
["pbr_double", "color", "frame_out", "color"],
|
||||
];
|
||||
for (const [a, as, b, bs] of links) {
|
||||
const id = `${a}_${as}_${b}_${bs}`;
|
||||
@@ -64,34 +63,44 @@ async function seed(root) {
|
||||
}
|
||||
const authored = await root.getState(),
|
||||
depth = authored.nodes.find((node) => node.id === "depth");
|
||||
depth.parameters.texture = {
|
||||
kind: "json",
|
||||
value: {
|
||||
dimension: "d2",
|
||||
format: "depth32_float",
|
||||
extent: {
|
||||
kind: "surface_relative",
|
||||
width: { numerator: 1, denominator: 1 },
|
||||
height: { numerator: 1, denominator: 1 },
|
||||
depthOrArrayLayers: 1,
|
||||
},
|
||||
mipLevelCount: 1,
|
||||
sampleCount: 1,
|
||||
viewFormats: [],
|
||||
},
|
||||
};
|
||||
depth.parameters.format = { kind: "string", value: "depth32_float" };
|
||||
for (const [id, name] of [["ground", "ground_plane"], ["pbr", "gltf_standard"], ["pbr_double", "gltf_standard_double_sided"]])
|
||||
authored.nodes.find((node) => node.id === id).parameters.pipeline = { kind: "string", value: name };
|
||||
await root.setState(authored);
|
||||
}
|
||||
export async function createRenderGraphEditor(canvas) {
|
||||
const allocateId = createNodeIdAllocator();
|
||||
let root, view, menu, destroying, dead = false;
|
||||
const requestAddNode = Object.assign(async (request, point, isCurrent = () => true) => {
|
||||
let typeId;
|
||||
try { typeId = await menu?.open(point); } catch (error) { if (!dead && isCurrent()) console.error(error); return; }
|
||||
if (dead || !isCurrent() || !root || !view) return;
|
||||
const alive = () => !dead && isCurrent();
|
||||
try { await spawnRequestedNode(root, view, request, typeId, allocateId, alive); } catch (error) { if (!dead) console.error(error); }
|
||||
}, { close: () => menu?.close() });
|
||||
let root,
|
||||
view,
|
||||
menu,
|
||||
destroying,
|
||||
dead = false;
|
||||
const requestAddNode = Object.assign(
|
||||
async (request, point, isCurrent = () => true) => {
|
||||
let typeId;
|
||||
try {
|
||||
typeId = await menu?.open(point);
|
||||
} catch (error) {
|
||||
if (!dead && isCurrent()) console.error(error);
|
||||
return;
|
||||
}
|
||||
if (dead || !isCurrent() || !root || !view) return;
|
||||
const alive = () => !dead && isCurrent();
|
||||
try {
|
||||
await spawnRequestedNode(
|
||||
root,
|
||||
view,
|
||||
request,
|
||||
typeId,
|
||||
allocateId,
|
||||
alive,
|
||||
);
|
||||
} catch (error) {
|
||||
if (!dead) console.error(error);
|
||||
}
|
||||
},
|
||||
{ close: () => menu?.close() },
|
||||
);
|
||||
const host = prepareBrowserHost(canvas, { requestAddNode });
|
||||
const destroy = () =>
|
||||
(destroying ??= (async () => {
|
||||
|
||||
+56
-258
@@ -1,278 +1,76 @@
|
||||
const input = (node, socket) => ({ node, socket });
|
||||
const node = (id, key, parameters = {}, inputs = {}) => ({
|
||||
id,
|
||||
state: "enabled",
|
||||
executor: { key, version: 1 },
|
||||
parameters,
|
||||
inputs,
|
||||
id, state: "enabled", executor: { key, version: 1 }, parameters, inputs,
|
||||
});
|
||||
const texture = (format) => ({
|
||||
const texture = (format, scale = 1) => ({
|
||||
texture: {
|
||||
dimension: "d2",
|
||||
format,
|
||||
extent: {
|
||||
kind: "surface_relative",
|
||||
width: { numerator: 1, denominator: 1 },
|
||||
height: { numerator: 1, denominator: 1 },
|
||||
depthOrArrayLayers: 1,
|
||||
},
|
||||
mipLevelCount: 1,
|
||||
sampleCount: 1,
|
||||
viewFormats: [],
|
||||
dimension: "d2", format,
|
||||
extent: { kind: "surface_relative", width: { numerator: 1, denominator: scale }, height: { numerator: 1, denominator: scale }, depthOrArrayLayers: 1 },
|
||||
mipLevelCount: 1, sampleCount: 1, viewFormats: [],
|
||||
},
|
||||
residency: "transient",
|
||||
});
|
||||
const direct = (graphId, clearColor) => Object.freeze({
|
||||
schemaVersion: 2,
|
||||
graphId,
|
||||
revision: 1,
|
||||
nodes: [
|
||||
node("surface", "surface_target"),
|
||||
node("depth", "texture_spec", texture("depth32_float")),
|
||||
node("scene", "scene_table"),
|
||||
node("visible", "visibility_flags", {}, { scene: input("scene", "scene") }),
|
||||
node("query", "mesh_query", {
|
||||
filters: [
|
||||
{ flag: "isVisible", predicate: "required_true" },
|
||||
{ flag: "isFrustumCulled", predicate: "any" },
|
||||
],
|
||||
}, { scene: input("scene", "scene"), isVisible: input("visible", "flags") }),
|
||||
node("depth_config", "depth_stencil_config", {
|
||||
depthCompare: "less_equal",
|
||||
depthWriteEnabled: true,
|
||||
clearDepth: 1,
|
||||
}),
|
||||
node("forward", "legacy_forward", { clearColor }, {
|
||||
scene: input("scene", "scene"),
|
||||
draws: input("query", "draws"),
|
||||
colorTarget: input("surface", "surface"),
|
||||
depthTarget: input("depth", "spec"),
|
||||
depthStencil: input("depth_config", "config"),
|
||||
}),
|
||||
node("present", "present", {}, { surface: input("forward", "color") }),
|
||||
],
|
||||
});
|
||||
const scene = (colorTarget, clearColor = [0.015, 0.02, 0.03, 1]) => [
|
||||
node("hdr", "texture", texture("rgba16_float")),
|
||||
node("depth", "texture", texture("depth32_float")),
|
||||
node("mesh", "mesh"),
|
||||
node("query", "mesh_query", { visiblePredicate: "required_true", visibleDefault: true, frustumCulledPredicate: "any", frustumCulledDefault: false }, { mesh: input("mesh", "mesh"), isVisible: input("mesh", "isVisible") }),
|
||||
node("registry", "pipeline_registry", {}, { pipelineIndices: input("mesh", "pipelineIndices") }),
|
||||
node("ground", "pipeline", { pipeline: "ground_plane", depthCompare: "less_equal", depthWriteEnabled: true, clearDepth: 1, clearColor }, { mesh: input("mesh", "mesh"), draws: input("query", "draws"), activation: input("registry", "activation"), colorTarget: input(colorTarget, "texture"), depthTarget: input("depth", "texture") }),
|
||||
node("pbr", "pipeline", { pipeline: "gltf_standard", depthCompare: "less_equal", depthWriteEnabled: true, clearDepth: 1, clearColor }, { mesh: input("mesh", "mesh"), draws: input("query", "draws"), activation: input("registry", "activation"), colorTarget: input("ground", "color"), depthTarget: input("ground", "depth") }),
|
||||
node("pbr_double", "pipeline", { pipeline: "gltf_standard_double_sided", depthCompare: "less_equal", depthWriteEnabled: true, clearDepth: 1, clearColor }, { mesh: input("mesh", "mesh"), draws: input("query", "draws"), activation: input("registry", "activation"), colorTarget: input("pbr", "color"), depthTarget: input("pbr", "depth") }),
|
||||
];
|
||||
const graph = (graphId, nodes) => Object.freeze({ schemaVersion: 2, graphId, revision: 1, nodes });
|
||||
const direct = (graphId, clearColor) => graph(graphId, [
|
||||
node("ldr", "texture", texture("rgba8_unorm")),
|
||||
...scene("ldr", clearColor).filter((item) => item.id !== "hdr"),
|
||||
node("frame_out", "frame_out", {}, { color: input("pbr_double", "color") }),
|
||||
]);
|
||||
export const midnight = direct("preset_midnight", [0.015, 0.06, 0.18, 1]);
|
||||
export const ember = direct("preset_ember", [0.18, 0.035, 0.012, 1]);
|
||||
export const hdr = Object.freeze({
|
||||
schemaVersion: 2,
|
||||
graphId: "preset_hdr_fullscreen",
|
||||
revision: 1,
|
||||
nodes: [
|
||||
node("surface", "surface_target"),
|
||||
node("hdr", "texture_spec", texture("rgba16_float")),
|
||||
node("depth", "texture_spec", texture("depth32_float")),
|
||||
node("scene", "scene_table"),
|
||||
node("visible", "visibility_flags", {}, { scene: input("scene", "scene") }),
|
||||
node(
|
||||
"query",
|
||||
"mesh_query",
|
||||
{
|
||||
filters: [
|
||||
{ flag: "isVisible", predicate: "required_true" },
|
||||
{ flag: "isFrustumCulled", predicate: "any" },
|
||||
],
|
||||
},
|
||||
{ scene: input("scene", "scene"), isVisible: input("visible", "flags") },
|
||||
),
|
||||
node("depth_config", "depth_stencil_config", {
|
||||
depthCompare: "less_equal",
|
||||
depthWriteEnabled: true,
|
||||
clearDepth: 1,
|
||||
}),
|
||||
node(
|
||||
"forward",
|
||||
"legacy_forward",
|
||||
{ clearColor: [0.015, 0.02, 0.03, 1] },
|
||||
{
|
||||
scene: input("scene", "scene"),
|
||||
draws: input("query", "draws"),
|
||||
colorTarget: input("hdr", "spec"),
|
||||
depthTarget: input("depth", "spec"),
|
||||
depthStencil: input("depth_config", "config"),
|
||||
},
|
||||
),
|
||||
node(
|
||||
"copy",
|
||||
"fullscreen_copy",
|
||||
{},
|
||||
{
|
||||
source: input("forward", "color"),
|
||||
colorTarget: input("surface", "surface"),
|
||||
},
|
||||
),
|
||||
node("present", "present", {}, { surface: input("copy", "color") }),
|
||||
],
|
||||
});
|
||||
export const culling = Object.freeze((() => {
|
||||
const graph = structuredClone(hdr);
|
||||
graph.graphId = "preset_gpu_culling";
|
||||
graph.nodes.splice(
|
||||
5,
|
||||
0,
|
||||
node("aabbs", "local_aabb_buffer", {}, { scene: input("scene", "scene") }),
|
||||
node("frustum", "camera_frustum"),
|
||||
node("cull", "frustum_cull", {}, {
|
||||
scene: input("scene", "scene"),
|
||||
localAabbs: input("aabbs", "localAabbs"),
|
||||
frustum: input("frustum", "frustum"),
|
||||
}),
|
||||
);
|
||||
const query = graph.nodes.find((x) => x.id === "query");
|
||||
query.parameters.filters[1].predicate = "required_false";
|
||||
query.inputs.isFrustumCulled = input("cull", "flags");
|
||||
return graph;
|
||||
export const hdr = graph("preset_hdr_fullscreen", [
|
||||
...scene("hdr"),
|
||||
node("frame_out", "frame_out", {}, { color: input("pbr_double", "color") }),
|
||||
]);
|
||||
export const culling = graph("preset_gpu_culling", (() => {
|
||||
const nodes = structuredClone(hdr.nodes);
|
||||
nodes.splice(3, 0, node("cull", "frustum_cull", { camera: "active" }, { mesh: input("mesh", "mesh"), localAabbs: input("mesh", "localAabbs") }));
|
||||
const query = nodes.find((item) => item.id === "query");
|
||||
query.parameters.frustumCulledPredicate = "required_false";
|
||||
query.inputs.isFrustumCulled = input("cull", "isFrustumCulled");
|
||||
return nodes;
|
||||
})());
|
||||
const postPreset = (graphId, kind) => {
|
||||
const nodes = hdr.nodes.slice(0, 8).map((x) => structuredClone(x));
|
||||
if (kind === "tone")
|
||||
nodes.push(
|
||||
node(
|
||||
"tone",
|
||||
"tone_map",
|
||||
{ exposure: 1 },
|
||||
{
|
||||
source: input("forward", "color"),
|
||||
colorTarget: input("surface", "surface"),
|
||||
},
|
||||
),
|
||||
);
|
||||
const nodes = [node("ldr", "texture", texture("rgba8_unorm")), ...scene("hdr")];
|
||||
let source = "pbr_double";
|
||||
if (kind === "edges") {
|
||||
nodes.splice(1, 0, node("edge_hdr", "texture_spec", texture("rgba16_float")));
|
||||
nodes.push(
|
||||
node(
|
||||
"edges",
|
||||
"luminance_edge",
|
||||
{ strength: 2 },
|
||||
{
|
||||
source: input("forward", "color"),
|
||||
colorTarget: input("edge_hdr", "spec"),
|
||||
},
|
||||
),
|
||||
);
|
||||
nodes.push(
|
||||
node(
|
||||
"tone",
|
||||
"tone_map",
|
||||
{ exposure: 1 },
|
||||
{
|
||||
source: input("edges", "color"),
|
||||
colorTarget: input("surface", "surface"),
|
||||
},
|
||||
),
|
||||
);
|
||||
nodes.splice(1, 0, node("edge_hdr", "texture", texture("rgba16_float")));
|
||||
nodes.push(node("edges", "luminance_edge", { strength: 2 }, { source: input(source, "color"), colorTarget: input("edge_hdr", "texture") }));
|
||||
source = "edges";
|
||||
}
|
||||
if (kind === "bloom" || kind === "combined") {
|
||||
const half = {
|
||||
texture: {
|
||||
...texture("rgba16_float").texture,
|
||||
extent: {
|
||||
kind: "surface_relative",
|
||||
width: { numerator: 1, denominator: 2 },
|
||||
height: { numerator: 1, denominator: 2 },
|
||||
depthOrArrayLayers: 1,
|
||||
},
|
||||
},
|
||||
residency: "transient",
|
||||
};
|
||||
nodes.splice(
|
||||
1,
|
||||
0,
|
||||
node("half_a", "texture_spec", structuredClone(half)),
|
||||
node("half_b", "texture_spec", structuredClone(half)),
|
||||
node("half_c", "texture_spec", structuredClone(half)),
|
||||
node("composite_hdr", "texture_spec", texture("rgba16_float")),
|
||||
);
|
||||
nodes.splice(1, 0,
|
||||
node("half_a", "texture", texture("rgba16_float", 2)), node("half_b", "texture", texture("rgba16_float", 2)),
|
||||
node("half_c", "texture", texture("rgba16_float", 2)), node("composite_hdr", "texture", texture("rgba16_float")));
|
||||
nodes.push(
|
||||
node(
|
||||
"extract",
|
||||
"bloom_extract",
|
||||
{ threshold: 1, knee: 0.5 },
|
||||
{
|
||||
source: input("forward", "color"),
|
||||
colorTarget: input("half_a", "spec"),
|
||||
},
|
||||
),
|
||||
node("extract", "bloom_extract", { threshold: 1, knee: 0.5 }, { source: input("pbr_double", "color"), colorTarget: input("half_a", "texture") }),
|
||||
node("blur_h", "bloom_blur", { direction: [1, 0], radius: 1 }, { source: input("extract", "color"), colorTarget: input("half_b", "texture") }),
|
||||
node("blur_v", "bloom_blur", { direction: [0, 1], radius: 1 }, { source: input("blur_h", "color"), colorTarget: input("half_c", "texture") }),
|
||||
node("composite", "bloom_composite", { intensity: 0.8 }, { source: input("pbr_double", "color"), bloom: input("blur_v", "color"), colorTarget: input("composite_hdr", "texture") }),
|
||||
);
|
||||
nodes.push(
|
||||
node(
|
||||
"blur_h",
|
||||
"bloom_blur",
|
||||
{ direction: [1, 0], radius: 1 },
|
||||
{
|
||||
source: input("extract", "color"),
|
||||
colorTarget: input("half_b", "spec"),
|
||||
},
|
||||
),
|
||||
);
|
||||
nodes.push(
|
||||
node(
|
||||
"blur_v",
|
||||
"bloom_blur",
|
||||
{ direction: [0, 1], radius: 1 },
|
||||
{
|
||||
source: input("blur_h", "color"),
|
||||
colorTarget: input("half_c", "spec"),
|
||||
},
|
||||
),
|
||||
);
|
||||
nodes.push(
|
||||
node(
|
||||
"composite",
|
||||
"bloom_composite",
|
||||
{ intensity: 0.8 },
|
||||
{
|
||||
source: input("forward", "color"),
|
||||
bloom: input("blur_v", "color"),
|
||||
colorTarget: input("composite_hdr", "spec"),
|
||||
},
|
||||
),
|
||||
);
|
||||
let toneSource = "composite";
|
||||
source = "composite";
|
||||
if (kind === "combined") {
|
||||
nodes.splice(1, 0, node("edge_hdr", "texture_spec", texture("rgba16_float")));
|
||||
nodes.push(
|
||||
node(
|
||||
"edges",
|
||||
"luminance_edge",
|
||||
{ strength: 2 },
|
||||
{
|
||||
source: input("composite", "color"),
|
||||
colorTarget: input("edge_hdr", "spec"),
|
||||
},
|
||||
),
|
||||
);
|
||||
toneSource = "edges";
|
||||
nodes.splice(1, 0, node("edge_hdr", "texture", texture("rgba16_float")));
|
||||
nodes.push(node("edges", "luminance_edge", { strength: 2 }, { source: input(source, "color"), colorTarget: input("edge_hdr", "texture") }));
|
||||
source = "edges";
|
||||
}
|
||||
nodes.push(
|
||||
node(
|
||||
"tone",
|
||||
"tone_map",
|
||||
{ exposure: 1 },
|
||||
{
|
||||
source: input(toneSource, "color"),
|
||||
colorTarget: input("surface", "surface"),
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
const last = nodes.at(-1);
|
||||
nodes.push(
|
||||
node("present", "present", {}, { surface: input(last.id, "color") }),
|
||||
);
|
||||
return Object.freeze({ schemaVersion: 2, graphId, revision: 1, nodes });
|
||||
nodes.push(node("tone", "tone_map", { exposure: 1 }, { source: input(source, "color"), colorTarget: input("ldr", "texture") }));
|
||||
nodes.push(node("frame_out", "frame_out", {}, { color: input("tone", "color") }));
|
||||
return graph(graphId, nodes);
|
||||
};
|
||||
export const tone = postPreset("preset_tone", "tone"),
|
||||
edges = postPreset("preset_edges", "edges"),
|
||||
bloom = postPreset("preset_bloom", "bloom"),
|
||||
combined = postPreset("preset_combined", "combined");
|
||||
export const renderGraphPresets = Object.freeze({
|
||||
midnight,
|
||||
ember,
|
||||
hdr,
|
||||
culling,
|
||||
tone,
|
||||
edges,
|
||||
bloom,
|
||||
combined,
|
||||
});
|
||||
export const tone = postPreset("preset_tone", "tone");
|
||||
export const edges = postPreset("preset_edges", "edges");
|
||||
export const bloom = postPreset("preset_bloom", "bloom");
|
||||
export const combined = postPreset("preset_combined", "combined");
|
||||
export const renderGraphPresets = Object.freeze({ midnight, ember, hdr, culling, tone, edges, bloom, combined });
|
||||
|
||||
Reference in New Issue
Block a user