Files
fn_registry/cpp/functions/gfx/dag_catalog.cpp
T
egutierrez dfbe26da01 fix(shaders_lab): drop zone no longer eats node/slider input + inline tests
The previous InvisibleButton captured mouse events, so you could drag
from the Functions palette into the canvas, but node dragging and
slider interaction inside the canvas stopped working.

Fix: watch the global drag-drop payload without an explicit target. When
the mouse releases LMB over the DAG window with a "DAG_NODE_TYPE"
payload active, queue an add at that canvas position. No button, no
capture.

Tests (compiled standalone with preprocessor defines):
- dag_compile: 6/6 asserts (empty, single gen, op chain, multi-source
  blend, Output-driven fragColor, unconnected-Output fallback).
- dag_catalog: 8/8 asserts (uniqueness, per-kind input invariants,
  exactly one Output, body_glsl present & returns, control param
  indices valid).
Build with:
  g++ -std=c++17 -Icpp/functions -DDAG_COMPILE_TEST cpp/functions/gfx/dag_compile.cpp cpp/functions/gfx/dag_catalog.cpp -o /tmp/dag_compile_test
  g++ -std=c++17 -Icpp/functions -DDAG_CATALOG_TEST cpp/functions/gfx/dag_catalog.cpp -o /tmp/dag_catalog_test

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 22:24:39 +02:00

335 lines
14 KiB
C++

