feat(shaders_lab): DAG pipeline mode with node catalog

- cpp/functions/gfx/dag_types: DagStep, DagNodeDef, DagControl (header-only)
- cpp/functions/gfx/dag_catalog: 10 hardcoded nodes (4 gen, 3 op, 3 blend) ported from shader-dag-blends.jsx
- cpp/functions/gfx/dag_compile: pipeline → GLSL 330 core with fan-in via source_id
- cpp/functions/gfx/dag_uniforms: upload u_params[16] via glUniform4fv
- cpp/functions/gfx/dag_panel: ImGui pipeline editor (add/remove/reorder/controls)
- main.cpp: Code/DAG mode toggle, per-mode compile path and uniforms
- gl_loader: +glUniform4fv
- rebuild Windows .exe

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-24 21:15:21 +02:00
parent 4610bb4a99
commit e115c2e3fd
19 changed files with 1041 additions and 45 deletions
Binary file not shown.
+4
View File
@@ -7,6 +7,10 @@ add_imgui_app(shaders_lab
${CMAKE_SOURCE_DIR}/functions/gfx/shader_canvas.cpp
${CMAKE_SOURCE_DIR}/functions/gfx/uniform_parser.cpp
${CMAKE_SOURCE_DIR}/functions/gfx/uniform_panel.cpp
${CMAKE_SOURCE_DIR}/functions/gfx/dag_catalog.cpp
${CMAKE_SOURCE_DIR}/functions/gfx/dag_compile.cpp
${CMAKE_SOURCE_DIR}/functions/gfx/dag_uniforms.cpp
${CMAKE_SOURCE_DIR}/functions/gfx/dag_panel.cpp
${CMAKE_SOURCE_DIR}/functions/core/fps_overlay.cpp
)
target_include_directories(shaders_lab PRIVATE
+146 -45
View File
@@ -5,6 +5,10 @@
#include "gfx/gl_shader.h"
#include "gfx/uniform_parser.h"
#include "gfx/uniform_panel.h"
#include "gfx/dag_catalog.h"
#include "gfx/dag_compile.h"
#include "gfx/dag_uniforms.h"
#include "gfx/dag_panel.h"
#include "core/fps_overlay.h"
#include "seed_shaders.h"
@@ -12,6 +16,9 @@
#include <string>
#include <vector>
enum class AppMode { Code, Dag };
static AppMode g_mode = AppMode::Code;
static fn::gfx::ShaderCanvas g_canvas;
static std::string g_source = PLASMA;
static std::string g_last_err;
@@ -20,18 +27,35 @@ static std::chrono::steady_clock::time_point g_last_edit;
static bool g_dirty = true;
static std::vector<fn::gfx::UniformDescriptor> g_descs;
static fn::gfx::UniformStore g_store;
static std::vector<fn::gfx::DagStep> g_pipeline;
static std::string g_dag_glsl;
static void try_compile() {
auto r = fn::gfx::compile_fragment(g_source);
if (r.ok) {
g_descs = fn::gfx::parse_uniforms(g_source);
fn::gfx::uniforms_sync(g_store, g_descs);
fn::gfx::canvas_set_program(g_canvas, r.program);
g_last_err.clear();
g_last_err_line = -1;
if (g_mode == AppMode::Code) {
auto r = fn::gfx::compile_fragment(g_source);
if (r.ok) {
g_descs = fn::gfx::parse_uniforms(g_source);
fn::gfx::uniforms_sync(g_store, g_descs);
fn::gfx::canvas_set_program(g_canvas, r.program);
g_last_err.clear();
g_last_err_line = -1;
} else {
g_last_err = r.err_msg;
g_last_err_line = r.err_line;
}
} else {
g_last_err = r.err_msg;
g_last_err_line = r.err_line;
g_dag_glsl = fn::gfx::compile_dag_to_glsl(g_pipeline);
auto r = fn::gfx::compile_fragment(g_dag_glsl);
if (r.ok) {
g_descs.clear();
g_store.values.clear();
fn::gfx::canvas_set_program(g_canvas, r.program);
g_last_err.clear();
g_last_err_line = -1;
} else {
g_last_err = r.err_msg;
g_last_err_line = r.err_line;
}
}
}
@@ -45,6 +69,20 @@ static void load_preset(const char* src) {
mark_dirty();
}
static void ensure_dag_default() {
if (g_pipeline.empty()) {
const fn::gfx::DagNodeDef* def = fn::gfx::dag_find("plasma");
if (def) {
fn::gfx::DagStep step;
step.id = "n_default";
step.name = def->name;
step.params = def->param_defaults;
g_pipeline.push_back(step);
mark_dirty();
}
}
}
static void render() {
if (!g_canvas.initialized) fn::gfx::canvas_init(g_canvas);
@@ -59,38 +97,82 @@ static void render() {
ImGui::DockSpaceOverViewport(0, ImGui::GetMainViewport());
// --- Code panel ---
if (ImGui::Begin("Code")) {
if (ImGui::Button("Plasma")) { load_preset(PLASMA); }
ImGui::SameLine();
if (ImGui::Button("Circle")) { load_preset(CIRCLE); }
ImGui::SameLine();
if (ImGui::Button("Checker")) { load_preset(CHECKER); }
ImVec2 avail = ImGui::GetContentRegionAvail();
float footer_height = g_last_err.empty() ? 0.0f : ImGui::GetTextLineHeightWithSpacing() + 8.0f;
ImVec2 editor_size(avail.x, avail.y - footer_height);
char buf[1 << 16];
size_t copy_len = g_source.size() < sizeof(buf) - 1 ? g_source.size() : sizeof(buf) - 1;
memcpy(buf, g_source.c_str(), copy_len);
buf[copy_len] = '\0';
ImGui::PushFont(nullptr); // use default monospace-ish font
if (ImGui::InputTextMultiline("##code", buf, sizeof(buf), editor_size,
ImGuiInputTextFlags_AllowTabInput)) {
g_source = buf;
mark_dirty();
}
ImGui::PopFont();
if (!g_last_err.empty()) {
// --- Left panel: Code or DAG ---
const char* panel_title = (g_mode == AppMode::Code) ? "Code" : "DAG";
if (ImGui::Begin(panel_title)) {
// Mode toggle topbar
{
bool code_active = (g_mode == AppMode::Code);
bool dag_active = (g_mode == AppMode::Dag);
if (code_active) ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.3f, 0.5f, 0.8f, 1.0f));
if (ImGui::Button("Code")) {
if (g_mode != AppMode::Code) {
g_mode = AppMode::Code;
mark_dirty();
}
}
if (code_active) ImGui::PopStyleColor();
ImGui::SameLine();
if (dag_active) ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.3f, 0.5f, 0.8f, 1.0f));
if (ImGui::Button("DAG")) {
if (g_mode != AppMode::Dag) {
g_mode = AppMode::Dag;
ensure_dag_default();
mark_dirty();
}
}
if (dag_active) ImGui::PopStyleColor();
ImGui::Separator();
if (g_last_err_line > 0) {
ImGui::TextColored(ImVec4(1, 0.4f, 0.4f, 1), "line %d: %s",
g_last_err_line, g_last_err.c_str());
} else {
ImGui::TextColored(ImVec4(1, 0.4f, 0.4f, 1), "%s", g_last_err.c_str());
}
if (g_mode == AppMode::Code) {
if (ImGui::Button("Plasma")) { load_preset(PLASMA); }
ImGui::SameLine();
if (ImGui::Button("Circle")) { load_preset(CIRCLE); }
ImGui::SameLine();
if (ImGui::Button("Checker")) { load_preset(CHECKER); }
ImVec2 avail = ImGui::GetContentRegionAvail();
float footer_height = g_last_err.empty() ? 0.0f : ImGui::GetTextLineHeightWithSpacing() + 8.0f;
ImVec2 editor_size(avail.x, avail.y - footer_height);
char buf[1 << 16];
size_t copy_len = g_source.size() < sizeof(buf) - 1 ? g_source.size() : sizeof(buf) - 1;
memcpy(buf, g_source.c_str(), copy_len);
buf[copy_len] = '\0';
ImGui::PushFont(nullptr);
if (ImGui::InputTextMultiline("##code", buf, sizeof(buf), editor_size,
ImGuiInputTextFlags_AllowTabInput)) {
g_source = buf;
mark_dirty();
}
ImGui::PopFont();
if (!g_last_err.empty()) {
ImGui::Separator();
if (g_last_err_line > 0) {
ImGui::TextColored(ImVec4(1, 0.4f, 0.4f, 1), "line %d: %s",
g_last_err_line, g_last_err.c_str());
} else {
ImGui::TextColored(ImVec4(1, 0.4f, 0.4f, 1), "%s", g_last_err.c_str());
}
}
} else {
// DAG mode
bool topo_changed = fn::gfx::dag_panel(g_pipeline);
if (topo_changed) {
mark_dirty();
}
if (!g_last_err.empty()) {
ImGui::Separator();
if (g_last_err_line > 0) {
ImGui::TextColored(ImVec4(1, 0.4f, 0.4f, 1), "line %d: %s",
g_last_err_line, g_last_err.c_str());
} else {
ImGui::TextColored(ImVec4(1, 0.4f, 0.4f, 1), "%s", g_last_err.c_str());
}
}
}
}
@@ -98,16 +180,35 @@ static void render() {
// --- Canvas panel ---
if (ImGui::Begin("Canvas")) {
fn::gfx::canvas_render(g_canvas, static_cast<float>(ImGui::GetTime()),
[](unsigned int program) {
fn::gfx::uniforms_apply(g_store, g_descs, program);
});
if (g_mode == AppMode::Code) {
fn::gfx::canvas_render(g_canvas, static_cast<float>(ImGui::GetTime()),
[](unsigned int program) {
fn::gfx::uniforms_apply(g_store, g_descs, program);
});
} else {
fn::gfx::canvas_render(g_canvas, static_cast<float>(ImGui::GetTime()),
[](unsigned int program) {
fn::gfx::dag_uniforms_apply(g_pipeline, program);
});
}
}
ImGui::End();
// --- Controls panel ---
if (ImGui::Begin("Controls")) {
fn::gfx::uniforms_panel(g_store, g_descs);
if (g_mode == AppMode::Code) {
fn::gfx::uniforms_panel(g_store, g_descs);
} else {
// Show generated GLSL read-only
if (ImGui::CollapsingHeader("Generated GLSL")) {
ImVec2 avail = ImGui::GetContentRegionAvail();
ImGui::InputTextMultiline("##dag_glsl",
const_cast<char*>(g_dag_glsl.c_str()),
g_dag_glsl.size() + 1,
ImVec2(avail.x, std::min(avail.y, 400.0f)),
ImGuiInputTextFlags_ReadOnly);
}
}
ImGui::Spacing();
fps_overlay();
}
+233
View File
@@ -0,0 +1,233 @@
#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.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.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.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));
}
// ── Gen: circle ───────────────────────────────────────────────
{
DagNodeDef n;
n.name = "circle";
n.label = "circle";
n.desc = "sdf de circulo";
n.kind = DagKind::Gen;
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(c, 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.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 - c.rgb, c.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.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(c.rgb, vec3(1.0 / max(p.x, 0.001))), c.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.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 a = 6.28318 * p.x;\n"
" float ca = cos(a), sa = sin(a);\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 * c.rgb, 0.0, 1.0), c.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.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.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.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));
}
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
+11
View File
@@ -0,0 +1,11 @@
#pragma once
#include "gfx/dag_types.h"
#include <vector>
namespace fn::gfx {
const std::vector<DagNodeDef>& dag_catalog();
const DagNodeDef* dag_find(const std::string& name);
} // namespace fn::gfx
+46
View File
@@ -0,0 +1,46 @@
---
name: dag_catalog
kind: function
lang: cpp
domain: gfx
version: "1.0.0"
purity: pure
signature: "const std::vector<DagNodeDef>& dag_catalog(); const DagNodeDef* dag_find(const std::string& name)"
description: "Catalogo global de nodos DAG para el pipeline de shaders. dag_catalog() devuelve referencia estable a los 10 nodos hardcoded (4 gen, 3 op, 3 blend) portados desde shader-dag-blends.jsx. dag_find() busca por nombre."
tags: [dag, shader, catalog, nodes, gfx, pipeline]
uses_functions: []
uses_types:
- dag_types_cpp_gfx
returns: []
returns_optional: false
error_type: ""
imports: [dag_types, string, vector]
tested: false
tests: []
test_file_path: ""
file_path: "cpp/functions/gfx/dag_catalog.cpp"
params: []
output: "dag_catalog(): referencia const estable al vector de DagNodeDef (instancia estatica, no se invalida). dag_find(name): puntero al nodo con ese nombre o nullptr si no existe."
---
## Nodos incluidos
### Generadores (4)
- **solid**: color constante RGB
- **gradient**: gradiente direccional con tono
- **plasma**: onda trigonometrica animada
- **circle**: SDF de circulo con suavizado
### Operadores (3)
- **invert**: 1 - rgb
- **gamma**: correccion gamma pow(rgb, 1/g)
- **hueShift**: rotacion de matiz via matriz YIQ
### Blends (3)
- **blend_mix**: interpolacion mix(a, b, t)
- **blend_multiply**: a.rgb * b.rgb
- **blend_screen**: 1 - (1-a)(1-b)
## Notas
Los cuerpos GLSL omiten las declaraciones de u_time, u_resolution, u_params — las proporciona el preamble de gl_shader::compile_fragment o compile_dag_to_glsl. El indice idx que recibe body_glsl es la posicion en el pipeline (para indexar u_params[idx]).
+84
View File
@@ -0,0 +1,84 @@
#include "gfx/dag_compile.h"
#include "gfx/dag_catalog.h"
#include <algorithm>
#include <sstream>
namespace fn::gfx {
static constexpr int MAX_NODES = 16;
std::string compile_dag_to_glsl(const std::vector<DagStep>& pipeline) {
const int n = static_cast<int>(std::min(pipeline.size(), static_cast<size_t>(MAX_NODES)));
std::ostringstream out;
out << "uniform vec4 u_params[16];\n\n";
if (n == 0) {
out << "void main() {\n";
out << " vec2 uv = gl_FragCoord.xy / u_resolution;\n";
out << " (void)uv;\n";
out << " fragColor = vec4(0.04, 0.04, 0.06, 1.0);\n";
out << "}\n";
return out.str();
}
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::Blend) {
out << "vec4 node_" << i << "(vec4 a, vec4 b, vec2 uv) {\n";
} else {
out << "vec4 node_" << i << "(vec4 c, vec2 uv) {\n";
}
out << def->body_glsl(i) << "\n";
out << "}\n\n";
}
out << "void main() {\n";
out << " vec2 uv = gl_FragCoord.xy / u_resolution;\n";
out << " vec4 c = vec4(0.04, 0.04, 0.06, 1.0);\n";
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) {
out << " vec4 out_" << i << " = (i > 0 ? out_" << (i-1) << " : c);\n";
continue;
}
std::string prev = (i == 0) ? "vec4(0.0, 0.0, 0.0, 1.0)" : "out_" + std::to_string(i - 1);
if (def->kind == DagKind::Blend) {
int src_idx = -1;
if (!step.source_id.empty()) {
for (int j = 0; j < i; ++j) {
if (pipeline[static_cast<size_t>(j)].id == step.source_id) {
src_idx = j;
break;
}
}
}
if (src_idx < 0 || src_idx >= i) {
src_idx = std::max(0, i - 2);
}
std::string src;
if (i == 0) {
src = prev;
} else {
src = "out_" + std::to_string(std::min(src_idx, i - 1));
}
out << " vec4 out_" << i << " = node_" << i << "(" << prev << ", " << src << ", uv);\n";
} else {
out << " vec4 out_" << i << " = node_" << i << "(" << prev << ", uv);\n";
}
}
out << " fragColor = out_" << (n - 1) << ";\n";
out << "}\n";
return out.str();
}
} // namespace fn::gfx
+14
View File
@@ -0,0 +1,14 @@
#pragma once
#include <string>
#include <vector>
#include "gfx/dag_types.h"
namespace fn::gfx {
// Compila un pipeline DAG a GLSL 330 core completo (listo para gl_shader::compile_fragment).
// El preamble de gl_shader ya declara #version, fragColor, u_time, u_resolution, u_mouse.
// Este compilador emite uniform vec4 u_params[16], las funciones node_<i> y void main().
// Si el pipeline esta vacio, emite un fragment que pinta gris oscuro.
std::string compile_dag_to_glsl(const std::vector<DagStep>& pipeline);
} // namespace fn::gfx
+51
View File
@@ -0,0 +1,51 @@
---
name: dag_compile
kind: function
lang: cpp
domain: gfx
version: "1.0.0"
purity: pure
signature: "std::string compile_dag_to_glsl(const std::vector<DagStep>& pipeline)"
description: "Compila un pipeline DAG a GLSL 330 core listo para pasarle a gl_shader::compile_fragment. Emite uniform vec4 u_params[16], una funcion node_<i> por paso y void main() que encadena los outputs. Blends usan source_id para fan-in estable ante reorders."
tags: [dag, shader, glsl, compiler, gfx, pipeline]
uses_functions:
- dag_catalog_cpp_gfx
uses_types:
- dag_types_cpp_gfx
returns: []
returns_optional: false
error_type: ""
imports: [dag_compile, dag_catalog, dag_types, string, vector, sstream, algorithm]
tested: false
tests: []
test_file_path: ""
file_path: "cpp/functions/gfx/dag_compile.cpp"
params:
- name: pipeline
desc: "Vector de DagStep. Cada paso tiene un nombre de nodo del catalogo, params array<float,4> y source_id para blends."
output: "String GLSL que se pega tras el preamble de gl_shader (que ya declara #version 330 core, fragColor, u_time, u_resolution, u_mouse). Incluye uniform vec4 u_params[16], funciones node_<i> y void main()."
---
## Estructura del GLSL emitido
```glsl
uniform vec4 u_params[16];
vec4 node_0(vec4 c, vec2 uv) { ... }
vec4 node_1(vec4 a, vec4 b, vec2 uv) { ... } // blend
void main() {
vec2 uv = gl_FragCoord.xy / u_resolution;
vec4 c = vec4(0.04, 0.04, 0.06, 1.0);
vec4 out_0 = node_0(vec4(0.0, 0.0, 0.0, 1.0), uv);
vec4 out_1 = node_1(out_0, out_0, uv);
fragColor = out_1;
}
```
## Notas
- El preamble de gl_shader::compile_fragment ya declara los 3 uniforms basicos. compile_dag_to_glsl NO los redeclara.
- Si el pipeline esta vacio, emite void main() que pinta gris oscuro (0.04, 0.04, 0.06).
- MAX_NODES = 16. Pipelines mas largos se truncan silenciosamente.
- source_id fallback: si el id no se encuentra o apunta a un indice >= idx, usa max(0, idx-2).
+228
View File
@@ -0,0 +1,228 @@
#include "gfx/dag_panel.h"
#include "gfx/dag_catalog.h"
#include "imgui.h"
#include <algorithm>
#include <string>
namespace fn::gfx {
static constexpr int MAX_NODES = 16;
static uint64_t s_next_id = 1;
static std::string make_id() {
return "n" + std::to_string(s_next_id++);
}
static ImVec4 kind_color(DagKind kind) {
switch (kind) {
case DagKind::Gen: return ImVec4(0.25f, 0.55f, 0.90f, 1.0f);
case DagKind::Op: return ImVec4(0.65f, 0.40f, 0.90f, 1.0f);
case DagKind::Blend: return ImVec4(0.90f, 0.65f, 0.15f, 1.0f);
}
return ImVec4(1, 1, 1, 1);
}
static const char* kind_label(DagKind kind) {
switch (kind) {
case DagKind::Gen: return "Gen";
case DagKind::Op: return "Op";
case DagKind::Blend: return "Blend";
}
return "?";
}
bool dag_panel(std::vector<DagStep>& pipeline) {
bool changed = false;
// Toolbar
int sz = static_cast<int>(pipeline.size());
if (ImGui::Button("+ Add Node")) {
ImGui::OpenPopup("dag_add_popup");
}
ImGui::SameLine();
ImGui::Text("%d/%d nodes", sz, MAX_NODES);
ImGui::SameLine();
if (ImGui::Button("Clear") && !pipeline.empty()) {
ImGui::OpenPopup("dag_clear_confirm");
}
if (ImGui::BeginPopupModal("dag_clear_confirm", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::Text("Vaciar el pipeline?");
if (ImGui::Button("Si")) {
pipeline.clear();
changed = true;
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button("No")) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
if (ImGui::BeginPopup("dag_add_popup")) {
const char* kind_names[] = { "Gen", "Op", "Blend" };
DagKind kinds[] = { DagKind::Gen, DagKind::Op, DagKind::Blend };
for (int k = 0; k < 3; ++k) {
if (ImGui::BeginMenu(kind_names[k])) {
for (const auto& def : dag_catalog()) {
if (def.kind != kinds[k]) continue;
if (ImGui::MenuItem(def.label.c_str())) {
if (static_cast<int>(pipeline.size()) < MAX_NODES) {
DagStep step;
step.id = make_id();
step.name = def.name;
step.params = def.param_defaults;
if (def.kind == DagKind::Blend && !pipeline.empty()) {
int src = std::max(0, static_cast<int>(pipeline.size()) - 2);
step.source_id = pipeline[static_cast<size_t>(src)].id;
}
pipeline.push_back(step);
changed = true;
}
}
}
ImGui::EndMenu();
}
}
ImGui::EndPopup();
}
ImGui::Separator();
// Pipeline list
int delete_idx = -1;
int move_up_idx = -1;
int move_down_idx = -1;
ImGui::BeginChild("dag_pipeline_scroll", ImVec2(0, 0), false);
for (int i = 0; i < static_cast<int>(pipeline.size()); ++i) {
DagStep& step = pipeline[static_cast<size_t>(i)];
const DagNodeDef* def = dag_find(step.name);
if (!def) continue;
ImVec4 col = kind_color(def->kind);
ImGui::PushID(i);
ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(col.x*0.4f, col.y*0.4f, col.z*0.4f, 0.8f));
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(col.x*0.5f, col.y*0.5f, col.z*0.5f, 0.9f));
ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(col.x*0.6f, col.y*0.6f, col.z*0.6f, 1.0f));
std::string header = std::string("#") + std::to_string(i) + " " + def->label + " [" + kind_label(def->kind) + "]";
bool open = ImGui::CollapsingHeader(header.c_str());
ImGui::PopStyleColor(3);
if (open) {
ImGui::Indent(8.0f);
// Source selector for blends
if (def->kind == DagKind::Blend) {
std::vector<std::string> prev_labels;
std::vector<int> prev_indices;
for (int j = 0; j < i; ++j) {
const DagNodeDef* pdef = dag_find(pipeline[static_cast<size_t>(j)].name);
std::string lbl = "out_" + std::to_string(j);
if (pdef) lbl += " (" + pdef->label + ")";
prev_labels.push_back(lbl);
prev_indices.push_back(j);
}
int current_src = std::max(0, i - 2);
if (!step.source_id.empty()) {
for (int j = 0; j < i; ++j) {
if (pipeline[static_cast<size_t>(j)].id == step.source_id) {
current_src = j;
break;
}
}
}
if (!prev_labels.empty()) {
std::vector<const char*> items;
for (const auto& l : prev_labels) items.push_back(l.c_str());
int sel = current_src;
if (ImGui::Combo("Source", &sel, items.data(), static_cast<int>(items.size()))) {
if (sel >= 0 && sel < i) {
step.source_id = pipeline[static_cast<size_t>(sel)].id;
changed = true;
}
}
} else {
ImGui::TextDisabled("(requiere al menos un nodo antes)");
}
}
// Controls
for (const auto& ctrl : def->controls) {
std::string uid_label = ctrl.label + "##" + step.id + std::to_string(&ctrl - def->controls.data());
if (ctrl.kind == DagControl::Kind::Slider) {
int pidx = ctrl.param_idx[0];
if (pidx >= 0 && pidx < 4) {
ImGui::SliderFloat(uid_label.c_str(), &step.params[static_cast<size_t>(pidx)], ctrl.min, ctrl.max);
}
} else if (ctrl.kind == DagControl::Kind::XY) {
int px = ctrl.param_idx[0];
int py = ctrl.param_idx[1];
if (px >= 0 && px < 4 && py >= 0 && py < 4 && py == px + 1) {
ImGui::SliderFloat2(uid_label.c_str(), &step.params[static_cast<size_t>(px)], ctrl.min, ctrl.max);
}
} else if (ctrl.kind == DagControl::Kind::Color) {
int pr = ctrl.param_idx[0];
if (pr >= 0 && pr + 2 < 4) {
ImGui::ColorEdit3(uid_label.c_str(), &step.params[static_cast<size_t>(pr)]);
}
}
}
// Action buttons
ImGui::Spacing();
ImGui::BeginDisabled(i == 0);
if (ImGui::SmallButton("Move Up")) {
move_up_idx = i;
}
ImGui::EndDisabled();
ImGui::SameLine();
ImGui::BeginDisabled(i == static_cast<int>(pipeline.size()) - 1);
if (ImGui::SmallButton("Move Down")) {
move_down_idx = i;
}
ImGui::EndDisabled();
ImGui::SameLine();
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.7f, 0.15f, 0.15f, 0.8f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.9f, 0.2f, 0.2f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1.0f, 0.3f, 0.3f, 1.0f));
if (ImGui::SmallButton("Delete")) {
delete_idx = i;
}
ImGui::PopStyleColor(3);
ImGui::Unindent(8.0f);
}
ImGui::PopID();
ImGui::Spacing();
}
ImGui::EndChild();
// Apply deferred mutations
if (delete_idx >= 0) {
pipeline.erase(pipeline.begin() + delete_idx);
changed = true;
}
if (move_up_idx > 0) {
std::swap(pipeline[static_cast<size_t>(move_up_idx)], pipeline[static_cast<size_t>(move_up_idx - 1)]);
changed = true;
}
if (move_down_idx >= 0 && move_down_idx < static_cast<int>(pipeline.size()) - 1) {
std::swap(pipeline[static_cast<size_t>(move_down_idx)], pipeline[static_cast<size_t>(move_down_idx + 1)]);
changed = true;
}
return changed;
}
} // namespace fn::gfx
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include <vector>
#include "gfx/dag_types.h"
namespace fn::gfx {
// Renderiza el panel del pipeline dentro del ImGui::Begin actual.
// Modifica pipeline in-place. Devuelve true si la topologia cambio
// (anadir/borrar/reordenar/cambiar source_id).
// Cambios de param (sliders/color/xy) no cuentan como topology change.
bool dag_panel(std::vector<DagStep>& pipeline);
} // namespace fn::gfx
+52
View File
@@ -0,0 +1,52 @@
---
name: dag_panel
kind: component
lang: cpp
domain: gfx
version: "1.0.0"
purity: impure
signature: "bool dag_panel(std::vector<DagStep>& pipeline)"
description: "Panel ImGui para editar un pipeline DAG de shaders. Toolbar con Add Node (popup por kind) y Clear. Lista scrollable de pasos con CollapsingHeader coloreado por kind, selector de source para blends, widgets de control (Slider/XY/Color) y botones Move Up/Down/Delete. Devuelve true si la topologia cambio."
tags: [dag, shader, imgui, pipeline, editor, gfx, component]
uses_functions:
- dag_catalog_cpp_gfx
uses_types:
- dag_types_cpp_gfx
returns: []
returns_optional: false
error_type: "error_go_core"
imports: [dag_panel, dag_catalog, dag_types, imgui, algorithm, string, vector]
tested: false
tests: []
test_file_path: ""
file_path: "cpp/functions/gfx/dag_panel.cpp"
framework: imgui
params:
- name: pipeline
desc: "Vector de DagStep modificado in-place. Add/Delete/Move cambian la topologia. Los sliders solo cambian params."
output: "true si la topologia cambio (add/delete/move/source_id change). false si solo cambiaron valores de params (sliders). El caller usa este flag para decidir si recompilar el shader."
---
## Estructura del panel
```
[+ Add Node] [N/16 nodes] [Clear]
─────────────────────────────────────
▼ #0 plasma [Gen]
velocidad [slider]
escala [slider]
[Move Up(disabled)] [Move Down] [Delete]
▶ #1 blend_mix [Blend]
...
```
## Colores por kind
- Gen: azul (0.25, 0.55, 0.90)
- Op: violeta (0.65, 0.40, 0.90)
- Blend: ambar (0.90, 0.65, 0.15)
## Notas
Los IDs de paso se generan con contador estatico `s_next_id`. Unicos dentro de la sesion. Al anadir un Blend, asigna automaticamente el source_id al paso de hace dos (o el primero si el pipeline tiene menos de 2 pasos). Las mutaciones (delete/move) se aplican al final del loop para no invalidar iteradores.
+39
View File
@@ -0,0 +1,39 @@
#pragma once
#include <array>
#include <functional>
#include <string>
#include <vector>
namespace fn::gfx {
enum class DagKind { Gen, Op, Blend };
struct DagControl {
enum class Kind { Slider, XY, Color };
Kind kind;
std::string label;
std::array<int, 3> param_idx{-1, -1, -1};
float min = 0.0f;
float max = 1.0f;
float step = 0.0f;
};
struct DagNodeDef {
std::string name;
std::string label;
std::string desc;
DagKind kind = DagKind::Gen;
std::array<std::string, 4> param_names{"", "", "", ""};
std::array<float, 4> param_defaults{0, 0, 0, 0};
std::vector<DagControl> controls;
std::function<std::string(int idx)> body_glsl;
};
struct DagStep {
std::string id;
std::string name;
std::array<float, 4> params{0, 0, 0, 0};
std::string source_id;
};
} // namespace fn::gfx
+27
View File
@@ -0,0 +1,27 @@
#include "gfx/dag_uniforms.h"
#include "gfx/gl_loader.h"
#include <algorithm>
#include <cstring>
namespace fn::gfx {
static constexpr int MAX_NODES = 16;
void dag_uniforms_apply(const std::vector<DagStep>& pipeline, unsigned int program) {
float data[MAX_NODES * 4];
std::memset(data, 0, sizeof(data));
const int n = static_cast<int>(std::min(pipeline.size(), static_cast<size_t>(MAX_NODES)));
for (int i = 0; i < n; ++i) {
const auto& step = pipeline[static_cast<size_t>(i)];
data[i * 4 + 0] = step.params[0];
data[i * 4 + 1] = step.params[1];
data[i * 4 + 2] = step.params[2];
data[i * 4 + 3] = step.params[3];
}
GLint loc = glGetUniformLocation(program, "u_params");
if (loc >= 0) glUniform4fv(loc, MAX_NODES, data);
}
} // namespace fn::gfx
+11
View File
@@ -0,0 +1,11 @@
#pragma once
#include <vector>
#include "gfx/dag_types.h"
namespace fn::gfx {
// Sube los params del pipeline al shader via glUniform4fv(u_params, 16, data).
// El programa GL debe estar activo (glUseProgram'd) por el caller.
void dag_uniforms_apply(const std::vector<DagStep>& pipeline, unsigned int program);
} // namespace fn::gfx
+33
View File
@@ -0,0 +1,33 @@
---
name: dag_uniforms
kind: function
lang: cpp
domain: gfx
version: "1.0.0"
purity: impure
signature: "void dag_uniforms_apply(const std::vector<DagStep>& pipeline, unsigned int program)"
description: "Sube los params del pipeline al shader activo via glUniform4fv. Construye un float[64] con los params de cada DagStep (hasta 16 pasos) y llama glUniform4fv(u_params, 16, data)."
tags: [dag, shader, uniforms, opengl, gfx, pipeline]
uses_functions:
- gl_loader_cpp_gfx
uses_types:
- dag_types_cpp_gfx
returns: []
returns_optional: false
error_type: "error_go_core"
imports: [dag_uniforms, gl_loader, dag_types, vector, algorithm, cstring]
tested: false
tests: []
test_file_path: ""
file_path: "cpp/functions/gfx/dag_uniforms.cpp"
params:
- name: pipeline
desc: "Vector de DagStep. params[0..3] de cada step se copian en data[i*4..i*4+3]. Steps mas alla de MAX_NODES=16 se ignoran."
- name: program
desc: "ID del programa GL activo. Debe haber sido activado con glUseProgram antes de llamar."
output: "Efecto lateral: actualiza el uniform u_params[16] en el programa GL activo para el frame actual."
---
## Notas
El array data[64] se inicializa a 0 antes de copiar, por lo que steps no usados quedan en cero. El caller es responsable de activar el programa antes de llamar.
+2
View File
@@ -30,6 +30,7 @@ PFNGLUNIFORM1IPROC fn_glUniform1i = nullptr;
PFNGLUNIFORM2FPROC fn_glUniform2f = nullptr;
PFNGLUNIFORM3FPROC fn_glUniform3f = nullptr;
PFNGLUNIFORM4FPROC fn_glUniform4f = nullptr;
PFNGLUNIFORM4FVPROC fn_glUniform4fv = nullptr;
PFNGLUSEPROGRAMPROC fn_glUseProgram = nullptr;
namespace fn::gfx {
@@ -67,6 +68,7 @@ bool gl_loader_init() {
LOAD(glUniform2f);
LOAD(glUniform3f);
LOAD(glUniform4f);
LOAD(glUniform4fv);
LOAD(glUseProgram);
#undef LOAD
+2
View File
@@ -36,6 +36,7 @@
extern PFNGLUNIFORM2FPROC fn_glUniform2f;
extern PFNGLUNIFORM3FPROC fn_glUniform3f;
extern PFNGLUNIFORM4FPROC fn_glUniform4f;
extern PFNGLUNIFORM4FVPROC fn_glUniform4fv;
extern PFNGLUSEPROGRAMPROC fn_glUseProgram;
#define glAttachShader fn_glAttachShader
@@ -66,6 +67,7 @@
#define glUniform2f fn_glUniform2f
#define glUniform3f fn_glUniform3f
#define glUniform4f fn_glUniform4f
#define glUniform4fv fn_glUniform4fv
#define glUseProgram fn_glUseProgram
#else
#define GL_GLEXT_PROTOTYPES
+45
View File
@@ -0,0 +1,45 @@
---
name: dag_types
lang: cpp
domain: gfx
version: "1.0.0"
algebraic: product
definition: |
enum class DagKind { Gen, Op, Blend };
struct DagControl {
enum class Kind { Slider, XY, Color };
Kind kind;
std::string label;
std::array<int, 3> param_idx{-1, -1, -1};
float min = 0.0f;
float max = 1.0f;
float step = 0.0f;
};
struct DagNodeDef {
std::string name;
std::string label;
std::string desc;
DagKind kind = DagKind::Gen;
std::array<std::string, 4> param_names{"", "", "", ""};
std::array<float, 4> param_defaults{0, 0, 0, 0};
std::vector<DagControl> controls;
std::function<std::string(int idx)> body_glsl;
};
struct DagStep {
std::string id;
std::string name;
std::array<float, 4> params{0, 0, 0, 0};
std::string source_id;
};
description: "Tipos del DAG de shaders: DagKind (gen/op/blend), DagControl (descriptor de widget UI), DagNodeDef (entrada del catalogo de nodos con body GLSL), DagStep (paso del pipeline con params y source_id estable ante reorders)."
tags: [dag, shader, gfx, pipeline, nodes, types]
uses_types: []
file_path: "cpp/functions/gfx/dag_types.h"
---
## Notas
Header-only. DagNodeDef::body_glsl es un callable que recibe el indice del nodo en el pipeline y devuelve el cuerpo GLSL de la funcion (sin llaves). DagStep::source_id es el id estable del paso fuente para blends; sobrevive a reordenamientos del pipeline porque referencia por id, no por indice.