#include "rendering/opengl_shader_backend.hpp" #include #include namespace Rendering { namespace { constexpr const char* VERTEX_SHADER_SRC = R"glsl( #version 330 core layout(location = 0) in vec2 aPos; out vec2 vUV; void main() { vUV = aPos * 0.5 + 0.5; gl_Position = vec4(aPos, 0.0, 1.0); } )glsl"; void logInfo(const std::string& msg) { std::cout << "[INFO] " << msg << '\n'; } void logError(const std::string& msg) { std::cerr << "[ERROR] " << msg << '\n'; } auto compileShader(GLenum type, const char* src) -> GLuint { const GLuint s = glCreateShader(type); glShaderSource(s, 1, &src, nullptr); glCompileShader(s); GLint ok = 0; glGetShaderiv(s, GL_COMPILE_STATUS, &ok); if (ok == 0) { GLint len = 0; glGetShaderiv(s, GL_INFO_LOG_LENGTH, &len); std::string log(len > 0 ? len : 1, ' '); glGetShaderInfoLog(s, len, nullptr, log.data()); logError("Shader compile error: " + log); glDeleteShader(s); return 0; } return s; } auto linkProgram(GLuint vs, GLuint fs) -> GLuint { const GLuint p = glCreateProgram(); glAttachShader(p, vs); glAttachShader(p, fs); glLinkProgram(p); GLint ok = 0; glGetProgramiv(p, GL_LINK_STATUS, &ok); if (ok == 0) { GLint len = 0; glGetProgramiv(p, GL_INFO_LOG_LENGTH, &len); std::string log(len > 0 ? len : 1, ' '); glGetProgramInfoLog(p, len, nullptr, log.data()); logError("Program link error: " + log); glDeleteProgram(p); return 0; } return p; } auto detectFeedbackChannel(const ShaderMetadata& metadata) -> int { if (metadata.iChannel0 == "self") { return 0; } if (metadata.iChannel1 == "self") { return 1; } if (metadata.iChannel2 == "self") { return 2; } if (metadata.iChannel3 == "self") { return 3; } return -1; } } // namespace OpenGLShaderBackend::~OpenGLShaderBackend() { cleanup(); } auto OpenGLShaderBackend::init(SDL_Window* window) -> bool { window_ = window; gl_context_ = SDL_GL_CreateContext(window_); if (gl_context_ == nullptr) { logError(std::string("SDL_GL_CreateContext error: ") + SDL_GetError()); return false; } if (gladLoadGLLoader(reinterpret_cast(SDL_GL_GetProcAddress)) == 0) { logError("Failed to initialize GL loader"); SDL_GL_DestroyContext(gl_context_); gl_context_ = nullptr; return false; } constexpr float QUAD_VERTICES[] = { -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, }; glGenVertexArrays(1, &vao_); glGenBuffers(1, &vbo_); glBindVertexArray(vao_); glBindBuffer(GL_ARRAY_BUFFER, vbo_); glBufferData(GL_ARRAY_BUFFER, sizeof(QUAD_VERTICES), QUAD_VERTICES, GL_STATIC_DRAW); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), nullptr); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); return true; } auto OpenGLShaderBackend::loadShader(const ShaderProgramSpec& spec) -> bool { const std::filesystem::path source_path = spec.folder / (spec.base_name + ".gl.glsl"); std::string fragSrc; if (!loadFileToString(source_path, fragSrc)) { logError("Failed to load shader file: " + source_path.string()); return false; } const int feedback = detectFeedbackChannel(spec.metadata); const GLuint vs = compileShader(GL_VERTEX_SHADER, VERTEX_SHADER_SRC); const GLuint fs = compileShader(GL_FRAGMENT_SHADER, fragSrc.c_str()); if (vs == 0 || fs == 0) { if (vs != 0) { glDeleteShader(vs); } if (fs != 0) { glDeleteShader(fs); } logError("Shader compilation failed for: " + source_path.string()); return false; } const GLuint program = linkProgram(vs, fs); glDeleteShader(vs); glDeleteShader(fs); if (program == 0) { logError("Program linking failed for: " + source_path.string()); return false; } if (current_program_ != 0) { glDeleteProgram(current_program_); } current_program_ = program; destroyFeedbackFBO(); feedback_channel_ = feedback; current_shader_uses_feedback_ = (feedback >= 0); if (current_shader_uses_feedback_) { logInfo("Shader uses self-feedback on iChannel" + std::to_string(feedback_channel_)); } logInfo("Shader loaded successfully: " + spec.base_name); return true; } auto OpenGLShaderBackend::createFeedbackFBO(int width, int height) -> bool { destroyFeedbackFBO(); glGenTextures(1, &feedback_texture_); glBindTexture(GL_TEXTURE_2D, feedback_texture_); 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); std::vector black(static_cast(width) * static_cast(height) * 4U, 0.0f); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, width, height, 0, GL_RGBA, GL_FLOAT, black.data()); glBindTexture(GL_TEXTURE_2D, 0); glGenFramebuffers(1, &feedback_fbo_); glBindFramebuffer(GL_FRAMEBUFFER, feedback_fbo_); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, feedback_texture_, 0); const GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); glBindFramebuffer(GL_FRAMEBUFFER, 0); if (status != GL_FRAMEBUFFER_COMPLETE) { logError("Feedback FBO creation failed: " + std::to_string(status)); destroyFeedbackFBO(); return false; } feedback_width_ = width; feedback_height_ = height; logInfo("Created feedback FBO (" + std::to_string(width) + "x" + std::to_string(height) + ")"); return true; } void OpenGLShaderBackend::destroyFeedbackFBO() { if (feedback_fbo_ != 0) { glDeleteFramebuffers(1, &feedback_fbo_); feedback_fbo_ = 0; } if (feedback_texture_ != 0) { glDeleteTextures(1, &feedback_texture_); feedback_texture_ = 0; } feedback_width_ = 0; feedback_height_ = 0; } void OpenGLShaderBackend::render(const ShaderUniforms& uniforms) { if (current_program_ == 0 || window_ == nullptr) { return; } int w = 0; int h = 0; SDL_GetWindowSize(window_, &w, &h); if (current_shader_uses_feedback_) { if (feedback_fbo_ == 0 || feedback_width_ != w || feedback_height_ != h) { createFeedbackFBO(w, h); } } glUseProgram(current_program_); const GLint locRes = glGetUniformLocation(current_program_, "iResolution"); const GLint locTime = glGetUniformLocation(current_program_, "iTime"); if (current_shader_uses_feedback_) { const std::string channel_name = "iChannel" + std::to_string(feedback_channel_); const GLint locChannel = glGetUniformLocation(current_program_, channel_name.c_str()); if (locChannel >= 0) { glActiveTexture(GL_TEXTURE0 + feedback_channel_); glBindTexture(GL_TEXTURE_2D, feedback_texture_); glUniform1i(locChannel, feedback_channel_); } glBindFramebuffer(GL_FRAMEBUFFER, feedback_fbo_); glViewport(0, 0, w, h); glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); if (locRes >= 0) { glUniform2f(locRes, static_cast(w), static_cast(h)); } if (locTime >= 0) { glUniform1f(locTime, uniforms.iTime); } glBindVertexArray(vao_); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glBindVertexArray(0); glBindFramebuffer(GL_FRAMEBUFFER, 0); glViewport(0, 0, w, h); if (locRes >= 0) { glUniform2f(locRes, static_cast(w), static_cast(h)); } if (locTime >= 0) { glUniform1f(locTime, uniforms.iTime); } glBindVertexArray(vao_); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glBindVertexArray(0); glActiveTexture(GL_TEXTURE0 + feedback_channel_); glBindTexture(GL_TEXTURE_2D, 0); } else { glViewport(0, 0, w, h); glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); if (locRes >= 0) { glUniform2f(locRes, static_cast(w), static_cast(h)); } if (locTime >= 0) { glUniform1f(locTime, uniforms.iTime); } glBindVertexArray(vao_); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glBindVertexArray(0); } SDL_GL_SwapWindow(window_); } void OpenGLShaderBackend::setVSync(bool vsync) { const int result = SDL_GL_SetSwapInterval(vsync ? 1 : 0); if (result == 0) { logInfo(vsync ? "VSync enabled" : "VSync disabled"); } else { logError(std::string("Failed to set VSync: ") + SDL_GetError()); } } void OpenGLShaderBackend::cleanup() { if (gl_context_ == nullptr) { return; } if (vbo_ != 0) { glDeleteBuffers(1, &vbo_); vbo_ = 0; } if (vao_ != 0) { glDeleteVertexArrays(1, &vao_); vao_ = 0; } if (current_program_ != 0) { glDeleteProgram(current_program_); current_program_ = 0; } destroyFeedbackFBO(); current_shader_uses_feedback_ = false; feedback_channel_ = -1; SDL_GL_DestroyContext(gl_context_); gl_context_ = nullptr; window_ = nullptr; } auto makeOpenGLBackend() -> std::unique_ptr { return std::make_unique(); } } // namespace Rendering