c7821c4c99
- 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>
249 lines
8.5 KiB
C++
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
|