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>
This commit is contained in:
2026-04-24 21:02:35 +02:00
parent 8e11c5cfce
commit c7821c4c99
14 changed files with 580 additions and 23 deletions
+2
View File
@@ -5,6 +5,8 @@ add_imgui_app(shaders_lab
${CMAKE_SOURCE_DIR}/functions/gfx/gl_framebuffer.cpp
${CMAKE_SOURCE_DIR}/functions/gfx/fullscreen_quad.cpp
${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/core/fps_overlay.cpp
)
target_include_directories(shaders_lab PRIVATE
+12 -2
View File
@@ -3,11 +3,14 @@
#include "gfx/shader_canvas.h"
#include "gfx/gl_shader.h"
#include "gfx/uniform_parser.h"
#include "gfx/uniform_panel.h"
#include "core/fps_overlay.h"
#include "seed_shaders.h"
#include <chrono>
#include <string>
#include <vector>
static fn::gfx::ShaderCanvas g_canvas;
static std::string g_source = PLASMA;
@@ -15,10 +18,14 @@ static std::string g_last_err;
static int g_last_err_line = -1;
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 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;
@@ -91,13 +98,16 @@ static void render() {
// --- Canvas panel ---
if (ImGui::Begin("Canvas")) {
fn::gfx::canvas_render(g_canvas, static_cast<float>(ImGui::GetTime()));
fn::gfx::canvas_render(g_canvas, static_cast<float>(ImGui::GetTime()),
[](unsigned int program) {
fn::gfx::uniforms_apply(g_store, g_descs, program);
});
}
ImGui::End();
// --- Controls panel ---
if (ImGui::Begin("Controls")) {
ImGui::TextDisabled("Controls (fase 2)");
fn::gfx::uniforms_panel(g_store, g_descs);
ImGui::Spacing();
fps_overlay();
}
+6 -16
View File
@@ -4,24 +4,14 @@
// compile_fragment() prepends those automatically.
static const char* PLASMA = 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 * 0.5;
float v1 = sin(uv.x * 10.0 + t);
float v2 = sin(uv.y * 10.0 + t * 1.3);
float v3 = sin((uv.x + uv.y) * 10.0 + t * 0.7);
float v4 = sin(length(uv - 0.5) * 20.0 - t * 2.0);
float v = (v1 + v2 + v3 + v4) * 0.25;
vec3 col = vec3(
sin(v * 3.14159 + 0.0) * 0.5 + 0.5,
sin(v * 3.14159 + 2.094) * 0.5 + 0.5,
sin(v * 3.14159 + 4.188) * 0.5 + 0.5
);
fragColor = vec4(col, 1.0);
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";
+6
View File
@@ -26,7 +26,10 @@ PFNGLGETUNIFORMLOCATIONPROC fn_glGetUniformLocation = nullptr;
PFNGLLINKPROGRAMPROC fn_glLinkProgram = nullptr;
PFNGLSHADERSOURCEPROC fn_glShaderSource = nullptr;
PFNGLUNIFORM1FPROC fn_glUniform1f = nullptr;
PFNGLUNIFORM1IPROC fn_glUniform1i = nullptr;
PFNGLUNIFORM2FPROC fn_glUniform2f = nullptr;
PFNGLUNIFORM3FPROC fn_glUniform3f = nullptr;
PFNGLUNIFORM4FPROC fn_glUniform4f = nullptr;
PFNGLUSEPROGRAMPROC fn_glUseProgram = nullptr;
namespace fn::gfx {
@@ -60,7 +63,10 @@ bool gl_loader_init() {
LOAD(glLinkProgram);
LOAD(glShaderSource);
LOAD(glUniform1f);
LOAD(glUniform1i);
LOAD(glUniform2f);
LOAD(glUniform3f);
LOAD(glUniform4f);
LOAD(glUseProgram);
#undef LOAD
+6
View File
@@ -32,7 +32,10 @@
extern PFNGLLINKPROGRAMPROC fn_glLinkProgram;
extern PFNGLSHADERSOURCEPROC fn_glShaderSource;
extern PFNGLUNIFORM1FPROC fn_glUniform1f;
extern PFNGLUNIFORM1IPROC fn_glUniform1i;
extern PFNGLUNIFORM2FPROC fn_glUniform2f;
extern PFNGLUNIFORM3FPROC fn_glUniform3f;
extern PFNGLUNIFORM4FPROC fn_glUniform4f;
extern PFNGLUSEPROGRAMPROC fn_glUseProgram;
#define glAttachShader fn_glAttachShader
@@ -59,7 +62,10 @@
#define glLinkProgram fn_glLinkProgram
#define glShaderSource fn_glShaderSource
#define glUniform1f fn_glUniform1f
#define glUniform1i fn_glUniform1i
#define glUniform2f fn_glUniform2f
#define glUniform3f fn_glUniform3f
#define glUniform4f fn_glUniform4f
#define glUseProgram fn_glUseProgram
#else
#define GL_GLEXT_PROTOTYPES
+5 -1
View File
@@ -1,6 +1,7 @@
#include "gfx/gl_loader.h"
#include "gfx/shader_canvas.h"
#include "imgui.h"
#include <functional>
namespace fn::gfx {
@@ -17,7 +18,8 @@ void canvas_set_program(ShaderCanvas& c, unsigned int program) {
c.program = program;
}
void canvas_render(ShaderCanvas& c, float time_seconds) {
void canvas_render(ShaderCanvas& c, float time_seconds,
const std::function<void(unsigned int program)>& uniforms_fn) {
ImVec2 avail = ImGui::GetContentRegionAvail();
int w = static_cast<int>(avail.x);
int h = static_cast<int>(avail.y);
@@ -56,6 +58,8 @@ void canvas_render(ShaderCanvas& c, float time_seconds) {
glUniform2f(loc_mouse, mx, my);
}
if (uniforms_fn) uniforms_fn(c.program);
quad_draw(c.quad);
glUseProgram(0);
}
+4 -1
View File
@@ -1,4 +1,5 @@
#pragma once
#include <functional>
#include <string>
#include "gfx/gl_framebuffer.h"
#include "gfx/gl_shader.h"
@@ -21,7 +22,9 @@ void canvas_set_program(ShaderCanvas& c, unsigned int program);
// Renderiza el shader al FBO y dibuja la textura resultante como contenido del panel ImGui.
// Llamar DENTRO de un ImGui::Begin/End. Ocupa GetContentRegionAvail().
void canvas_render(ShaderCanvas& c, float time_seconds);
// uniforms_fn se invoca tras glUseProgram y antes de quad_draw, recibe el program ID.
void canvas_render(ShaderCanvas& c, float time_seconds,
const std::function<void(unsigned int program)>& uniforms_fn = {});
void canvas_destroy(ShaderCanvas& c);
+6 -3
View File
@@ -3,15 +3,16 @@ name: shader_canvas
kind: component
lang: cpp
domain: gfx
version: "1.0.0"
version: "1.1.0"
purity: impure
signature: "void canvas_render(ShaderCanvas& c, float time_seconds)"
description: "Componente ImGui que renderiza un fragment shader GLSL a un FBO y lo muestra en el panel actual. Compone gl_framebuffer, fullscreen_quad y gl_shader. Gestiona resize automático y coordenadas de mouse."
signature: "void canvas_render(ShaderCanvas& c, float time_seconds, const std::function<void(unsigned int)>& uniforms_fn = {})"
description: "Componente ImGui que renderiza un fragment shader GLSL a un FBO y lo muestra en el panel actual. Compone gl_framebuffer, fullscreen_quad y gl_shader. Gestiona resize automático y coordenadas de mouse. Acepta callback opcional uniforms_fn invocado tras glUseProgram para uniforms custom."
tags: [opengl, shader, canvas, imgui, fbo, gfx, component]
uses_functions:
- gl_shader_cpp_gfx
- gl_framebuffer_cpp_gfx
- fullscreen_quad_cpp_gfx
- uniform_panel_cpp_gfx
uses_types: []
returns: []
returns_optional: false
@@ -27,6 +28,8 @@ params:
desc: "ShaderCanvas con estado GL interno (fb, quad, program). Inicializar con canvas_init() antes del primer frame."
- name: time_seconds
desc: "Tiempo en segundos para el uniform u_time. Usar ImGui::GetTime() o clock propio."
- name: uniforms_fn
desc: "Callback opcional invocado tras glUseProgram y antes de quad_draw. Recibe el program ID. Usar para aplicar uniforms custom (ej: uniforms_apply de uniform_panel)."
output: "Dibuja ImGui::Image con la textura del FBO renderizado. El panel ImGui debe estar abierto (entre Begin/End). Ocupa GetContentRegionAvail()."
---
+91
View File
@@ -0,0 +1,91 @@
#include "gfx/uniform_panel.h"
#include "gfx/gl_loader.h"
#include "imgui.h"
namespace fn::gfx {
void uniforms_sync(UniformStore& store, const std::vector<UniformDescriptor>& descs) {
// Remove keys no longer present
for (auto it = store.values.begin(); it != store.values.end(); ) {
bool found = false;
for (auto& d : descs) if (d.name == it->first) { found = true; break; }
it = found ? std::next(it) : store.values.erase(it);
}
// Add new entries with defaults
for (auto& d : descs) {
if (store.values.count(d.name)) continue;
std::array<float, 4> arr = {
d.defaults[0], d.defaults[1], d.defaults[2], d.defaults[3]
};
store.values[d.name] = arr;
}
}
void uniforms_panel(UniformStore& store, const std::vector<UniformDescriptor>& descs) {
if (descs.empty()) {
ImGui::TextDisabled("Declare uniforms with @slider ... annotations.");
return;
}
for (auto& d : descs) {
auto it = store.values.find(d.name);
if (it == store.values.end()) continue;
float* v = it->second.data();
switch (d.widget) {
case WidgetKind::Slider: {
if (d.glsl_type == GLSLType::Int) {
int tmp = static_cast<int>(v[0]);
if (ImGui::SliderInt(d.name.c_str(), &tmp,
static_cast<int>(d.min[0]),
static_cast<int>(d.max[0]))) {
v[0] = static_cast<float>(tmp);
}
} else {
ImGuiSliderFlags flags = d.log_scale ? ImGuiSliderFlags_Logarithmic : 0;
ImGui::SliderFloat(d.name.c_str(), &v[0], d.min[0], d.max[0], "%.3f", flags);
}
break;
}
case WidgetKind::Color: {
if (d.glsl_type == GLSLType::Vec4)
ImGui::ColorEdit4(d.name.c_str(), v);
else
ImGui::ColorEdit3(d.name.c_str(), v);
break;
}
case WidgetKind::Toggle: {
bool tmp = v[0] != 0.0f;
if (ImGui::Checkbox(d.name.c_str(), &tmp))
v[0] = tmp ? 1.0f : 0.0f;
break;
}
case WidgetKind::XY: {
ImGui::SliderFloat2(d.name.c_str(), v, d.min[0], d.max[0]);
break;
}
}
}
}
void uniforms_apply(const UniformStore& store, const std::vector<UniformDescriptor>& descs, unsigned int program) {
for (auto& d : descs) {
auto it = store.values.find(d.name);
if (it == store.values.end()) continue;
const float* v = it->second.data();
GLint loc = glGetUniformLocation(program, d.name.c_str());
if (loc < 0) continue;
switch (d.glsl_type) {
case GLSLType::Float: glUniform1f(loc, v[0]); break;
case GLSLType::Int: glUniform1i(loc, static_cast<GLint>(v[0])); break;
case GLSLType::Bool: glUniform1i(loc, v[0] != 0.0f ? 1 : 0); break;
case GLSLType::Vec2: glUniform2f(loc, v[0], v[1]); break;
case GLSLType::Vec3: glUniform3f(loc, v[0], v[1], v[2]); break;
case GLSLType::Vec4: glUniform4f(loc, v[0], v[1], v[2], v[3]); break;
}
}
}
} // namespace fn::gfx
+24
View File
@@ -0,0 +1,24 @@
#pragma once
#include <array>
#include <string>
#include <unordered_map>
#include <vector>
#include "gfx/uniform_parser.h"
namespace fn::gfx {
struct UniformStore {
std::unordered_map<std::string, std::array<float, 4>> values;
};
// Mantiene el store sincronizado con los descriptores actuales.
void uniforms_sync(UniformStore& store, const std::vector<UniformDescriptor>& descs);
// Renderiza widgets ImGui en el panel actual (llamar entre Begin/End).
void uniforms_panel(UniformStore& store, const std::vector<UniformDescriptor>& descs);
// Llama glUniform* sobre el programa GL activo (el caller debe haber llamado glUseProgram).
void uniforms_apply(const UniformStore& store, const std::vector<UniformDescriptor>& descs, unsigned int program);
} // namespace fn::gfx
+60
View File
@@ -0,0 +1,60 @@
---
name: uniform_panel
kind: component
lang: cpp
domain: gfx
version: "1.0.0"
purity: impure
signature: "void uniforms_panel(UniformStore&, const std::vector<UniformDescriptor>&)"
description: "Panel ImGui auto-generado a partir de UniformDescriptor. Sincroniza un UniformStore con los descriptores, renderiza widgets (slider/color/toggle/xy) y aplica los valores al programa GL activo vía glUniform*."
tags: [opengl, shader, uniforms, imgui, controls, gfx, component]
uses_functions:
- uniform_parser_cpp_gfx
- gl_loader_cpp_gfx
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: [imgui, gl_loader, uniform_parser, array, unordered_map]
tested: false
tests: []
test_file_path: ""
file_path: "cpp/functions/gfx/uniform_panel.cpp"
framework: imgui
params:
- name: store
desc: "Mapa nombre→array<float,4> con los valores actuales de cada uniform. Se modifica in-place al interactuar con los widgets."
- name: descs
desc: "Descriptores parseados por parse_uniforms(). Determinan qué widgets se renderizan y con qué rangos."
- name: program
desc: "(uniforms_apply) ID del programa GL sobre el que aplicar los valores. Debe estar activo con glUseProgram antes de llamar."
output: "Dibuja los widgets ImGui en el panel actual. uniforms_apply llama glUniform* actualizando los uniforms del programa para el frame actual."
---
# uniform_panel
Tres funciones que forman el ciclo completo de controles de uniforms:
```
parse_uniforms(src) → descs
uniforms_sync(store, descs) // al compilar shader
uniforms_panel(store, descs) // cada frame, dentro de ImGui::Begin/End
uniforms_apply(store, descs, program) // cada frame, dentro de canvas_render callback
```
## Widgets por tipo
| GLSLType | WidgetKind | ImGui call |
|----------|------------|-------------------------------------|
| Float | Slider | SliderFloat (+ flag Logarithmic) |
| Int | Slider | SliderInt |
| Bool | Toggle | Checkbox |
| Vec2 | XY | SliderFloat2 |
| Vec3 | Color | ColorEdit3 |
| Vec4 | Color | ColorEdit4 |
## Notas
- `uniforms_sync` elimina entradas obsoletas y añade nuevas con sus defaults. Preserva valores actuales para uniforms que siguen presentes.
- Si no hay descriptores, `uniforms_panel` muestra `"Declare uniforms with @slider ... annotations."`.
- `uniforms_apply` usa `glGetUniformLocation` por nombre; si no existe (loc < 0), lo salta silenciosamente.
+248
View File
@@ -0,0 +1,248 @@
#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
+28
View File
@@ -0,0 +1,28 @@
#pragma once
#include <string>
#include <vector>
namespace fn::gfx {
enum class GLSLType { Float, Int, Bool, Vec2, Vec3, Vec4 };
enum class WidgetKind { Slider, Color, Toggle, XY };
struct UniformDescriptor {
std::string name;
GLSLType glsl_type = GLSLType::Float;
WidgetKind widget = WidgetKind::Slider;
float min[4] = {0, 0, 0, 0};
float max[4] = {1, 1, 1, 1};
float defaults[4] = {0, 0, 0, 0};
float step = 0.0f;
bool log_scale = false;
bool default_bool = false;
};
// Parsea líneas `uniform <type> <name>; // @<widget> key=value ...`
// Ignora u_resolution, u_time, u_mouse (reservados).
// Ignora sampler2D silenciosamente.
// Sin anotación: defaults por tipo.
std::vector<UniformDescriptor> parse_uniforms(const std::string& glsl_source);
} // namespace fn::gfx
+82
View File
@@ -0,0 +1,82 @@
---
name: uniform_parser
kind: function
lang: cpp
domain: gfx
version: "1.0.0"
purity: pure
signature: "std::vector<UniformDescriptor> parse_uniforms(const std::string& glsl_source)"
description: "Parsea un fragment shader GLSL y extrae descriptores de uniforms anotados con @slider/@color/@toggle/@xy. Ignora los uniforms reservados (u_resolution, u_time, u_mouse) y sampler2D. Sin anotación aplica defaults por tipo."
tags: [opengl, shader, uniforms, parser, imgui, gfx]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: ""
imports: [regex, string, vector, sstream, unordered_map, unordered_set]
tested: true
tests:
- "sin anotacion float -> Slider 0..1"
- "@slider min=0 max=5 default=1 sobre float"
- "@color default=0.1,0.2,0.5 sobre vec3"
- "@xy min=-1 max=1 default=0,0 sobre vec2"
- "uniform vec2 u_resolution ignorado"
- "@toggle default=true sobre bool"
test_file_path: "cpp/functions/gfx/uniform_parser.cpp"
file_path: "cpp/functions/gfx/uniform_parser.cpp"
params:
- name: glsl_source
desc: "Código fuente GLSL completo del fragment shader. Se escanea línea a línea buscando declaraciones uniform con anotaciones opcionales."
output: "Vector de UniformDescriptor ordenado según aparición en el shader. Cada descriptor tiene nombre, tipo GLSL, widget ImGui, rangos min/max, valor por defecto y flags (log_scale, step, default_bool)."
---
# uniform_parser
Parsea declaraciones `uniform <type> <name>; // @<widget> key=value ...` en GLSL.
## Sintaxis de anotación
```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
uniform bool u_debug; // @toggle default=true
uniform vec2 u_offset; // @xy min=-1 max=1 default=0,0
```
### Widgets disponibles
| Widget | Tipos soportados | Props relevantes |
|----------|-------------------|---------------------------------------------|
| `slider` | float, int, vec2 | min, max, default, step, log=true |
| `color` | vec3, vec4 | default (lista comma-separated) |
| `toggle` | bool | default=true/false |
| `xy` | vec2 | min, max, default (dos componentes) |
### Defaults por tipo sin anotación
| Tipo | Widget | min | max | default |
|-------|--------|------|------|-------------|
| float | Slider | 0 | 1 | 0 |
| int | Slider | 0 | 10 | 0, step=1 |
| bool | Toggle | — | — | false |
| vec2 | XY | 0,0 | 1,1 | 0.5,0.5 |
| vec3 | Color | — | — | 1,1,1 |
| vec4 | Color | — | — | 1,1,1,1 |
## Notas
- `@slider` sobre `vec2` equivale a `@xy`.
- Props desconocidas o de otro widget se ignoran silenciosamente.
- No lanza excepciones; errores de parse usan defaults.
- `u_resolution`, `u_time`, `u_mouse` y `sampler2D` se ignoran.
### Tests
Build y ejecución del test inline:
```bash
g++ -std=c++17 -DUNIFORM_PARSER_TEST -I cpp/functions \
cpp/functions/gfx/uniform_parser.cpp \
-o /tmp/uniform_parser_test \
&& /tmp/uniform_parser_test
```