feat(shaders_lab): Output node + Functions palette with drag-drop

- DagKind::Output (new enum): terminal sink; compiler wires fragColor to its source_ids[0]
- dag_catalog: "output" node (1 input, red)
- dag_compile: skips Output in node_<i> emission; final fragColor resolves from Output's connection
- dag_node_editor: no more Add button; drops "DAG_NODE_TYPE" payloads at mouse canvas position; Output cannot be deleted; Output has no output pin
- dag_palette (new fn): Functions window with grouped, draggable node cards
- main.cpp: "Functions" window added; ensure_dag_default seeds plasma + connected Output
This commit is contained in:
2026-04-24 22:16:47 +02:00
parent e91e80bfcf
commit 0be4b29a4b
10 changed files with 253 additions and 106 deletions
+32 -15
View File
@@ -22,11 +22,12 @@ std::string compile_dag_to_glsl(const std::vector<DagStep>& pipeline) {
return out.str();
}
// Emit per-node functions with signatures based on num_inputs
// Emit per-node functions (skip Output: it's a sink, no body)
for (int i = 0; i < n; ++i) {
const DagStep& step = pipeline[static_cast<size_t>(i)];
const DagNodeDef* def = dag_find(step.name);
if (!def) continue;
if (def->kind == DagKind::Output) continue;
int ni = def->num_inputs;
out << "vec4 node_" << i << "(";
@@ -43,34 +44,30 @@ std::string compile_dag_to_glsl(const std::vector<DagStep>& pipeline) {
out << "void main() {\n";
out << " vec2 uv = gl_FragCoord.xy / u_resolution;\n";
int last_valid_out = -1;
int output_idx = -1;
for (int i = 0; i < n; ++i) {
const DagStep& step = pipeline[static_cast<size_t>(i)];
const DagNodeDef* def = dag_find(step.name);
if (!def) {
if (i == 0) {
out << " vec4 out_" << i << " = vec4(0.0, 0.0, 0.0, 1.0);\n";
} else {
out << " vec4 out_" << i << " = out_" << (i - 1) << ";\n";
}
continue;
}
if (!def) continue;
if (def->kind == DagKind::Output) { output_idx = i; continue; }
int ni = def->num_inputs;
// Resolve each input slot
// For slot k: look for source_ids[k] in pipeline[0..i-1]; fallback = prev output
auto resolve = [&](int k) -> std::string {
const std::string& sid = step.source_ids[static_cast<size_t>(k)];
if (!sid.empty()) {
for (int j = 0; j < i; ++j) {
if (pipeline[static_cast<size_t>(j)].id == sid) {
const DagNodeDef* jdef = dag_find(pipeline[static_cast<size_t>(j)].name);
if (jdef && jdef->kind == DagKind::Output) continue; // Output has no out_j
return "out_" + std::to_string(j);
}
}
}
// fallback
if (i == 0) return "vec4(0.0, 0.0, 0.0, 1.0)";
return "out_" + std::to_string(i - 1);
if (last_valid_out < 0) return "vec4(0.0, 0.0, 0.0, 1.0)";
return "out_" + std::to_string(last_valid_out);
};
out << " vec4 out_" << i << " = node_" << i << "(";
@@ -80,9 +77,29 @@ std::string compile_dag_to_glsl(const std::vector<DagStep>& pipeline) {
}
if (ni > 0) out << ", ";
out << "uv);\n";
last_valid_out = i;
}
// Resolve fragColor: if there's an Output node with a connection, use that; else fallback.
auto seed = [&]() { out << " fragColor = vec4(0.04, 0.04, 0.06, 1.0);\n"; };
if (output_idx >= 0) {
const std::string& sid = pipeline[static_cast<size_t>(output_idx)].source_ids[0];
int src = -1;
if (!sid.empty()) {
for (int j = 0; j < output_idx; ++j) {
if (pipeline[static_cast<size_t>(j)].id == sid) { src = j; break; }
}
}
if (src >= 0) out << " fragColor = out_" << src << ";\n";
else seed();
} else if (last_valid_out >= 0) {
out << " fragColor = out_" << last_valid_out << ";\n";
} else {
seed();
}
out << " fragColor = out_" << (n - 1) << ";\n";
out << "}\n";
return out.str();