Files
fn_registry/cpp/functions/gfx/gl_shader.cpp
T
egutierrez 3008b56e76 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>
2026-04-24 20:33:36 +02:00

120 lines
3.4 KiB
C++

#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