Files
fn_registry/cpp/functions/gfx/uniform_parser.cpp
T
egutierrez c7821c4c99 feat(shaders_lab): uniform annotations → auto-generated ImGui controls
- cpp/functions/gfx/uniform_parser: regex-based parser of @slider/@color/@toggle/@xy annotations (+ inline tests)
- cpp/functions/gfx/uniform_panel: ImGui widgets + value store + glUniform* apply
- shader_canvas: optional uniforms callback invoked per-frame
- gl_loader: +glUniform1i/3f/4f
- seed plasma: demo uniforms u_speed + u_color
- rebuild Windows .exe

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 21:02:35 +02:00

249 lines
8.5 KiB
C++

#include "gfx/uniform_parser.h"
#include <regex>
#include <sstream>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
namespace fn::gfx {
static const std::unordered_set<std::string> k_reserved = {
"u_resolution", "u_time", "u_mouse"
};
// Parse a comma-separated list of up to 4 floats into out[]. Returns count parsed.
static int parse_floats(const std::string& s, float out[4]) {
int n = 0;
std::istringstream ss(s);
std::string tok;
while (n < 4 && std::getline(ss, tok, ',')) {
try { out[n++] = std::stof(tok); } catch (...) {}
}
return n;
}
// Parse `key=value` pairs from annotation tail.
static std::unordered_map<std::string, std::string> parse_props(const std::string& tail) {
std::unordered_map<std::string, std::string> props;
std::regex kv_re(R"((\w+)=([^\s]+))");
auto begin = std::sregex_iterator(tail.begin(), tail.end(), kv_re);
auto end = std::sregex_iterator();
for (auto it = begin; it != end; ++it) {
props[(*it)[1].str()] = (*it)[2].str();
}
return props;
}
static void apply_defaults_for_type(UniformDescriptor& d) {
switch (d.glsl_type) {
case GLSLType::Float:
d.widget = WidgetKind::Slider;
d.min[0] = 0.0f; d.max[0] = 1.0f;
d.defaults[0] = 0.0f;
break;
case GLSLType::Int:
d.widget = WidgetKind::Slider;
d.min[0] = 0.0f; d.max[0] = 10.0f;
d.step = 1.0f;
d.defaults[0] = 0.0f;
break;
case GLSLType::Bool:
d.widget = WidgetKind::Toggle;
d.default_bool = false;
d.defaults[0] = 0.0f;
break;
case GLSLType::Vec2:
d.widget = WidgetKind::XY;
d.min[0] = d.min[1] = 0.0f;
d.max[0] = d.max[1] = 1.0f;
d.defaults[0] = d.defaults[1] = 0.5f;
break;
case GLSLType::Vec3:
d.widget = WidgetKind::Color;
d.defaults[0] = d.defaults[1] = d.defaults[2] = 1.0f;
break;
case GLSLType::Vec4:
d.widget = WidgetKind::Color;
d.defaults[0] = d.defaults[1] = d.defaults[2] = d.defaults[3] = 1.0f;
break;
}
}
static void apply_props(UniformDescriptor& d, const std::unordered_map<std::string, std::string>& props) {
auto get = [&](const char* k) -> const std::string* {
auto it = props.find(k);
return it != props.end() ? &it->second : nullptr;
};
if (auto* v = get("min")) {
float tmp[4] = {d.min[0], d.min[1], d.min[2], d.min[3]};
int n = parse_floats(*v, tmp);
for (int i = 0; i < 4; i++) d.min[i] = (i < n) ? tmp[i] : tmp[n > 0 ? n-1 : 0];
}
if (auto* v = get("max")) {
float tmp[4] = {d.max[0], d.max[1], d.max[2], d.max[3]};
int n = parse_floats(*v, tmp);
for (int i = 0; i < 4; i++) d.max[i] = (i < n) ? tmp[i] : tmp[n > 0 ? n-1 : 0];
}
if (auto* v = get("default")) {
if (d.glsl_type == GLSLType::Bool) {
d.default_bool = (*v == "true" || *v == "1");
d.defaults[0] = d.default_bool ? 1.0f : 0.0f;
} else {
float tmp[4] = {};
int n = parse_floats(*v, tmp);
for (int i = 0; i < 4; i++) d.defaults[i] = (i < n) ? tmp[i] : 0.0f;
}
}
if (auto* v = get("step")) {
try { d.step = std::stof(*v); } catch (...) {}
}
if (auto* v = get("log")) {
d.log_scale = (*v == "true");
}
}
std::vector<UniformDescriptor> parse_uniforms(const std::string& glsl_source) {
// Match: uniform <type> <name>; optionally followed by // @<widget> <props>
static const std::regex line_re(
R"(^\s*uniform\s+(\w+)\s+(\w+)\s*;(?:\s*//\s*@(\w+)(.*))?\s*$)"
);
std::vector<UniformDescriptor> result;
std::istringstream stream(glsl_source);
std::string line;
while (std::getline(stream, line)) {
std::smatch m;
if (!std::regex_match(line, m, line_re)) continue;
std::string type_str = m[1].str();
std::string name_str = m[2].str();
bool has_annotation = m[3].matched;
std::string widget_str = has_annotation ? m[3].str() : "";
std::string props_str = has_annotation ? m[4].str() : "";
// Skip reserved and unsupported
if (k_reserved.count(name_str)) continue;
if (type_str == "sampler2D") continue;
UniformDescriptor d;
d.name = name_str;
// Map GLSL type
if (type_str == "float") d.glsl_type = GLSLType::Float;
else if (type_str == "int") d.glsl_type = GLSLType::Int;
else if (type_str == "bool") d.glsl_type = GLSLType::Bool;
else if (type_str == "vec2") d.glsl_type = GLSLType::Vec2;
else if (type_str == "vec3") d.glsl_type = GLSLType::Vec3;
else if (type_str == "vec4") d.glsl_type = GLSLType::Vec4;
else continue; // unknown type — skip
// Set defaults for type first
apply_defaults_for_type(d);
if (has_annotation) {
// Override widget kind
if (widget_str == "slider") {
d.widget = (d.glsl_type == GLSLType::Vec2) ? WidgetKind::XY : WidgetKind::Slider;
}
else if (widget_str == "color") d.widget = WidgetKind::Color;
else if (widget_str == "toggle") d.widget = WidgetKind::Toggle;
else if (widget_str == "xy") d.widget = WidgetKind::XY;
auto props = parse_props(props_str);
apply_props(d, props);
}
result.push_back(d);
}
return result;
}
} // namespace fn::gfx
// ──────────────────────────────────────────────
// Inline tests — compile with -DUNIFORM_PARSER_TEST
// ──────────────────────────────────────────────
#ifdef UNIFORM_PARSER_TEST
#include <cassert>
#include <cstdio>
using namespace fn::gfx;
int main() {
// Test 1: sin anotación float → Slider 0..1
{
auto v = parse_uniforms("uniform float u_x;");
assert(v.size() == 1);
assert(v[0].name == "u_x");
assert(v[0].glsl_type == GLSLType::Float);
assert(v[0].widget == WidgetKind::Slider);
assert(v[0].min[0] == 0.0f);
assert(v[0].max[0] == 1.0f);
assert(v[0].defaults[0] == 0.0f);
printf("PASS: sin anotacion float -> Slider 0..1\n");
}
// Test 2: @slider min=0 max=5 default=1 sobre float
{
auto v = parse_uniforms("uniform float u_speed; // @slider min=0 max=5 default=1");
assert(v.size() == 1);
assert(v[0].widget == WidgetKind::Slider);
assert(v[0].min[0] == 0.0f);
assert(v[0].max[0] == 5.0f);
assert(v[0].defaults[0] == 1.0f);
printf("PASS: @slider min=0 max=5 default=1 sobre float\n");
}
// Test 3: @color default=0.1,0.2,0.5 sobre vec3
{
auto v = parse_uniforms("uniform vec3 u_color; // @color default=0.1,0.2,0.5");
assert(v.size() == 1);
assert(v[0].glsl_type == GLSLType::Vec3);
assert(v[0].widget == WidgetKind::Color);
assert(v[0].defaults[0] == 0.1f);
assert(v[0].defaults[1] == 0.2f);
assert(v[0].defaults[2] == 0.5f);
printf("PASS: @color default=0.1,0.2,0.5 sobre vec3\n");
}
// Test 4: @xy min=-1 max=1 default=0,0 sobre vec2
{
auto v = parse_uniforms("uniform vec2 u_pos; // @xy min=-1 max=1 default=0,0");
assert(v.size() == 1);
assert(v[0].glsl_type == GLSLType::Vec2);
assert(v[0].widget == WidgetKind::XY);
assert(v[0].min[0] == -1.0f);
assert(v[0].max[0] == 1.0f);
assert(v[0].defaults[0] == 0.0f);
assert(v[0].defaults[1] == 0.0f);
printf("PASS: @xy min=-1 max=1 default=0,0 sobre vec2\n");
}
// Test 5: uniform vec2 u_resolution ignorado (reservado)
{
auto v = parse_uniforms("uniform vec2 u_resolution;");
assert(v.empty());
printf("PASS: uniform vec2 u_resolution ignorado\n");
}
// Test 6: @toggle default=true sobre bool
{
auto v = parse_uniforms("uniform bool u_debug; // @toggle default=true");
assert(v.size() == 1);
assert(v[0].glsl_type == GLSLType::Bool);
assert(v[0].widget == WidgetKind::Toggle);
assert(v[0].default_bool == true);
assert(v[0].defaults[0] == 1.0f);
printf("PASS: @toggle default=true sobre bool\n");
}
printf("---\nAll 6 tests passed.\n");
return 0;
}
#endif