feat(shaders_lab): scaffold C++ app with GLSL live-reload canvas
- cpp/functions/gfx: gl_shader, gl_framebuffer, fullscreen_quad, shader_canvas - cpp/apps/shaders_lab: main + 3 seed shaders (plasma, circle, checker) - ImGui docking layout: Code | Canvas | Controls Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
#define GL_GLEXT_PROTOTYPES
|
||||
#include <GL/gl.h>
|
||||
#include <GL/glext.h>
|
||||
|
||||
#include "gfx/fullscreen_quad.h"
|
||||
|
||||
namespace fn::gfx {
|
||||
|
||||
void quad_init(Quad& q) {
|
||||
glGenVertexArrays(1, &q.vao);
|
||||
glGenBuffers(1, &q.vbo);
|
||||
// Vertex shader generates positions from gl_VertexID — VBO stays empty.
|
||||
glBindVertexArray(q.vao);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, q.vbo);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
void quad_draw(const Quad& q) {
|
||||
glBindVertexArray(q.vao);
|
||||
glDrawArrays(GL_TRIANGLES, 0, 6);
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
void quad_destroy(Quad& q) {
|
||||
if (q.vao) { glDeleteVertexArrays(1, &q.vao); q.vao = 0; }
|
||||
if (q.vbo) { glDeleteBuffers(1, &q.vbo); q.vbo = 0; }
|
||||
}
|
||||
|
||||
} // namespace fn::gfx
|
||||
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
namespace fn::gfx {
|
||||
|
||||
struct Quad {
|
||||
unsigned int vao = 0;
|
||||
unsigned int vbo = 0;
|
||||
};
|
||||
|
||||
void quad_init(Quad& q);
|
||||
void quad_draw(const Quad& q);
|
||||
void quad_destroy(Quad& q);
|
||||
|
||||
} // namespace fn::gfx
|
||||
@@ -0,0 +1,45 @@
|
||||
---
|
||||
name: fullscreen_quad
|
||||
kind: function
|
||||
lang: cpp
|
||||
domain: gfx
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "void quad_init(Quad& q); void quad_draw(const Quad& q); void quad_destroy(Quad& q)"
|
||||
description: "VAO/VBO para un fullscreen quad de 6 vértices. El vertex shader genera las posiciones via gl_VertexID, por lo que el VBO queda vacío. quad_draw emite glDrawArrays(GL_TRIANGLES, 0, 6)."
|
||||
tags: [opengl, quad, fullscreen, vao, vbo, gfx]
|
||||
uses_functions: []
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
imports: [GL/gl.h, GL/glext.h]
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "cpp/functions/gfx/fullscreen_quad.cpp"
|
||||
framework: opengl
|
||||
params:
|
||||
- name: q
|
||||
desc: "Struct Quad con campos vao, vbo (GL ids). Inicializar a {0} antes de quad_init."
|
||||
output: "Modifica q in-place. quad_draw necesita un programa GL activo con vertex shader que genere posiciones desde gl_VertexID."
|
||||
---
|
||||
|
||||
# fullscreen_quad
|
||||
|
||||
VAO + VBO vacío para dibujar un quad de pantalla completa. El vertex shader de `gl_shader` ya embebe las 6 posiciones via `gl_VertexID`, por lo que no se necesitan datos en el VBO.
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```cpp
|
||||
fn::gfx::Quad quad{};
|
||||
fn::gfx::quad_init(quad);
|
||||
|
||||
// En el render loop (con program activo):
|
||||
glUseProgram(program);
|
||||
fn::gfx::quad_draw(quad);
|
||||
glUseProgram(0);
|
||||
|
||||
// Al destruir:
|
||||
fn::gfx::quad_destroy(quad);
|
||||
```
|
||||
@@ -0,0 +1,49 @@
|
||||
#define GL_GLEXT_PROTOTYPES
|
||||
#include <GL/gl.h>
|
||||
#include <GL/glext.h>
|
||||
|
||||
#include "gfx/gl_framebuffer.h"
|
||||
|
||||
namespace fn::gfx {
|
||||
|
||||
static void create_tex(Framebuffer& f) {
|
||||
glGenTextures(1, &f.tex);
|
||||
glBindTexture(GL_TEXTURE_2D, f.tex);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, f.width, f.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
void fb_init(Framebuffer& f) {
|
||||
f.width = 1;
|
||||
f.height = 1;
|
||||
create_tex(f);
|
||||
glGenFramebuffers(1, &f.fbo);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, f.fbo);
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, f.tex, 0);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
void fb_resize(Framebuffer& f, int w, int h) {
|
||||
if (w == f.width && h == f.height) return;
|
||||
f.width = w;
|
||||
f.height = h;
|
||||
if (f.tex) glDeleteTextures(1, &f.tex);
|
||||
f.tex = 0;
|
||||
create_tex(f);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, f.fbo);
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, f.tex, 0);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
void fb_destroy(Framebuffer& f) {
|
||||
if (f.fbo) { glDeleteFramebuffers(1, &f.fbo); f.fbo = 0; }
|
||||
if (f.tex) { glDeleteTextures(1, &f.tex); f.tex = 0; }
|
||||
f.width = 0;
|
||||
f.height = 0;
|
||||
}
|
||||
|
||||
} // namespace fn::gfx
|
||||
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
namespace fn::gfx {
|
||||
|
||||
struct Framebuffer {
|
||||
unsigned int fbo = 0;
|
||||
unsigned int tex = 0; // GL_RGBA8, clamp, linear
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
};
|
||||
|
||||
void fb_init(Framebuffer& f); // crea fbo+tex 1x1 iniciales
|
||||
void fb_resize(Framebuffer& f, int w, int h); // no-op si w,h iguales
|
||||
void fb_destroy(Framebuffer& f);
|
||||
|
||||
} // namespace fn::gfx
|
||||
@@ -0,0 +1,62 @@
|
||||
---
|
||||
name: gl_framebuffer
|
||||
kind: function
|
||||
lang: cpp
|
||||
domain: gfx
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "void fb_init(Framebuffer& f); void fb_resize(Framebuffer& f, int w, int h); void fb_destroy(Framebuffer& f)"
|
||||
description: "CRUD de un framebuffer OpenGL (FBO + textura RGBA8). fb_resize es no-op si las dimensiones no cambian. Listo para uso con ImGui::Image."
|
||||
tags: [opengl, framebuffer, fbo, texture, gfx, offscreen]
|
||||
uses_functions: []
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
imports: [GL/gl.h, GL/glext.h]
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "cpp/functions/gfx/gl_framebuffer.cpp"
|
||||
framework: opengl
|
||||
params:
|
||||
- name: f
|
||||
desc: "Struct Framebuffer con campos fbo, tex (GL ids), width, height. Inicializar a {0} antes de fb_init."
|
||||
- name: w
|
||||
desc: "Ancho deseado en pixels (fb_resize)"
|
||||
- name: h
|
||||
desc: "Alto deseado en pixels (fb_resize)"
|
||||
output: "Modifica f in-place. Después de fb_init, f.fbo y f.tex son IDs GL válidos. fb_destroy pone todos los campos a 0."
|
||||
---
|
||||
|
||||
# gl_framebuffer
|
||||
|
||||
FBO con textura color RGBA8 (GL_CLAMP_TO_EDGE, GL_LINEAR). Diseñado para renderizado offscreen y posterior display via `ImGui::Image`.
|
||||
|
||||
## Ciclo de vida
|
||||
|
||||
```cpp
|
||||
fn::gfx::Framebuffer fb{};
|
||||
fn::gfx::fb_init(fb); // fbo + tex 1x1
|
||||
|
||||
// En el render loop:
|
||||
fn::gfx::fb_resize(fb, w, h); // no-op si mismas dimensiones
|
||||
|
||||
// Al destruir:
|
||||
fn::gfx::fb_destroy(fb);
|
||||
```
|
||||
|
||||
## Uso con ImGui::Image
|
||||
|
||||
```cpp
|
||||
// Flip Y porque OpenGL tiene origen bottom-left
|
||||
ImGui::Image(
|
||||
reinterpret_cast<ImTextureID>(static_cast<uintptr_t>(fb.tex)),
|
||||
size,
|
||||
ImVec2(0, 1), ImVec2(1, 0)
|
||||
);
|
||||
```
|
||||
|
||||
## Notas
|
||||
|
||||
`fb_resize` recrea solo la textura (no el FBO) cuando las dimensiones cambian, reattachando la nueva textura al FBO existente. Esto minimiza el overhead de resize.
|
||||
@@ -0,0 +1,119 @@
|
||||
#define GL_GLEXT_PROTOTYPES
|
||||
#include <GL/gl.h>
|
||||
#include <GL/glext.h>
|
||||
|
||||
#include "gfx/gl_shader.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <regex>
|
||||
#include <string>
|
||||
|
||||
namespace fn::gfx {
|
||||
|
||||
static const char* k_vert_src = R"glsl(
|
||||
#version 330 core
|
||||
const vec2 verts[6] = vec2[](
|
||||
vec2(-1,-1), vec2(1,-1), vec2(-1,1),
|
||||
vec2(1,-1), vec2(1,1), vec2(-1,1)
|
||||
);
|
||||
void main() { gl_Position = vec4(verts[gl_VertexID], 0.0, 1.0); }
|
||||
)glsl";
|
||||
|
||||
static const char* k_frag_preamble =
|
||||
"#version 330 core\n"
|
||||
"out vec4 fragColor;\n"
|
||||
"uniform vec2 u_resolution;\n"
|
||||
"uniform float u_time;\n"
|
||||
"uniform vec2 u_mouse;\n";
|
||||
|
||||
static int parse_err_line(const char* log) {
|
||||
// Try "ERROR: 0:<line>:" format
|
||||
std::regex re1(R"(ERROR:\s*\d+:(\d+):)");
|
||||
// Try "0(<line>)" format
|
||||
std::regex re2(R"(\d+\((\d+)\))");
|
||||
std::cmatch m;
|
||||
if (std::regex_search(log, m, re1)) {
|
||||
return std::stoi(m[1].str());
|
||||
}
|
||||
if (std::regex_search(log, m, re2)) {
|
||||
return std::stoi(m[1].str());
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static unsigned int compile_shader(GLenum type, const char** srcs, int count, std::string& out_err, int& out_line) {
|
||||
unsigned int s = glCreateShader(type);
|
||||
glShaderSource(s, count, srcs, nullptr);
|
||||
glCompileShader(s);
|
||||
GLint ok = 0;
|
||||
glGetShaderiv(s, GL_COMPILE_STATUS, &ok);
|
||||
if (!ok) {
|
||||
GLint len = 0;
|
||||
glGetShaderiv(s, GL_INFO_LOG_LENGTH, &len);
|
||||
out_err.resize(static_cast<size_t>(len));
|
||||
glGetShaderInfoLog(s, len, nullptr, &out_err[0]);
|
||||
out_line = parse_err_line(out_err.c_str());
|
||||
glDeleteShader(s);
|
||||
return 0;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
CompileResult compile_fragment(const std::string& user_fragment_src) {
|
||||
CompileResult result;
|
||||
|
||||
// Vertex shader (no preamble needed — it's a standalone complete shader)
|
||||
std::string vert_err;
|
||||
int vert_line = -1;
|
||||
unsigned int vert = compile_shader(GL_VERTEX_SHADER, &k_vert_src, 1, vert_err, vert_line);
|
||||
if (!vert) {
|
||||
result.err_msg = "vertex: " + vert_err;
|
||||
result.err_line = vert_line;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Fragment shader — prepend preamble, then user body
|
||||
const char* frag_srcs[2] = { k_frag_preamble, user_fragment_src.c_str() };
|
||||
std::string frag_err;
|
||||
int frag_line = -1;
|
||||
unsigned int frag = compile_shader(GL_FRAGMENT_SHADER, frag_srcs, 2, frag_err, frag_line);
|
||||
if (!frag) {
|
||||
glDeleteShader(vert);
|
||||
result.err_msg = frag_err;
|
||||
// Adjust line: subtract preamble lines (4 lines)
|
||||
if (frag_line > 4) result.err_line = frag_line - 4;
|
||||
else result.err_line = frag_line;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Link
|
||||
unsigned int prog = glCreateProgram();
|
||||
glAttachShader(prog, vert);
|
||||
glAttachShader(prog, frag);
|
||||
glLinkProgram(prog);
|
||||
glDeleteShader(vert);
|
||||
glDeleteShader(frag);
|
||||
|
||||
GLint ok = 0;
|
||||
glGetProgramiv(prog, GL_LINK_STATUS, &ok);
|
||||
if (!ok) {
|
||||
GLint len = 0;
|
||||
glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &len);
|
||||
result.err_msg.resize(static_cast<size_t>(len));
|
||||
glGetProgramInfoLog(prog, len, nullptr, &result.err_msg[0]);
|
||||
result.err_line = parse_err_line(result.err_msg.c_str());
|
||||
glDeleteProgram(prog);
|
||||
return result;
|
||||
}
|
||||
|
||||
result.program = prog;
|
||||
result.ok = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
void delete_program(unsigned int program) {
|
||||
if (program) glDeleteProgram(program);
|
||||
}
|
||||
|
||||
} // namespace fn::gfx
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
|
||||
namespace fn::gfx {
|
||||
|
||||
struct CompileResult {
|
||||
unsigned int program = 0; // GL program id, 0 si falla
|
||||
bool ok = false;
|
||||
int err_line = -1; // línea parseada del infoLog, -1 si no
|
||||
std::string err_msg;
|
||||
};
|
||||
|
||||
// Compila un fragment shader GLSL (sólo el cuerpo del usuario).
|
||||
// Prepends automáticamente: version, out vec4 fragColor, y uniforms u_resolution/u_time/u_mouse.
|
||||
// Usa un vertex shader fijo que genera un fullscreen quad via gl_VertexID.
|
||||
// Si falla, program = 0. Si ok, program es una id válida de glProgram lista para usar.
|
||||
CompileResult compile_fragment(const std::string& user_fragment_src);
|
||||
|
||||
// Libera el programa. Seguro con id = 0.
|
||||
void delete_program(unsigned int program);
|
||||
|
||||
} // namespace fn::gfx
|
||||
@@ -0,0 +1,66 @@
|
||||
---
|
||||
name: gl_shader
|
||||
kind: function
|
||||
lang: cpp
|
||||
domain: gfx
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "CompileResult compile_fragment(const std::string& user_fragment_src)"
|
||||
description: "Compila un cuerpo de fragment shader GLSL 330 y retorna un GL program listo para usar. Prepende automáticamente version, out vec4 fragColor y uniforms u_resolution/u_time/u_mouse. Usa GL_GLEXT_PROTOTYPES + GL/glext.h."
|
||||
tags: [opengl, shader, glsl, compile, fragment, gfx]
|
||||
uses_functions: []
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
imports: [GL/gl.h, GL/glext.h]
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "cpp/functions/gfx/gl_shader.cpp"
|
||||
framework: opengl
|
||||
params:
|
||||
- name: user_fragment_src
|
||||
desc: "Cuerpo del fragment shader GLSL sin #version, sin 'out vec4 fragColor' ni declaraciones de uniforms. Solo el void main() y funciones auxiliares."
|
||||
output: "CompileResult con program=GL id si ok=true, o err_msg/err_line si falla. program=0 indica error."
|
||||
---
|
||||
|
||||
# gl_shader
|
||||
|
||||
Compila y enlaza un fragment shader GLSL 330 contra un vertex shader fijo que genera un fullscreen quad via `gl_VertexID`.
|
||||
|
||||
## Vertex shader fijo
|
||||
|
||||
```glsl
|
||||
#version 330 core
|
||||
const vec2 verts[6] = vec2[](
|
||||
vec2(-1,-1), vec2(1,-1), vec2(-1,1),
|
||||
vec2(1,-1), vec2(1,1), vec2(-1,1)
|
||||
);
|
||||
void main() { gl_Position = vec4(verts[gl_VertexID], 0.0, 1.0); }
|
||||
```
|
||||
|
||||
## Preamble prepended al fragment
|
||||
|
||||
```glsl
|
||||
#version 330 core
|
||||
out vec4 fragColor;
|
||||
uniform vec2 u_resolution;
|
||||
uniform float u_time;
|
||||
uniform vec2 u_mouse;
|
||||
```
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```cpp
|
||||
auto r = fn::gfx::compile_fragment("void main() { fragColor = vec4(1,0,0,1); }");
|
||||
if (r.ok) {
|
||||
glUseProgram(r.program);
|
||||
} else {
|
||||
fprintf(stderr, "line %d: %s\n", r.err_line, r.err_msg.c_str());
|
||||
}
|
||||
```
|
||||
|
||||
## Notas
|
||||
|
||||
Usa `#define GL_GLEXT_PROTOTYPES` + `<GL/gl.h>` + `<GL/glext.h>` (mismo patrón que `graph_renderer`). El loader de ImGui ya ha inicializado los symbols GL antes de que esta función sea llamada. El err_line del fragment se ajusta restando las 4 líneas del preamble.
|
||||
@@ -0,0 +1,85 @@
|
||||
#define GL_GLEXT_PROTOTYPES
|
||||
#include <GL/gl.h>
|
||||
#include <GL/glext.h>
|
||||
|
||||
#include "gfx/shader_canvas.h"
|
||||
#include "imgui.h"
|
||||
|
||||
namespace fn::gfx {
|
||||
|
||||
void canvas_init(ShaderCanvas& c) {
|
||||
if (c.initialized) return;
|
||||
fb_init(c.fb);
|
||||
quad_init(c.quad);
|
||||
c.initialized = true;
|
||||
}
|
||||
|
||||
void canvas_set_program(ShaderCanvas& c, unsigned int program) {
|
||||
if (c.program) delete_program(c.program);
|
||||
c.program = program;
|
||||
}
|
||||
|
||||
void canvas_render(ShaderCanvas& c, float time_seconds) {
|
||||
ImVec2 avail = ImGui::GetContentRegionAvail();
|
||||
int w = static_cast<int>(avail.x);
|
||||
int h = static_cast<int>(avail.y);
|
||||
if (w < 1) w = 1;
|
||||
if (h < 1) h = 1;
|
||||
|
||||
fb_resize(c.fb, w, h);
|
||||
|
||||
// Save GL state
|
||||
GLint prev_fbo = 0;
|
||||
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &prev_fbo);
|
||||
GLint prev_vp[4];
|
||||
glGetIntegerv(GL_VIEWPORT, prev_vp);
|
||||
|
||||
// Render to FBO
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, c.fb.fbo);
|
||||
glViewport(0, 0, w, h);
|
||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
if (c.program) {
|
||||
glUseProgram(c.program);
|
||||
|
||||
GLint loc_res = glGetUniformLocation(c.program, "u_resolution");
|
||||
GLint loc_time = glGetUniformLocation(c.program, "u_time");
|
||||
GLint loc_mouse = glGetUniformLocation(c.program, "u_mouse");
|
||||
|
||||
if (loc_res >= 0) glUniform2f(loc_res, static_cast<float>(w), static_cast<float>(h));
|
||||
if (loc_time >= 0) glUniform1f(loc_time, time_seconds);
|
||||
if (loc_mouse >= 0) {
|
||||
ImVec2 mouse = ImGui::GetMousePos();
|
||||
ImVec2 canvas_pos = ImGui::GetCursorScreenPos();
|
||||
// cursor pos is AFTER content region starts
|
||||
float mx = mouse.x - canvas_pos.x;
|
||||
float my = static_cast<float>(h) - (mouse.y - canvas_pos.y);
|
||||
glUniform2f(loc_mouse, mx, my);
|
||||
}
|
||||
|
||||
quad_draw(c.quad);
|
||||
glUseProgram(0);
|
||||
}
|
||||
|
||||
// Restore GL state
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, static_cast<GLuint>(prev_fbo));
|
||||
glViewport(prev_vp[0], prev_vp[1], prev_vp[2], prev_vp[3]);
|
||||
|
||||
// Draw texture in ImGui panel (flip V: OpenGL origin is bottom-left)
|
||||
ImGui::Image(
|
||||
(ImTextureID)(intptr_t)c.fb.tex,
|
||||
avail,
|
||||
ImVec2(0, 1), ImVec2(1, 0)
|
||||
);
|
||||
}
|
||||
|
||||
void canvas_destroy(ShaderCanvas& c) {
|
||||
if (!c.initialized) return;
|
||||
if (c.program) { delete_program(c.program); c.program = 0; }
|
||||
quad_destroy(c.quad);
|
||||
fb_destroy(c.fb);
|
||||
c.initialized = false;
|
||||
}
|
||||
|
||||
} // namespace fn::gfx
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include "gfx/gl_framebuffer.h"
|
||||
#include "gfx/gl_shader.h"
|
||||
#include "gfx/fullscreen_quad.h"
|
||||
|
||||
namespace fn::gfx {
|
||||
|
||||
struct ShaderCanvas {
|
||||
Framebuffer fb;
|
||||
Quad quad;
|
||||
unsigned int program = 0;
|
||||
bool initialized = false;
|
||||
};
|
||||
|
||||
// Inicializa recursos GL (idempotente).
|
||||
void canvas_init(ShaderCanvas& c);
|
||||
|
||||
// Sustituye el programa activo (borra el anterior). Acepta program=0 para pantalla en negro.
|
||||
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);
|
||||
|
||||
void canvas_destroy(ShaderCanvas& c);
|
||||
|
||||
} // namespace fn::gfx
|
||||
@@ -0,0 +1,61 @@
|
||||
---
|
||||
name: shader_canvas
|
||||
kind: component
|
||||
lang: cpp
|
||||
domain: gfx
|
||||
version: "1.0.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."
|
||||
tags: [opengl, shader, canvas, imgui, fbo, gfx, component]
|
||||
uses_functions:
|
||||
- gl_shader_cpp_gfx
|
||||
- gl_framebuffer_cpp_gfx
|
||||
- fullscreen_quad_cpp_gfx
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
imports: [imgui, GL/gl.h, GL/glext.h]
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "cpp/functions/gfx/shader_canvas.cpp"
|
||||
framework: imgui
|
||||
params:
|
||||
- name: c
|
||||
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."
|
||||
output: "Dibuja ImGui::Image con la textura del FBO renderizado. El panel ImGui debe estar abierto (entre Begin/End). Ocupa GetContentRegionAvail()."
|
||||
---
|
||||
|
||||
# shader_canvas
|
||||
|
||||
Componente que encapsula el ciclo render-to-FBO + ImGui::Image. Llama a `canvas_render()` dentro de un `ImGui::Begin/End` activo.
|
||||
|
||||
## Ciclo de vida
|
||||
|
||||
```cpp
|
||||
fn::gfx::ShaderCanvas canvas{};
|
||||
|
||||
// En el render loop:
|
||||
if (!canvas.initialized) fn::gfx::canvas_init(canvas);
|
||||
|
||||
// Cargar un shader compilado:
|
||||
auto r = fn::gfx::compile_fragment(src);
|
||||
if (r.ok) fn::gfx::canvas_set_program(canvas, r.program);
|
||||
|
||||
// Dentro de ImGui::Begin/End:
|
||||
fn::gfx::canvas_render(canvas, (float)ImGui::GetTime());
|
||||
|
||||
// Al destruir:
|
||||
fn::gfx::canvas_destroy(canvas);
|
||||
```
|
||||
|
||||
## Notas
|
||||
|
||||
- `canvas_set_program` borra el programa anterior automáticamente.
|
||||
- `canvas_set_program(c, 0)` deja la pantalla en negro (glClear sin draw call).
|
||||
- El flip de coordenadas UV (`ImVec2(0,1)` / `ImVec2(1,0)`) corrige el origen OpenGL bottom-left vs ImGui top-left.
|
||||
- Guarda y restaura `GL_FRAMEBUFFER_BINDING` y `GL_VIEWPORT` para compatibilidad con el render loop de ImGui.
|
||||
Reference in New Issue
Block a user