#include "gfx/dag_catalog.h"
#include <string>
namespace fn::gfx {
static const std::vector<DagNodeDef>& build_catalog() {
static std::vector<DagNodeDef> catalog = []() {
std::vector<DagNodeDef> v;
// ── Gen: solid ────────────────────────────────────────────────
{
DagNodeDef n;
n.name = "solid";
n.label = "solid";
n.desc = "color constante";
n.kind = DagKind::Gen;
n.num_inputs = 0;
n.param_names = {"r", "g", "b", ""};
n.param_defaults = {0.35f, 0.25f, 0.55f, 0.0f};
n.controls = {
{ DagControl::Kind::Color, "color", {0, 1, 2}, 0.0f, 1.0f, 0.0f },
};
n.body_glsl = [](int idx) -> std::string {
std::string i = std::to_string(idx);
return " vec4 p = u_params[" + i + "];\n"
" return vec4(p.x, p.y, p.z, 1.0);";
};
v.push_back(std::move(n));
}
// ── Gen: gradient ─────────────────────────────────────────────
{
DagNodeDef n;
n.name = "gradient";
n.label = "gradient";
n.desc = "gradiente direccional";
n.kind = DagKind::Gen;
n.num_inputs = 0;
n.param_names = {"angle", "hue", "", ""};
n.param_defaults = {0.8f, 0.5f, 0.0f, 0.0f};
n.controls = {
{ DagControl::Kind::Slider, "angulo", {0, -1, -1}, 0.0f, 6.2832f, 0.01f },
{ DagControl::Kind::Slider, "tono", {1, -1, -1}, 0.0f, 1.0f, 0.01f },
};
n.body_glsl = [](int idx) -> std::string {
std::string i = std::to_string(idx);
return " vec4 p = u_params[" + i + "];\n"
" vec2 dir = vec2(cos(p.x), sin(p.x));\n"
" float t = dot(uv - 0.5, dir) + 0.5;\n"
" vec3 col = 0.5 + 0.5 * cos(6.28318 * (p.y + vec3(0.0, 0.33, 0.67) + t));\n"
" return vec4(col, 1.0);";
};
v.push_back(std::move(n));
}
// ── Gen: plasma ───────────────────────────────────────────────
{
DagNodeDef n;
n.name = "plasma";
n.label = "plasma";
n.desc = "onda trigonometrica";
n.kind = DagKind::Gen;
n.num_inputs = 0;
n.param_names = {"speed", "scale", "", ""};
n.param_defaults = {1.0f, 2.0f, 0.0f, 0.0f};
n.controls = {
{ DagControl::Kind::Slider, "velocidad", {0, -1, -1}, 0.0f, 3.0f, 0.01f },
{ DagControl::Kind::Slider, "escala", {1, -1, -1}, 0.5f, 10.0f, 0.1f },
};
n.body_glsl = [](int idx) -> std::string {
std::string i = std::to_string(idx);
return " vec4 p = u_params[" + i + "];\n"
" vec3 col = 0.5 + 0.5 * cos(u_time * p.x + uv.xyx * p.y + vec3(0.0, 2.0, 4.0));\n"
" return vec4(col, 1.0);";
};
v.push_back(std::move(n));
}
// ── Op: circle ────────────────────────────────────────────────
// Reclassified as Op (num_inputs=1): composites circle over input 'a'
{
DagNodeDef n;
n.name = "circle";
n.label = "circle";
n.desc = "sdf de circulo (composita sobre input)";
n.kind = DagKind::Op;
n.num_inputs = 1;
n.param_names = {"cx", "cy", "radius", "soft"};
n.param_defaults = {0.0f, 0.0f, 0.35f, 0.01f};
n.controls = {
{ DagControl::Kind::XY, "centro", {0, 1, -1}, -0.8f, 0.8f, 0.01f },
{ DagControl::Kind::Slider, "radio", {2, -1, -1}, 0.0f, 1.0f, 0.01f },
{ DagControl::Kind::Slider, "suavidad", {3, -1, -1}, 0.001f, 0.1f, 0.001f },
};
n.body_glsl = [](int idx) -> std::string {
std::string i = std::to_string(idx);
return " vec4 p = u_params[" + i + "];\n"
" float aspect = u_resolution.x / u_resolution.y;\n"
" vec2 pos = vec2((uv.x - 0.5) * aspect - p.x, uv.y - 0.5 - p.y);\n"
" float d = length(pos) - p.z;\n"
" float fill = smoothstep(p.w, -p.w, d);\n"
" return mix(a, vec4(1.0), fill);";
};
v.push_back(std::move(n));
}
// ── Op: invert ────────────────────────────────────────────────
{
DagNodeDef n;
n.name = "invert";
n.label = "invert";
n.desc = "1 - rgb";
n.kind = DagKind::Op;
n.num_inputs = 1;
n.param_names = {"", "", "", ""};
n.param_defaults = {0.0f, 0.0f, 0.0f, 0.0f};
n.controls = {};
n.body_glsl = [](int /*idx*/) -> std::string {
return " return vec4(1.0 - a.rgb, a.a);";
};
v.push_back(std::move(n));
}
// ── Op: gamma ─────────────────────────────────────────────────
{
DagNodeDef n;
n.name = "gamma";
n.label = "gamma";
n.desc = "pow(rgb, gamma)";
n.kind = DagKind::Op;
n.num_inputs = 1;
n.param_names = {"gamma", "", "", ""};
n.param_defaults = {1.0f, 0.0f, 0.0f, 0.0f};
n.controls = {
{ DagControl::Kind::Slider, "gamma", {0, -1, -1}, 0.1f, 4.0f, 0.01f },
};
n.body_glsl = [](int idx) -> std::string {
std::string i = std::to_string(idx);
return " vec4 p = u_params[" + i + "];\n"
" return vec4(pow(a.rgb, vec3(1.0 / max(p.x, 0.001))), a.a);";
};
v.push_back(std::move(n));
}
// ── Op: hueShift ──────────────────────────────────────────────
{
DagNodeDef n;
n.name = "hueShift";
n.label = "hue shift";
n.desc = "rotar matiz";
n.kind = DagKind::Op;
n.num_inputs = 1;
n.param_names = {"h", "", "", ""};
n.param_defaults = {0.0f, 0.0f, 0.0f, 0.0f};
n.controls = {
{ DagControl::Kind::Slider, "h", {0, -1, -1}, 0.0f, 1.0f, 0.01f },
};
n.body_glsl = [](int idx) -> std::string {
std::string i = std::to_string(idx);
return " vec4 p = u_params[" + i + "];\n"
" float ang = 6.28318 * p.x;\n"
" float ca = cos(ang), sa = sin(ang);\n"
" mat3 hueMat = mat3(\n"
" vec3(0.299 + 0.701 * ca + 0.168 * sa, 0.587 - 0.587 * ca + 0.330 * sa, 0.114 - 0.114 * ca - 0.497 * sa),\n"
" vec3(0.299 - 0.299 * ca - 0.328 * sa, 0.587 + 0.413 * ca + 0.035 * sa, 0.114 - 0.114 * ca + 0.292 * sa),\n"
" vec3(0.299 - 0.300 * ca + 1.250 * sa, 0.587 - 0.588 * ca - 1.050 * sa, 0.114 + 0.886 * ca - 0.203 * sa)\n"
" );\n"
" return vec4(clamp(hueMat * a.rgb, 0.0, 1.0), a.a);";
};
v.push_back(std::move(n));
}
// ── Blend: mix ────────────────────────────────────────────────
{
DagNodeDef n;
n.name = "blend_mix";
n.label = "mix";
n.desc = "interpolacion mix(a, b, t)";
n.kind = DagKind::Blend;
n.num_inputs = 2;
n.param_names = {"t", "", "", ""};
n.param_defaults = {0.5f, 0.0f, 0.0f, 0.0f};
n.controls = {
{ DagControl::Kind::Slider, "t", {0, -1, -1}, 0.0f, 1.0f, 0.01f },
};
n.body_glsl = [](int idx) -> std::string {
std::string i = std::to_string(idx);
return " vec4 p = u_params[" + i + "];\n"
" return mix(a, b, p.x);";
};
v.push_back(std::move(n));
}
// ── Blend: multiply ───────────────────────────────────────────
{
DagNodeDef n;
n.name = "blend_multiply";
n.label = "multiply";
n.desc = "a * b";
n.kind = DagKind::Blend;
n.num_inputs = 2;
n.param_names = {"", "", "", ""};
n.param_defaults = {0.0f, 0.0f, 0.0f, 0.0f};
n.controls = {};
n.body_glsl = [](int /*idx*/) -> std::string {
return " return vec4(a.rgb * b.rgb, a.a);";
};
v.push_back(std::move(n));
}
// ── Blend: screen ─────────────────────────────────────────────
{
DagNodeDef n;
n.name = "blend_screen";
n.label = "screen";
n.desc = "1 - (1-a)(1-b)";
n.kind = DagKind::Blend;
n.num_inputs = 2;
n.param_names = {"", "", "", ""};
n.param_defaults = {0.0f, 0.0f, 0.0f, 0.0f};
n.controls = {};
n.body_glsl = [](int /*idx*/) -> std::string {
return " return vec4(1.0 - (1.0 - a.rgb) * (1.0 - b.rgb), a.a);";
};
v.push_back(std::move(n));
}
// ── Output (sink — drives fragColor) ─────────────────────────
{
DagNodeDef n;
n.name = "output";
n.label = "Output";
n.desc = "canvas DAG output";
n.kind = DagKind::Output;
n.num_inputs = 1;
n.param_names = {"", "", "", ""};
n.param_defaults = {0.0f, 0.0f, 0.0f, 0.0f};
n.controls = {};
n.body_glsl = [](int) -> std::string { return ""; };
v.push_back(std::move(n));
}
return v;
}();
return catalog;
}
const std::vector<DagNodeDef>& dag_catalog() {
return build_catalog();
}
const DagNodeDef* dag_find(const std::string& name) {
for (const auto& n : dag_catalog()) {
if (n.name == name) return &n;
}
return nullptr;
}
} // namespace fn::gfx
#ifdef DAG_CATALOG_TEST
#include <cassert>
#include <cstdio>
#include <set>
int main() {
using namespace fn::gfx;
const auto& cat = dag_catalog();
// 1. Catalog not empty
assert(!cat.empty());
// 2. All node names are unique
std::set<std::string> names;
for (const auto& n : cat) {
assert(names.insert(n.name).second && "duplicate node name");
}
// 3. Invariants by kind
int gen_count = 0, op_count = 0, blend_count = 0, output_count = 0;
for (const auto& n : cat) {
switch (n.kind) {
case DagKind::Gen:
assert(n.num_inputs == 0 && "Gen must have 0 inputs");
++gen_count; break;
case DagKind::Op:
assert(n.num_inputs >= 1 && "Op must have >= 1 input");
++op_count; break;
case DagKind::Blend:
assert(n.num_inputs >= 2 && "Blend must have >= 2 inputs");
++blend_count; break;
case DagKind::Output:
assert(n.num_inputs == 1 && "Output must have exactly 1 input");
++output_count; break;
}
assert(n.num_inputs >= 0 && n.num_inputs <= 4);
assert(n.body_glsl && "body_glsl must be set");
}
// 4. Exactly one Output in the catalog
assert(output_count == 1 && "catalog must declare exactly one Output node");
// 5. At least one Gen and one Op
assert(gen_count >= 1);
assert(op_count >= 1);
assert(blend_count >= 1);
// 6. dag_find works and returns nullptr for unknown names
assert(dag_find("output") != nullptr);
assert(dag_find("plasma") != nullptr);
assert(dag_find("__nonexistent__") == nullptr);
// 7. Every non-Output body emits non-empty GLSL
for (const auto& n : cat) {
if (n.kind == DagKind::Output) continue;
auto body = n.body_glsl(0);
assert(!body.empty() && "non-Output nodes must emit body");
// sanity: should contain a return statement
assert(body.find("return") != std::string::npos);
}
// 8. Control param indices stay within 0..3
for (const auto& n : cat) {
for (const auto& c : n.controls) {
for (int idx : c.param_idx) {
assert(idx >= -1 && idx < 4);
}
}
}
std::printf("dag_catalog: 8/8 asserts passed (%zu nodes)\n", cat.size());
return 0;
}
#endif