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:
Binary file not shown.
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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()."
|
||||
---
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
```
|
||||
Reference in New Issue
Block a user