Files
fn_registry/cpp/functions/gfx/code_to_generator.cpp
T
egutierrez a9045d45a0 feat(cpp/gfx): code_to_generator + shaderlab_db
Dos primitivas del pipeline de shaders_lab que ya estaban en uso pero sin
indexar al registry:

- code_to_generator_cpp_gfx (function, pure)
  Traduce un fragment shader GLSL escrito a mano (modo Code, con void main()
  + fragColor = ...) en un body de DAG Gen + DagControl[]. Cada uniform
  anotado se convierte en un control; el body usa el parametro uv y reemplaza
  fragColor= por return. Empaqueta uniforms en vec4 (4 x n_uniforms).

- shaderlab_db_cpp_gfx (function, impure)
  CRUD persistente para generators custom de shaders_lab via sqlite3.
  Guarda el GLSL original, el body traducido para el DAG, los DagControl y
  los param_defaults en una BD local (shaders_lab.db). Soporta open(:memory:)
  para tests.

Ambas se indexan ahora en registry.db y son reusables fuera de shaders_lab
si en el futuro hay otra app que componga DAGs de shaders.
2026-04-25 21:26:03 +02:00

401 lines
14 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "gfx/code_to_generator.h"
#include "gfx/uniform_parser.h"
#include <regex>
#include <sstream>
#include <string>
namespace fn::gfx {
// Replace every occurrence of `from` with `to` in `s`.
static void replace_all(std::string& s, const std::string& from, const std::string& to) {
if (from.empty()) return;
size_t p = 0;
while ((p = s.find(from, p)) != std::string::npos) {
s.replace(p, from.size(), to);
p += to.size();
}
}
// Extract the body of `void main() { ... }` (content between the outermost braces).
// Returns empty string if no balanced body is found.
static std::string extract_main_body(const std::string& source) {
static const std::regex main_re(R"(\bvoid\s+main\s*\(\s*\)\s*\{)");
std::smatch m;
if (!std::regex_search(source, m, main_re)) return "";
size_t start = static_cast<size_t>(m.position(0)) + m.length(0); // after the '{'
int depth = 1;
size_t end = start;
for (; end < source.size() && depth > 0; ++end) {
if (source[end] == '{') ++depth;
else if (source[end] == '}') --depth;
if (depth == 0) break;
}
if (depth != 0) return "";
return source.substr(start, end - start);
}
// Strip lines that match `vec2 uv = <expr>;` (the function already provides uv).
static std::string strip_uv_decl(const std::string& body) {
static const std::regex uv_re(R"(^\s*vec2\s+uv\s*=\s*[^;]+;\s*$)");
std::ostringstream out;
std::istringstream in(body);
std::string line;
bool first = true;
while (std::getline(in, line)) {
if (std::regex_match(line, uv_re)) continue;
if (!first) out << '\n';
out << line;
first = false;
}
return out.str();
}
// Replace `fragColor = X;` with `return X;`. Tolerant to whitespace; one-shot regex.
static std::string fragcolor_to_return(const std::string& body) {
static const std::regex fc_re(R"(\bfragColor\s*=\s*([^;]+);)");
return std::regex_replace(body, fc_re, "return $1;");
}
// GLSL type info for a uniform: alias declaration, control kind, slot count.
struct TypeInfo {
bool ok = true;
const char* alias_type = "float"; // GLSL keyword
const char* alias_swizzle = ".x"; // suffix on u_params[k]
DagControl::Kind ctrl_kind = DagControl::Kind::Slider;
int components = 1; // floats consumed
};
static TypeInfo type_info_for(GLSLType t, WidgetKind w) {
TypeInfo ti;
switch (t) {
case GLSLType::Float:
ti.alias_type = "float";
ti.alias_swizzle = ".x";
ti.ctrl_kind = DagControl::Kind::Slider;
ti.components = 1;
break;
case GLSLType::Int:
ti.alias_type = "int";
ti.alias_swizzle = ".x";
ti.ctrl_kind = DagControl::Kind::Slider;
ti.components = 1;
break;
case GLSLType::Bool:
ti.alias_type = "bool";
ti.alias_swizzle = ".x";
ti.ctrl_kind = DagControl::Kind::Slider; // 0/1 slider
ti.components = 1;
break;
case GLSLType::Vec2:
ti.alias_type = "vec2";
ti.alias_swizzle = ".xy";
ti.ctrl_kind = DagControl::Kind::XY;
ti.components = 2;
break;
case GLSLType::Vec3:
ti.alias_type = "vec3";
ti.alias_swizzle = ".xyz";
ti.ctrl_kind = DagControl::Kind::Color;
ti.components = 3;
break;
case GLSLType::Vec4:
// Treat as Color if widget says so, otherwise unsupported (KISS).
if (w == WidgetKind::Color) {
ti.alias_type = "vec4";
ti.alias_swizzle = "";
ti.ctrl_kind = DagControl::Kind::Color;
ti.components = 3; // we render only RGB; alpha sits unused
} else {
ti.ok = false;
}
break;
}
// Widget overrides (when annotated)
if (ti.ok) {
if (w == WidgetKind::Color && t != GLSLType::Vec3 && t != GLSLType::Vec4) {
ti.ctrl_kind = DagControl::Kind::Slider; // can't color a non-vec3/4
}
if (w == WidgetKind::Toggle) {
ti.ctrl_kind = DagControl::Kind::Slider;
}
if (w == WidgetKind::XY && t == GLSLType::Vec2) {
ti.ctrl_kind = DagControl::Kind::XY;
}
}
return ti;
}
// Special-case: a vec4 typed as Color has alias `.xyz` for the local declared as vec3?
// We keep the alias as the full vec4 to avoid changing the user's body, and treat the
// control as Color over the first 3 components.
static const char* alias_swizzle_for(GLSLType t) {
switch (t) {
case GLSLType::Float:
case GLSLType::Int:
case GLSLType::Bool: return ".x";
case GLSLType::Vec2: return ".xy";
case GLSLType::Vec3: return ".xyz";
case GLSLType::Vec4: return "";
}
return "";
}
CodeToGeneratorResult code_to_generator(const std::string& source) {
CodeToGeneratorResult r;
auto descs = parse_uniforms(source);
// Reject sampler2D and other unsupported forms by inspecting raw source.
// parse_uniforms already drops sampler2D silently; warn the user explicitly.
if (source.find("sampler2D") != std::string::npos) {
r.err = "sampler2D uniforms are not supported in saved generators";
return r;
}
if (descs.empty() && source.find("uniform") != std::string::npos) {
// Has uniforms but parser found none → unsupported types.
// (Could be more granular; for now just warn.)
}
if (descs.size() > 16) {
r.err = "too many uniforms (max 16 per generator)";
return r;
}
std::string body_main = extract_main_body(source);
if (body_main.empty()) {
r.err = "could not find `void main() { ... }` body";
return r;
}
if (body_main.find("fragColor") == std::string::npos) {
r.err = "body must assign `fragColor` at least once";
return r;
}
// Build aliases (preamble) and DagControls.
std::ostringstream pre;
int slot = 0;
for (const auto& d : descs) {
TypeInfo ti = type_info_for(d.glsl_type, d.widget);
if (!ti.ok) {
r.err = "uniform '" + d.name + "': vec4 only supported with @color";
return r;
}
// Local alias: float u_speed = u_params[__BASE__+slot].x;
pre << " " << ti.alias_type << ' ' << d.name
<< " = " << ti.alias_type << "(u_params[__BASE__+" << slot << "]"
<< alias_swizzle_for(d.glsl_type) << ");\n";
// Defaults: pack components into the 4 floats of this slot
const int base_float = slot * 4;
for (int c = 0; c < ti.components && c < 4; ++c) {
r.param_defaults.push_back(d.defaults[c]);
}
for (int c = ti.components; c < 4; ++c) {
r.param_defaults.push_back(0.0f);
}
// Param names: NAME, NAME.y, NAME.z... (for debug only)
r.param_names.push_back(d.name);
for (int c = 1; c < 4; ++c) r.param_names.push_back("");
// Build DagControl
DagControl ctrl;
ctrl.kind = ti.ctrl_kind;
ctrl.label = d.name;
ctrl.min = d.min[0];
ctrl.max = d.max[0];
ctrl.step = d.step;
if (ctrl.kind == DagControl::Kind::Slider) {
ctrl.param_idx = { base_float, -1, -1 };
} else if (ctrl.kind == DagControl::Kind::XY) {
ctrl.param_idx = { base_float, base_float + 1, -1 };
} else if (ctrl.kind == DagControl::Kind::Color) {
ctrl.param_idx = { base_float, base_float + 1, base_float + 2 };
ctrl.min = 0.0f;
ctrl.max = 1.0f;
}
r.controls.push_back(std::move(ctrl));
++slot;
}
r.param_count = static_cast<int>(r.param_defaults.size());
// Transform body: strip uv decl + fragColor → return.
std::string body = strip_uv_decl(body_main);
body = fragcolor_to_return(body);
// Final body_template: preamble (aliases) + transformed body.
std::ostringstream tpl;
tpl << pre.str() << body;
r.body_template = tpl.str();
r.ok = true;
return r;
}
DagNodeDef make_generator_def(const std::string& name,
const std::string& label,
const std::string& desc,
const CodeToGeneratorResult& tr) {
DagNodeDef d;
d.name = name;
d.label = label.empty() ? name : label;
d.desc = desc;
d.kind = DagKind::Gen;
d.num_inputs = 0;
d.param_names = tr.param_names;
d.param_defaults = tr.param_defaults;
d.controls = tr.controls;
d.is_builtin = false;
const std::string body_template = tr.body_template;
d.body_glsl = [body_template](int base) -> std::string {
std::string s = body_template;
replace_all(s, "__BASE__", std::to_string(base));
return s;
};
return d;
}
} // namespace fn::gfx
#ifdef CODE_TO_GENERATOR_TEST
#include <cassert>
#include <cstdio>
static bool contains(const std::string& hay, const std::string& needle) {
return hay.find(needle) != std::string::npos;
}
int main() {
using namespace fn::gfx;
// 1. Plasma-like Code: float speed + vec3 color → 2 controls (slider + color)
{
const char* src = R"glsl(
uniform float u_speed; // @slider min=0.1 max=5 default=1
uniform vec3 u_color; // @color default=0.5,0.2,0.8
void main() {
vec2 uv = gl_FragCoord.xy / u_resolution;
float t = u_time * u_speed;
vec3 c = u_color * (0.5 + 0.5 * cos(t + uv.xyx + vec3(0.0, 2.0, 4.0)));
fragColor = vec4(c, 1.0);
}
)glsl";
auto r = code_to_generator(src);
assert(r.ok);
assert(r.controls.size() == 2);
assert(r.controls[0].kind == DagControl::Kind::Slider);
assert(r.controls[0].label == "u_speed");
assert(r.controls[0].param_idx[0] == 0);
assert(r.controls[0].max == 5.0f);
assert(r.controls[1].kind == DagControl::Kind::Color);
assert(r.controls[1].label == "u_color");
assert(r.controls[1].param_idx[0] == 4);
assert(r.controls[1].param_idx[2] == 6);
assert(r.param_count == 8); // 2 uniforms × 4 floats
assert(r.param_defaults.size() == 8);
assert(r.param_defaults[0] == 1.0f); // u_speed default
assert(r.param_defaults[4] == 0.5f); // u_color.r
assert(r.param_defaults[5] == 0.2f);
assert(r.param_defaults[6] == 0.8f);
// body_template: should declare aliases referencing __BASE__
assert(contains(r.body_template, "float u_speed"));
assert(contains(r.body_template, "u_params[__BASE__+0]"));
assert(contains(r.body_template, "vec3 u_color"));
assert(contains(r.body_template, "u_params[__BASE__+1]"));
// uv = ... line stripped
assert(!contains(r.body_template, "vec2 uv = gl_FragCoord"));
// fragColor → return
assert(contains(r.body_template, "return vec4(c, 1.0);"));
assert(!contains(r.body_template, "fragColor ="));
}
// 2. make_generator_def + lambda substitution
{
const char* src = R"glsl(
uniform float u_x; // @slider min=0 max=1 default=0.5
void main() {
fragColor = vec4(u_x, 0.0, 0.0, 1.0);
}
)glsl";
auto tr = code_to_generator(src);
assert(tr.ok);
DagNodeDef def = make_generator_def("test_gen", "Test", "desc", tr);
assert(!def.is_builtin);
assert(def.kind == DagKind::Gen);
assert(def.num_inputs == 0);
std::string b3 = def.body_glsl(3);
assert(contains(b3, "u_params[3+0]"));
assert(!contains(b3, "__BASE__"));
std::string b0 = def.body_glsl(0);
assert(contains(b0, "u_params[0+0]"));
}
// 3. Missing void main → error
{
auto r = code_to_generator("uniform float u_x;\n");
assert(!r.ok);
assert(r.err.find("void main") != std::string::npos);
}
// 4. No fragColor → error
{
auto r = code_to_generator("void main() { }\n");
assert(!r.ok);
assert(r.err.find("fragColor") != std::string::npos);
}
// 5. sampler2D rejected
{
const char* src = R"glsl(
uniform sampler2D u_tex;
void main() { fragColor = vec4(1.0); }
)glsl";
auto r = code_to_generator(src);
assert(!r.ok);
assert(r.err.find("sampler2D") != std::string::npos);
}
// 6. vec2 with @xy → XY control
{
const char* src = R"glsl(
uniform vec2 u_pos; // @xy min=-1 max=1 default=0,0
void main() { fragColor = vec4(u_pos, 0.0, 1.0); }
)glsl";
auto r = code_to_generator(src);
assert(r.ok);
assert(r.controls.size() == 1);
assert(r.controls[0].kind == DagControl::Kind::XY);
assert(r.controls[0].param_idx[0] == 0);
assert(r.controls[0].param_idx[1] == 1);
assert(r.controls[0].min == -1.0f);
assert(r.controls[0].max == 1.0f);
assert(contains(r.body_template, "vec2 u_pos"));
assert(contains(r.body_template, "u_params[__BASE__+0].xy"));
}
// 7. No uniforms at all → empty controls, body_template still ok
{
const char* src = R"glsl(
void main() {
vec2 uv = gl_FragCoord.xy / u_resolution;
fragColor = vec4(uv, 0.0, 1.0);
}
)glsl";
auto r = code_to_generator(src);
assert(r.ok);
assert(r.controls.empty());
assert(r.param_count == 0);
assert(contains(r.body_template, "return vec4(uv, 0.0, 1.0);"));
}
std::printf("code_to_generator: 7/7 asserts passed\n");
return 0;
}
#endif