3008b56e76
- 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>
120 lines
3.4 KiB
C++
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
|