298 lines
10 KiB
C++
298 lines
10 KiB
C++
#include "rendering/opengl_shader_backend.hpp"
|
|
|
|
#include <iostream>
|
|
#include <vector>
|
|
|
|
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<GLADloadproc>(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<float> black(static_cast<std::size_t>(width) * static_cast<std::size_t>(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<float>(w), static_cast<float>(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<float>(w), static_cast<float>(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<float>(w), static_cast<float>(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<IShaderBackend> {
|
|
return std::make_unique<OpenGLShaderBackend>();
|
|
}
|
|
|
|
} // namespace Rendering
|