Files
fn_registry/cpp/functions/gfx/dag_catalog.cpp
T
egutierrez 53402d84d5 docs(issues): marcar 0025 y 0026 como completados + WIP master
Wave 1 de parallel-fix-issues integrada a master:
- 0025: text_editor_cpp_core + file_watcher_cpp_core
- 0026: gl_texture_load_cpp_gfx (vendor: stb_image v2.30)

Ademas se commitea WIP previo de master que estaba sin commitear (cambios
en shaders_lab, dag_*, framework, tokens, kpi_card, gl_loader.md, etc.)
para dejar HEAD buildable.

Notas:
- Algunos deps del gallery (button.cpp, toolbar.cpp, modal_dialog.cpp...)
  siguen UNTRACKED — gating con FN_BUILD_GALLERY=ON (default OFF) para
  que master build (sin flag) no los necesite.
- Build OK con y sin flag. fn index registra 904 functions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 21:14:15 +02:00

610 lines
28 KiB
C++

#include "gfx/dag_catalog.h"
#include <algorithm>
#include <string>
namespace fn::gfx {
static std::vector<DagNodeDef>& mutable_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};
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};
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};
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 = {};
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};
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};
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};
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 = {};
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 = {};
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));
}
// ── Gen: checker ──────────────────────────────────────────────
{
DagNodeDef n;
n.name = "checker";
n.label = "checker";
n.desc = "tablero animado";
n.kind = DagKind::Gen;
n.num_inputs = 0;
n.param_names = {"scale", "rotation", "hue", "speed"};
n.param_defaults = {8.0f, 0.0f, 0.55f, 0.2f};
n.controls = {
{ DagControl::Kind::Slider, "escala", {0, -1, -1}, 1.0f, 32.0f, 0.1f },
{ DagControl::Kind::Slider, "rotacion", {1, -1, -1}, 0.0f, 6.2832f, 0.01f },
{ DagControl::Kind::Slider, "tono", {2, -1, -1}, 0.0f, 1.0f, 0.01f },
{ DagControl::Kind::Slider, "velocidad",{3, -1, -1}, 0.0f, 3.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 = p.y + u_time * p.w * 0.2;\n"
" float ca = cos(ang), sa = sin(ang);\n"
" vec2 q = uv - 0.5;\n"
" q = vec2(ca*q.x - sa*q.y, sa*q.x + ca*q.y) * p.x + 0.5;\n"
" vec2 cell = floor(q);\n"
" float c = mod(cell.x + cell.y, 2.0);\n"
" vec3 ca_col = 0.5 + 0.5 * cos(6.28318 * (p.z + vec3(0.0, 0.33, 0.67)));\n"
" vec3 cb_col = 0.5 + 0.5 * cos(6.28318 * (p.z + 0.5 + vec3(0.0, 0.33, 0.67)));\n"
" return vec4(mix(cb_col, ca_col, c), 1.0);";
};
v.push_back(std::move(n));
}
// ── Gen: stripes ──────────────────────────────────────────────
{
DagNodeDef n;
n.name = "stripes";
n.label = "stripes";
n.desc = "bandas direccionales";
n.kind = DagKind::Gen;
n.num_inputs = 0;
n.param_names = {"angle", "freq", "phase_speed", "hue"};
n.param_defaults = {0.785f, 12.0f, 1.0f, 0.6f};
n.controls = {
{ DagControl::Kind::Slider, "angulo", {0, -1, -1}, 0.0f, 6.2832f, 0.01f },
{ DagControl::Kind::Slider, "frecuencia", {1, -1, -1}, 1.0f, 64.0f, 0.5f },
{ DagControl::Kind::Slider, "velocidad", {2, -1, -1}, 0.0f, 5.0f, 0.01f },
{ DagControl::Kind::Slider, "tono", {3, -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) * p.y + u_time * p.z;\n"
" float s = 0.5 + 0.5 * sin(t);\n"
" vec3 col = 0.5 + 0.5 * cos(6.28318 * (p.w + s + vec3(0.0, 0.33, 0.67)));\n"
" return vec4(col, 1.0);";
};
v.push_back(std::move(n));
}
// ── Gen: dots ─────────────────────────────────────────────────
{
DagNodeDef n;
n.name = "dots";
n.label = "dots";
n.desc = "rejilla de puntos";
n.kind = DagKind::Gen;
n.num_inputs = 0;
n.param_names = {"scale", "radius", "soft", "hue"};
n.param_defaults = {16.0f, 0.3f, 0.05f, 0.7f};
n.controls = {
{ DagControl::Kind::Slider, "escala", {0, -1, -1}, 1.0f, 64.0f, 0.5f },
{ DagControl::Kind::Slider, "radio", {1, -1, -1}, 0.0f, 0.5f, 0.01f },
{ DagControl::Kind::Slider, "suavidad", {2, -1, -1}, 0.001f, 0.2f, 0.001f },
{ DagControl::Kind::Slider, "tono", {3, -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 aspect = u_resolution.x / u_resolution.y;\n"
" vec2 q = vec2((uv.x - 0.5) * aspect, uv.y - 0.5) * p.x;\n"
" vec2 cell = fract(q) - 0.5;\n"
" float d = length(cell) - p.y;\n"
" float fill = smoothstep(p.z, -p.z, d);\n"
" vec3 col = 0.5 + 0.5 * cos(6.28318 * (p.w + vec3(0.0, 0.33, 0.67)));\n"
" return vec4(col * fill, 1.0);";
};
v.push_back(std::move(n));
}
// ── Gen: rings ────────────────────────────────────────────────
{
DagNodeDef n;
n.name = "rings";
n.label = "rings";
n.desc = "anillos concentricos";
n.kind = DagKind::Gen;
n.num_inputs = 0;
n.param_names = {"cx", "cy", "freq", "speed"};
n.param_defaults = {0.0f, 0.0f, 30.0f, 2.0f};
n.controls = {
{ DagControl::Kind::XY, "centro", {0, 1, -1}, -0.5f, 0.5f, 0.01f },
{ DagControl::Kind::Slider, "frecuencia",{2, -1, -1}, 1.0f, 100.0f, 0.5f },
{ DagControl::Kind::Slider, "velocidad", {3, -1, -1}, 0.0f, 10.0f, 0.05f },
};
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 q = vec2((uv.x - 0.5) * aspect - p.x, uv.y - 0.5 - p.y);\n"
" float d = length(q);\n"
" float r = 0.5 + 0.5 * sin(d * p.z - u_time * p.w);\n"
" return vec4(vec3(r), 1.0);";
};
v.push_back(std::move(n));
}
// ── Gen: polar_rays ───────────────────────────────────────────
{
DagNodeDef n;
n.name = "polar_rays";
n.label = "polar rays";
n.desc = "rayos radiales";
n.kind = DagKind::Gen;
n.num_inputs = 0;
n.param_names = {"cx", "cy", "count", "speed"};
n.param_defaults = {0.0f, 0.0f, 12.0f, 0.5f};
n.controls = {
{ DagControl::Kind::XY, "centro", {0, 1, -1}, -0.5f, 0.5f, 0.01f },
{ DagControl::Kind::Slider, "rayos", {2, -1, -1}, 1.0f, 64.0f, 1.0f },
{ DagControl::Kind::Slider, "velocidad", {3, -1, -1}, -3.0f, 3.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 aspect = u_resolution.x / u_resolution.y;\n"
" vec2 q = vec2((uv.x - 0.5) * aspect - p.x, uv.y - 0.5 - p.y);\n"
" float a = atan(q.y, q.x);\n"
" float r = 0.5 + 0.5 * sin(a * p.z + u_time * p.w);\n"
" return vec4(vec3(r), 1.0);";
};
v.push_back(std::move(n));
}
// ── Gen: noise_value ──────────────────────────────────────────
{
DagNodeDef n;
n.name = "noise_value";
n.label = "noise value";
n.desc = "value noise 2D";
n.kind = DagKind::Gen;
n.num_inputs = 0;
n.param_names = {"scale", "speed", "hue"};
n.param_defaults = {6.0f, 0.3f, 0.5f};
n.controls = {
{ DagControl::Kind::Slider, "escala", {0, -1, -1}, 0.5f, 32.0f, 0.1f },
{ DagControl::Kind::Slider, "velocidad", {1, -1, -1}, 0.0f, 3.0f, 0.01f },
{ DagControl::Kind::Slider, "tono", {2, -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 q = uv * p.x + u_time * p.y;\n"
" vec2 fl = floor(q);\n"
" vec2 fr = fract(q);\n"
" fr = fr * fr * (3.0 - 2.0 * fr);\n"
" float a = fract(sin(dot(fl + vec2(0.0, 0.0), vec2(12.9898, 78.233))) * 43758.5453);\n"
" float b = fract(sin(dot(fl + vec2(1.0, 0.0), vec2(12.9898, 78.233))) * 43758.5453);\n"
" float c = fract(sin(dot(fl + vec2(0.0, 1.0), vec2(12.9898, 78.233))) * 43758.5453);\n"
" float d = fract(sin(dot(fl + vec2(1.0, 1.0), vec2(12.9898, 78.233))) * 43758.5453);\n"
" float n = mix(mix(a, b, fr.x), mix(c, d, fr.x), fr.y);\n"
" vec3 col = 0.5 + 0.5 * cos(6.28318 * (p.z + n + vec3(0.0, 0.33, 0.67)));\n"
" return vec4(col, 1.0);";
};
v.push_back(std::move(n));
}
// ── Gen: voronoi ──────────────────────────────────────────────
{
DagNodeDef n;
n.name = "voronoi";
n.label = "voronoi";
n.desc = "celdas voronoi";
n.kind = DagKind::Gen;
n.num_inputs = 0;
n.param_names = {"scale", "speed", "hue"};
n.param_defaults = {8.0f, 0.5f, 0.4f};
n.controls = {
{ DagControl::Kind::Slider, "escala", {0, -1, -1}, 1.0f, 32.0f, 0.1f },
{ DagControl::Kind::Slider, "velocidad", {1, -1, -1}, 0.0f, 3.0f, 0.01f },
{ DagControl::Kind::Slider, "tono", {2, -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 q = uv * p.x;\n"
" vec2 fl = floor(q);\n"
" vec2 fr = fract(q);\n"
" float md = 1.0;\n"
" for (int yy = -1; yy <= 1; yy++) {\n"
" for (int xx = -1; xx <= 1; xx++) {\n"
" vec2 nb = vec2(float(xx), float(yy));\n"
" vec2 r = fract(sin(dot(fl + nb, vec2(127.1, 311.7))) * vec2(43758.5453, 22578.1459)) * vec2(1.0);\n"
" vec2 pt = nb + 0.5 + 0.5 * sin(u_time * p.y + 6.2831 * r) - fr;\n"
" md = min(md, dot(pt, pt));\n"
" }\n"
" }\n"
" float d = sqrt(md);\n"
" vec3 col = 0.5 + 0.5 * cos(6.28318 * (p.z + d + vec3(0.0, 0.33, 0.67)));\n"
" return vec4(col, 1.0);";
};
v.push_back(std::move(n));
}
// ── Gen: truchet ──────────────────────────────────────────────
{
DagNodeDef n;
n.name = "truchet";
n.label = "truchet";
n.desc = "patron truchet curvo";
n.kind = DagKind::Gen;
n.num_inputs = 0;
n.param_names = {"scale", "thickness", "hue"};
n.param_defaults = {10.0f, 0.15f, 0.3f};
n.controls = {
{ DagControl::Kind::Slider, "escala", {0, -1, -1}, 1.0f, 40.0f, 0.5f },
{ DagControl::Kind::Slider, "grosor", {1, -1, -1}, 0.02f, 0.45f, 0.01f },
{ DagControl::Kind::Slider, "tono", {2, -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 q = uv * p.x;\n"
" vec2 fl = floor(q);\n"
" vec2 fr = fract(q);\n"
" float h = fract(sin(dot(fl, vec2(12.9898, 78.233))) * 43758.5453);\n"
" if (h > 0.5) fr.x = 1.0 - fr.x;\n"
" float d1 = abs(length(fr) - 0.5);\n"
" float d2 = abs(length(fr - 1.0) - 0.5);\n"
" float d = min(d1, d2);\n"
" float fill = 1.0 - smoothstep(p.y - 0.02, p.y, d);\n"
" vec3 col = 0.5 + 0.5 * cos(6.28318 * (p.z + vec3(0.0, 0.33, 0.67)));\n"
" return vec4(col * fill, 1.0);";
};
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 = {};
n.controls = {};
n.body_glsl = [](int) -> std::string { return ""; };
v.push_back(std::move(n));
}
for (auto& n : v) n.is_builtin = true;
return v;
}();
return catalog;
}
const std::vector<DagNodeDef>& dag_catalog() {
return mutable_catalog();
}
const DagNodeDef* dag_find(const std::string& name) {
for (const auto& n : dag_catalog()) {
if (n.name == name) return &n;
}
return nullptr;
}
bool dag_register_node(DagNodeDef def) {
auto& cat = mutable_catalog();
for (auto& n : cat) {
if (n.name == def.name) {
if (n.is_builtin) return false;
n = std::move(def);
n.is_builtin = false;
return true;
}
}
def.is_builtin = false;
cat.push_back(std::move(def));
return true;
}
bool dag_unregister_node(const std::string& name) {
auto& cat = mutable_catalog();
auto it = std::find_if(cat.begin(), cat.end(),
[&](const DagNodeDef& n){ return n.name == name && !n.is_builtin; });
if (it == cat.end()) return false;
cat.erase(it);
return true;
}
} // 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..param_count-1 of their node
for (const auto& n : cat) {
int pc = static_cast<int>(n.param_defaults.size());
for (const auto& c : n.controls) {
for (int idx : c.param_idx) {
assert(idx >= -1 && idx < pc);
}
}
assert(n.param_names.size() == n.param_defaults.size());
}
std::printf("dag_catalog: 8/8 asserts passed (%zu nodes)\n", cat.size());
return 0;
}
#endif