From 07e29f2030119ca340bef7e27e9bd6dab5e7a8a5 Mon Sep 17 00:00:00 2001 From: WerWolv Date: Mon, 20 Jan 2025 21:24:25 +0100 Subject: [PATCH] feat: Added support for OpenGL post processing shaders --- lib/libimhex/include/hex/helpers/opengl.hpp | 2 + lib/libimhex/source/helpers/opengl.cpp | 3 + main/gui/include/window.hpp | 7 + main/gui/source/window/window.cpp | 138 ++++++++++++++++++-- 4 files changed, 137 insertions(+), 13 deletions(-) diff --git a/lib/libimhex/include/hex/helpers/opengl.hpp b/lib/libimhex/include/hex/helpers/opengl.hpp index b52a46454..6efbefb0d 100644 --- a/lib/libimhex/include/hex/helpers/opengl.hpp +++ b/lib/libimhex/include/hex/helpers/opengl.hpp @@ -807,6 +807,8 @@ namespace hex::gl { void bind() const; void unbind() const; + bool isValid() const { return m_program != 0; } + void setUniform(std::string_view name, const int &value); void setUniform(std::string_view name, const float &value); diff --git a/lib/libimhex/source/helpers/opengl.cpp b/lib/libimhex/source/helpers/opengl.cpp index baecce7c0..c8756e3d2 100644 --- a/lib/libimhex/source/helpers/opengl.cpp +++ b/lib/libimhex/source/helpers/opengl.cpp @@ -81,6 +81,9 @@ namespace hex::gl { std::vector log(512); glGetShaderInfoLog(m_program, log.size(), nullptr, log.data()); log::error("Failed to link shader: {}", log.data()); + + glDeleteProgram(m_program); + m_program = 0; } } diff --git a/main/gui/include/window.hpp b/main/gui/include/window.hpp index f98e243ab..0e11b535e 100644 --- a/main/gui/include/window.hpp +++ b/main/gui/include/window.hpp @@ -9,6 +9,7 @@ #include #include +#include struct GLFWwindow; struct ImGuiSettingsHandler; @@ -47,6 +48,10 @@ namespace hex { void exitImGui(); void registerEventHandlers(); + void loadPostProcessingShader(); + + void drawImGui(); + void drawWithShader(); GLFWwindow *m_window = nullptr; @@ -72,6 +77,8 @@ namespace hex { std::atomic m_sleepFlag; std::condition_variable m_sleepCondVar; std::mutex m_sleepMutex; + + gl::Shader m_postProcessingShader; }; } \ No newline at end of file diff --git a/main/gui/source/window/window.cpp b/main/gui/source/window/window.cpp index 0045b3b3a..7873152af 100644 --- a/main/gui/source/window/window.cpp +++ b/main/gui/source/window/window.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -43,6 +44,7 @@ #include #include #include +#include namespace hex { @@ -75,6 +77,7 @@ namespace hex { this->initImGui(); this->setupNativeWindow(); this->registerEventHandlers(); + this->loadPostProcessingShader(); ContentRegistry::Settings::impl::store(); ContentRegistry::Settings::impl::load(); @@ -151,6 +154,36 @@ namespace hex { }); } + void Window::loadPostProcessingShader() { + + for (const auto &folder : paths::Resources.all()) { + auto vertexShaderPath = folder / "shader.vert"; + auto fragmentShaderPath = folder / "shader.frag"; + + if (!wolv::io::fs::exists(vertexShaderPath)) + continue; + if (!wolv::io::fs::exists(fragmentShaderPath)) + continue; + + auto vertexShaderFile = wolv::io::File(vertexShaderPath, wolv::io::File::Mode::Read); + if (!vertexShaderFile.isValid()) + continue; + + auto fragmentShaderFile = wolv::io::File(fragmentShaderPath, wolv::io::File::Mode::Read); + if (!fragmentShaderFile.isValid()) + continue; + + const auto vertexShaderSource = vertexShaderFile.readString(); + const auto fragmentShaderSource = fragmentShaderFile.readString(); + m_postProcessingShader = gl::Shader(vertexShaderSource, fragmentShaderSource); + if (!m_postProcessingShader.isValid()) + continue; + + break; + } + } + + void handleException() { try { throw; @@ -758,20 +791,12 @@ namespace hex { glfwMakeContextCurrent(backupContext); if (shouldRender) { - auto* drawData = ImGui::GetDrawData(); - - // Avoid accidentally clearing the viewport when the application is minimized, - // otherwise the OS will display an empty frame during deminimization on macOS - if (drawData->DisplaySize.x != 0 && drawData->DisplaySize.y != 0) { - int displayWidth, displayHeight; - glfwGetFramebufferSize(m_window, &displayWidth, &displayHeight); - glViewport(0, 0, displayWidth, displayHeight); - glClearColor(0.00F, 0.00F, 0.00F, 0.00F); - glClear(GL_COLOR_BUFFER_BIT); - ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + if (m_postProcessingShader.isValid()) + drawWithShader(); + else + drawImGui(); - glfwSwapBuffers(m_window); - } + glfwSwapBuffers(m_window); } // Process layout load requests @@ -782,6 +807,92 @@ namespace hex { ImGui::GetIO().FontGlobalScale = 1.0F / ImHexApi::System::getBackingScaleFactor(); } + void Window::drawImGui() { + auto* drawData = ImGui::GetDrawData(); + + // Avoid accidentally clearing the viewport when the application is minimized, + // otherwise the OS will display an empty frame during deminimization on macOS + if (drawData->DisplaySize.x != 0 && drawData->DisplaySize.y != 0) { + int displayWidth, displayHeight; + glfwGetFramebufferSize(m_window, &displayWidth, &displayHeight); + glViewport(0, 0, displayWidth, displayHeight); + glClearColor(0.00F, 0.00F, 0.00F, 0.00F); + glClear(GL_COLOR_BUFFER_BIT); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + } + } + + void Window::drawWithShader() { + int displayWidth, displayHeight; + glfwGetFramebufferSize(m_window, &displayWidth, &displayHeight); + + GLuint fbo, texture; + glGenFramebuffers(1, &fbo); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + + // Create a texture to render into + glGenTextures(1, &texture); + glBindTexture(GL_TEXTURE_2D, texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, displayWidth, displayHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + // Attach the texture to the framebuffer + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); + + // Check if framebuffer is complete + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + log::error("Framebuffer is not complete!"); + } + + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + + drawImGui(); + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + GLuint quadVAO, quadVBO; + float quadVertices[] = { + // positions // texCoords + -1.0f, 1.0f, 0.0f, 1.0f, + -1.0f, -1.0f, 0.0f, 0.0f, + 1.0f, -1.0f, 1.0f, 0.0f, + + -1.0f, 1.0f, 0.0f, 1.0f, + 1.0f, -1.0f, 1.0f, 0.0f, + 1.0f, 1.0f, 1.0f, 1.0f + }; + + glGenVertexArrays(1, &quadVAO); + glGenBuffers(1, &quadVBO); + glBindVertexArray(quadVAO); + glBindBuffer(GL_ARRAY_BUFFER, quadVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), quadVertices, GL_STATIC_DRAW); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0); + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float))); + glBindVertexArray(0); + + m_postProcessingShader.bind(); + + glBindVertexArray(quadVAO); + glBindTexture(GL_TEXTURE_2D, texture); + glClearColor(0.00F, 0.00F, 0.00F, 0.00F); + glClear(GL_COLOR_BUFFER_BIT); + glDrawArrays(GL_TRIANGLES, 0, 6); + + m_postProcessingShader.unbind(); + + glDeleteVertexArrays(1, &quadVAO); + glDeleteBuffers(1, &quadVBO); + glDeleteTextures(1, &texture); + glDeleteFramebuffers(1, &fbo); + } + void Window::initGLFW() { auto initialWindowProperties = ImHexApi::System::getInitialWindowProperties(); glfwSetErrorCallback([](int error, const char *desc) { @@ -813,6 +924,7 @@ namespace hex { glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API); + glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_TRUE); if (initialWindowProperties.has_value()) { glfwWindowHint(GLFW_MAXIMIZED, initialWindowProperties->maximized);