From 1b57d52179994c1f298d69b0b94da5395b30ea00 Mon Sep 17 00:00:00 2001 From: Vaxry Date: Wed, 21 Feb 2024 22:19:01 +0000 Subject: [PATCH] background: add blurring --- src/config/ConfigManager.cpp | 14 ++ src/renderer/Framebuffer.cpp | 112 +++++++++++++ src/renderer/Framebuffer.hpp | 24 +++ src/renderer/Renderer.cpp | 214 ++++++++++++++++++++++++- src/renderer/Renderer.hpp | 13 +- src/renderer/Shaders.hpp | 233 +++++++++++++++++++++++++++- src/renderer/widgets/Background.cpp | 33 +++- src/renderer/widgets/Background.hpp | 15 +- 8 files changed, 645 insertions(+), 13 deletions(-) create mode 100644 src/renderer/Framebuffer.cpp create mode 100644 src/renderer/Framebuffer.hpp diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index ea222dd..3927562 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -27,6 +27,13 @@ void CConfigManager::init() { m_config.addSpecialConfigValue("background", "monitor", Hyprlang::STRING{""}); m_config.addSpecialConfigValue("background", "path", Hyprlang::STRING{""}); m_config.addSpecialConfigValue("background", "color", Hyprlang::INT{0xFF111111}); + m_config.addSpecialConfigValue("background", "blur_size", Hyprlang::INT{8}); + m_config.addSpecialConfigValue("background", "blur_passes", Hyprlang::INT{0}); + m_config.addSpecialConfigValue("background", "noise", Hyprlang::FLOAT{0.0117}); + m_config.addSpecialConfigValue("background", "contrast", Hyprlang::FLOAT{0.8917}); + m_config.addSpecialConfigValue("background", "brightness", Hyprlang::FLOAT{0.8172}); + m_config.addSpecialConfigValue("background", "vibrancy", Hyprlang::FLOAT{0.1686}); + m_config.addSpecialConfigValue("background", "vibrancy_darkness", Hyprlang::FLOAT{0.05}); m_config.addSpecialCategory("input-field", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true}); m_config.addSpecialConfigValue("input-field", "monitor", Hyprlang::STRING{""}); @@ -83,6 +90,13 @@ std::vector CConfigManager::getWidgetConfigs() { { {"path", m_config.getSpecialConfigValue("background", "path", k.c_str())}, {"color", m_config.getSpecialConfigValue("background", "color", k.c_str())}, + {"blur_size", m_config.getSpecialConfigValue("background", "blur_size", k.c_str())}, + {"blur_passes", m_config.getSpecialConfigValue("background", "blur_passes", k.c_str())}, + {"noise", m_config.getSpecialConfigValue("background", "noise", k.c_str())}, + {"contrast", m_config.getSpecialConfigValue("background", "contrast", k.c_str())}, + {"vibrancy", m_config.getSpecialConfigValue("background", "vibrancy", k.c_str())}, + {"brightness", m_config.getSpecialConfigValue("background", "brightness", k.c_str())}, + {"vibrancy_darkness", m_config.getSpecialConfigValue("background", "vibrancy_darkness", k.c_str())}, } }); // clang-format on diff --git a/src/renderer/Framebuffer.cpp b/src/renderer/Framebuffer.cpp new file mode 100644 index 0000000..d57fec5 --- /dev/null +++ b/src/renderer/Framebuffer.cpp @@ -0,0 +1,112 @@ +#include "Framebuffer.hpp" +#include "../helpers/Log.hpp" +#include + +static uint32_t drmFormatToGL(uint32_t drm) { + switch (drm) { + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_XBGR8888: return GL_RGBA; // doesn't matter, opengl is gucci in this case. + case DRM_FORMAT_XRGB2101010: + case DRM_FORMAT_XBGR2101010: return GL_RGB10_A2; + default: return GL_RGBA; + } + return GL_RGBA; +} + +static uint32_t glFormatToType(uint32_t gl) { + return gl != GL_RGBA ? GL_UNSIGNED_INT_2_10_10_10_REV : GL_UNSIGNED_BYTE; +} + +bool CFramebuffer::alloc(int w, int h, uint32_t drmFormat) { + bool firstAlloc = false; + + uint32_t glFormat = drmFormatToGL(drmFormat); + uint32_t glType = glFormatToType(glFormat); + + if (m_iFb == (uint32_t)-1) { + firstAlloc = true; + glGenFramebuffers(1, &m_iFb); + } + + if (m_cTex.m_iTexID == 0) { + firstAlloc = true; + glGenTextures(1, &m_cTex.m_iTexID); + glBindTexture(GL_TEXTURE_2D, m_cTex.m_iTexID); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + } + + if (firstAlloc || m_vSize != Vector2D(w, h)) { + glBindTexture(GL_TEXTURE_2D, m_cTex.m_iTexID); + glTexImage2D(GL_TEXTURE_2D, 0, glFormat, w, h, 0, GL_RGBA, glType, 0); + + glBindFramebuffer(GL_FRAMEBUFFER, m_iFb); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_cTex.m_iTexID, 0); + + if (m_pStencilTex) { + glBindTexture(GL_TEXTURE_2D, m_pStencilTex->m_iTexID); + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, w, h, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, 0); + + glBindFramebuffer(GL_FRAMEBUFFER, m_iFb); + + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, m_pStencilTex->m_iTexID, 0); + } + + auto status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (status != GL_FRAMEBUFFER_COMPLETE) { + Debug::log(ERR, "Framebuffer incomplete, couldn't create! (FB status: {})", status); + abort(); + } + + Debug::log(LOG, "Framebuffer created, status {}", status); + } + + glBindTexture(GL_TEXTURE_2D, 0); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + m_vSize = Vector2D(w, h); + + return true; +} + +void CFramebuffer::addStencil() { + glBindTexture(GL_TEXTURE_2D, m_pStencilTex->m_iTexID); + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, m_vSize.x, m_vSize.y, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, 0); + + glBindFramebuffer(GL_FRAMEBUFFER, m_iFb); + + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, m_pStencilTex->m_iTexID, 0); + + auto status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + RASSERT((status == GL_FRAMEBUFFER_COMPLETE), "Failed adding a stencil to fbo!", status); + + glBindTexture(GL_TEXTURE_2D, 0); + glBindFramebuffer(GL_FRAMEBUFFER, 0); +} + +void CFramebuffer::bind() const { + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_iFb); + glViewport(0, 0, m_vSize.x, m_vSize.y); +} + +void CFramebuffer::release() { + if (m_iFb != (uint32_t)-1 && m_iFb) + glDeleteFramebuffers(1, &m_iFb); + + if (m_cTex.m_iTexID) + glDeleteTextures(1, &m_cTex.m_iTexID); + + m_cTex.m_iTexID = 0; + m_iFb = -1; + m_vSize = Vector2D(); +} + +CFramebuffer::~CFramebuffer() { + release(); +} + +bool CFramebuffer::isAllocated() { + return m_iFb != (GLuint)-1; +} \ No newline at end of file diff --git a/src/renderer/Framebuffer.hpp b/src/renderer/Framebuffer.hpp new file mode 100644 index 0000000..c9e28f0 --- /dev/null +++ b/src/renderer/Framebuffer.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include "../helpers/Vector2D.hpp" +#include +#include "Texture.hpp" + +class CFramebuffer { + public: + ~CFramebuffer(); + + bool alloc(int w, int h, uint32_t format = GL_RGBA); + void addStencil(); + void bind() const; + void release(); + void reset(); + bool isAllocated(); + + Vector2D m_vSize; + + CTexture m_cTex; + GLuint m_iFb = -1; + + CTexture* m_pStencilTex = nullptr; +}; \ No newline at end of file diff --git a/src/renderer/Renderer.cpp b/src/renderer/Renderer.cpp index a317169..b26b195 100644 --- a/src/renderer/Renderer.cpp +++ b/src/renderer/Renderer.cpp @@ -106,6 +106,47 @@ CRenderer::CRenderer() { texShader.tint = glGetUniformLocation(prog, "tint"); texShader.useAlphaMatte = glGetUniformLocation(prog, "useAlphaMatte"); + prog = createProgram(TEXVERTSRC, FRAGBLUR1); + blurShader1.program = prog; + blurShader1.tex = glGetUniformLocation(prog, "tex"); + blurShader1.alpha = glGetUniformLocation(prog, "alpha"); + blurShader1.proj = glGetUniformLocation(prog, "proj"); + blurShader1.posAttrib = glGetAttribLocation(prog, "pos"); + blurShader1.texAttrib = glGetAttribLocation(prog, "texcoord"); + blurShader1.radius = glGetUniformLocation(prog, "radius"); + blurShader1.halfpixel = glGetUniformLocation(prog, "halfpixel"); + blurShader1.passes = glGetUniformLocation(prog, "passes"); + blurShader1.vibrancy = glGetUniformLocation(prog, "vibrancy"); + blurShader1.vibrancy_darkness = glGetUniformLocation(prog, "vibrancy_darkness"); + + prog = createProgram(TEXVERTSRC, FRAGBLUR2); + blurShader2.program = prog; + blurShader2.tex = glGetUniformLocation(prog, "tex"); + blurShader2.alpha = glGetUniformLocation(prog, "alpha"); + blurShader2.proj = glGetUniformLocation(prog, "proj"); + blurShader2.posAttrib = glGetAttribLocation(prog, "pos"); + blurShader2.texAttrib = glGetAttribLocation(prog, "texcoord"); + blurShader2.radius = glGetUniformLocation(prog, "radius"); + blurShader2.halfpixel = glGetUniformLocation(prog, "halfpixel"); + + prog = createProgram(TEXVERTSRC, FRAGBLURPREPARE); + blurPrepareShader.program = prog; + blurPrepareShader.tex = glGetUniformLocation(prog, "tex"); + blurPrepareShader.proj = glGetUniformLocation(prog, "proj"); + blurPrepareShader.posAttrib = glGetAttribLocation(prog, "pos"); + blurPrepareShader.texAttrib = glGetAttribLocation(prog, "texcoord"); + blurPrepareShader.contrast = glGetUniformLocation(prog, "contrast"); + blurPrepareShader.brightness = glGetUniformLocation(prog, "brightness"); + + prog = createProgram(TEXVERTSRC, FRAGBLURFINISH); + blurFinishShader.program = prog; + blurFinishShader.tex = glGetUniformLocation(prog, "tex"); + blurFinishShader.proj = glGetUniformLocation(prog, "proj"); + blurFinishShader.posAttrib = glGetAttribLocation(prog, "pos"); + blurFinishShader.texAttrib = glGetAttribLocation(prog, "texcoord"); + blurFinishShader.brightness = glGetUniformLocation(prog, "brightness"); + blurFinishShader.noise = glGetUniformLocation(prog, "noise"); + wlr_matrix_identity(projMatrix.data()); asyncResourceGatherer = std::make_unique(); @@ -199,9 +240,9 @@ void CRenderer::renderRect(const CBox& box, const CColor& col, int rounding) { glDisableVertexAttribArray(rectShader.posAttrib); } -void CRenderer::renderTexture(const CBox& box, const CTexture& tex, float a, int rounding) { +void CRenderer::renderTexture(const CBox& box, const CTexture& tex, float a, int rounding, bool noTransform) { float matrix[9]; - wlr_matrix_project_box(matrix, &box, WL_OUTPUT_TRANSFORM_FLIPPED_180 /* ugh coordinate spaces */, 0, + wlr_matrix_project_box(matrix, &box, noTransform ? WL_OUTPUT_TRANSFORM_NORMAL : WL_OUTPUT_TRANSFORM_FLIPPED_180 /* ugh coordinate spaces */, 0, projMatrix.data()); // TODO: write own, don't use WLR here float glMatrix[9]; @@ -262,7 +303,7 @@ std::vector>* CRenderer::getOrCreateWidgetsFor(const CS else if (!PATH.empty()) resourceID = "background:" + PATH; - widgets[surf].emplace_back(std::make_unique(surf->size, resourceID, std::any_cast(c.values.at("color")))); + widgets[surf].emplace_back(std::make_unique(surf->size, resourceID, c.values)); } else if (c.type == "input-field") { widgets[surf].emplace_back(std::make_unique(surf->size, c.values)); } else if (c.type == "label") { @@ -273,3 +314,170 @@ std::vector>* CRenderer::getOrCreateWidgetsFor(const CS return &widgets[surf]; } + +void CRenderer::blurTexture(const CFramebuffer& outfb, const CTexture& tex, SBlurParams params) { + glDisable(GL_BLEND); + glDisable(GL_STENCIL_TEST); + + float matrix[9]; + CBox box{0, 0, tex.m_vSize.x, tex.m_vSize.y}; + wlr_matrix_project_box(matrix, &box, WL_OUTPUT_TRANSFORM_NORMAL, 0, + projMatrix.data()); // TODO: write own, don't use WLR here + + float glMatrix[9]; + wlr_matrix_multiply(glMatrix, projection.data(), matrix); + + CFramebuffer mirrors[2]; + mirrors[0].alloc(tex.m_vSize.x, tex.m_vSize.y); + mirrors[1].alloc(tex.m_vSize.x, tex.m_vSize.y); + + CFramebuffer* currentRenderToFB = &mirrors[0]; + + // Begin with base color adjustments - global brightness and contrast + // TODO: make this a part of the first pass maybe to save on a drawcall? + { + mirrors[1].bind(); + + glActiveTexture(GL_TEXTURE0); + + glBindTexture(tex.m_iTarget, tex.m_iTexID); + + glTexParameteri(tex.m_iTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + + glUseProgram(blurPrepareShader.program); + +#ifndef GLES2 + glUniformMatrix3fv(blurPrepareShader.proj, 1, GL_TRUE, glMatrix); +#else + wlr_matrix_transpose(glMatrix, glMatrix); + glUniformMatrix3fv(blurPrepareShader.proj, 1, GL_FALSE, glMatrix); +#endif + glUniform1f(blurPrepareShader.contrast, params.contrast); + glUniform1f(blurPrepareShader.brightness, params.brightness); + glUniform1i(blurPrepareShader.tex, 0); + + glVertexAttribPointer(blurPrepareShader.posAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts); + glVertexAttribPointer(blurPrepareShader.texAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts); + + glEnableVertexAttribArray(blurPrepareShader.posAttrib); + glEnableVertexAttribArray(blurPrepareShader.texAttrib); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + glDisableVertexAttribArray(blurPrepareShader.posAttrib); + glDisableVertexAttribArray(blurPrepareShader.texAttrib); + + currentRenderToFB = &mirrors[1]; + } + + // declare the draw func + auto drawPass = [&](CShader* pShader) { + if (currentRenderToFB == &mirrors[0]) + mirrors[1].bind(); + else + mirrors[0].bind(); + + glActiveTexture(GL_TEXTURE0); + + glBindTexture(currentRenderToFB->m_cTex.m_iTarget, currentRenderToFB->m_cTex.m_iTexID); + + glTexParameteri(currentRenderToFB->m_cTex.m_iTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + + glUseProgram(pShader->program); + + // prep two shaders +#ifndef GLES2 + glUniformMatrix3fv(pShader->proj, 1, GL_TRUE, glMatrix); +#else + wlr_matrix_transpose(glMatrix, glMatrix); + glUniformMatrix3fv(pShader->proj, 1, GL_FALSE, glMatrix); +#endif + glUniform1f(pShader->radius, params.size); + if (pShader == &blurShader1) { + glUniform2f(blurShader1.halfpixel, 0.5f / (tex.m_vSize.x / 2.f), 0.5f / (tex.m_vSize.y / 2.f)); + glUniform1i(blurShader1.passes, params.passes); + glUniform1f(blurShader1.vibrancy, params.vibrancy); + glUniform1f(blurShader1.vibrancy_darkness, params.vibrancy_darkness); + } else + glUniform2f(blurShader2.halfpixel, 0.5f / (tex.m_vSize.x * 2.f), 0.5f / (tex.m_vSize.y * 2.f)); + glUniform1i(pShader->tex, 0); + + glVertexAttribPointer(pShader->posAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts); + glVertexAttribPointer(pShader->texAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts); + + glEnableVertexAttribArray(pShader->posAttrib); + glEnableVertexAttribArray(pShader->texAttrib); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + glDisableVertexAttribArray(pShader->posAttrib); + glDisableVertexAttribArray(pShader->texAttrib); + + if (currentRenderToFB != &mirrors[0]) + currentRenderToFB = &mirrors[0]; + else + currentRenderToFB = &mirrors[1]; + }; + + // draw the things. + // first draw is swap -> mirr + mirrors[0].bind(); + glBindTexture(mirrors[1].m_cTex.m_iTarget, mirrors[1].m_cTex.m_iTexID); + + for (int i = 1; i <= params.passes; ++i) { + drawPass(&blurShader1); // down + } + + for (int i = params.passes - 1; i >= 0; --i) { + drawPass(&blurShader2); // up + } + + // finalize the image + { + if (currentRenderToFB == &mirrors[0]) + mirrors[1].bind(); + else + mirrors[0].bind(); + + glActiveTexture(GL_TEXTURE0); + + glBindTexture(currentRenderToFB->m_cTex.m_iTarget, currentRenderToFB->m_cTex.m_iTexID); + + glTexParameteri(currentRenderToFB->m_cTex.m_iTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + + glUseProgram(blurFinishShader.program); + +#ifndef GLES2 + glUniformMatrix3fv(blurFinishShader.proj, 1, GL_TRUE, glMatrix); +#else + wlr_matrix_transpose(glMatrix, glMatrix); + glUniformMatrix3fv(blurFinishShader.proj, 1, GL_FALSE, glMatrix); +#endif + glUniform1f(blurFinishShader.noise, params.noise); + glUniform1f(blurFinishShader.brightness, params.brightness); + + glUniform1i(blurFinishShader.tex, 0); + + glVertexAttribPointer(blurFinishShader.posAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts); + glVertexAttribPointer(blurFinishShader.texAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts); + + glEnableVertexAttribArray(blurFinishShader.posAttrib); + glEnableVertexAttribArray(blurFinishShader.texAttrib); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + glDisableVertexAttribArray(blurFinishShader.posAttrib); + glDisableVertexAttribArray(blurFinishShader.texAttrib); + + if (currentRenderToFB != &mirrors[0]) + currentRenderToFB = &mirrors[0]; + else + currentRenderToFB = &mirrors[1]; + } + + // finish + outfb.bind(); + renderTexture(box, currentRenderToFB->m_cTex, 1.0, 0, true); + + glEnable(GL_BLEND); +} \ No newline at end of file diff --git a/src/renderer/Renderer.hpp b/src/renderer/Renderer.hpp index e07536b..43441e1 100644 --- a/src/renderer/Renderer.hpp +++ b/src/renderer/Renderer.hpp @@ -9,6 +9,7 @@ #include "../helpers/Color.hpp" #include "AsyncResourceGatherer.hpp" #include "widgets/IWidget.hpp" +#include "Framebuffer.hpp" typedef std::unordered_map>> widgetMap_t; @@ -20,10 +21,16 @@ class CRenderer { bool needsFrame = false; }; + struct SBlurParams { + int size = 0, passes = 0; + float noise = 0, contrast = 0, brightness = 0, vibrancy = 0, vibrancy_darkness = 0; + }; + SRenderFeedback renderLock(const CSessionLockSurface& surface); void renderRect(const CBox& box, const CColor& col, int rounding = 0); - void renderTexture(const CBox& box, const CTexture& tex, float a = 1.0, int rounding = 0); + void renderTexture(const CBox& box, const CTexture& tex, float a = 1.0, int rounding = 0, bool noTransform = false); + void blurTexture(const CFramebuffer& outfb, const CTexture& tex, SBlurParams params); std::unique_ptr asyncResourceGatherer; std::chrono::system_clock::time_point gatheredAt; @@ -35,6 +42,10 @@ class CRenderer { CShader rectShader; CShader texShader; + CShader blurShader1; + CShader blurShader2; + CShader blurPrepareShader; + CShader blurFinishShader; std::array projMatrix; std::array projection; diff --git a/src/renderer/Shaders.hpp b/src/renderer/Shaders.hpp index 0eca6a9..de41f3a 100644 --- a/src/renderer/Shaders.hpp +++ b/src/renderer/Shaders.hpp @@ -119,4 +119,235 @@ void main() { } gl_FragColor = pixColor * alpha; -})#"; \ No newline at end of file +})#"; + +inline const std::string FRAGBLUR1 = R"#( +#version 100 +precision highp float; +varying highp vec2 v_texcoord; // is in 0-1 +uniform sampler2D tex; + +uniform float radius; +uniform vec2 halfpixel; +uniform int passes; +uniform float vibrancy; +uniform float vibrancy_darkness; + +// see http://alienryderflex.com/hsp.html +const float Pr = 0.299; +const float Pg = 0.587; +const float Pb = 0.114; + +// Y is "v" ( brightness ). X is "s" ( saturation ) +// see https://www.desmos.com/3d/a88652b9a4 +// Determines if high brightness or high saturation is more important +const float a = 0.93; +const float b = 0.11; +const float c = 0.66; // Determines the smoothness of the transition of unboosted to boosted colors +// + +// http://www.flong.com/archive/texts/code/shapers_circ/ +float doubleCircleSigmoid(float x, float a) { + a = clamp(a, 0.0, 1.0); + + float y = .0; + if (x <= a) { + y = a - sqrt(a * a - x * x); + } else { + y = a + sqrt(pow(1. - a, 2.) - pow(x - 1., 2.)); + } + return y; +} + +vec3 rgb2hsl(vec3 col) { + float red = col.r; + float green = col.g; + float blue = col.b; + + float minc = min(col.r, min(col.g, col.b)); + float maxc = max(col.r, max(col.g, col.b)); + float delta = maxc - minc; + + float lum = (minc + maxc) * 0.5; + float sat = 0.0; + float hue = 0.0; + + if (lum > 0.0 && lum < 1.0) { + float mul = (lum < 0.5) ? (lum) : (1.0 - lum); + sat = delta / (mul * 2.0); + } + + if (delta > 0.0) { + vec3 maxcVec = vec3(maxc); + vec3 masks = vec3(equal(maxcVec, col)) * vec3(notEqual(maxcVec, vec3(green, blue, red))); + vec3 adds = vec3(0.0, 2.0, 4.0) + vec3(green - blue, blue - red, red - green) / delta; + + hue += dot(adds, masks); + hue /= 6.0; + + if (hue < 0.0) + hue += 1.0; + } + + return vec3(hue, sat, lum); +} + +vec3 hsl2rgb(vec3 col) { + const float onethird = 1.0 / 3.0; + const float twothird = 2.0 / 3.0; + const float rcpsixth = 6.0; + + float hue = col.x; + float sat = col.y; + float lum = col.z; + + vec3 xt = vec3(0.0); + + if (hue < onethird) { + xt.r = rcpsixth * (onethird - hue); + xt.g = rcpsixth * hue; + xt.b = 0.0; + } else if (hue < twothird) { + xt.r = 0.0; + xt.g = rcpsixth * (twothird - hue); + xt.b = rcpsixth * (hue - onethird); + } else + xt = vec3(rcpsixth * (hue - twothird), 0.0, rcpsixth * (1.0 - hue)); + + xt = min(xt, 1.0); + + float sat2 = 2.0 * sat; + float satinv = 1.0 - sat; + float luminv = 1.0 - lum; + float lum2m1 = (2.0 * lum) - 1.0; + vec3 ct = (sat2 * xt) + satinv; + + vec3 rgb; + if (lum >= 0.5) + rgb = (luminv * ct) + lum2m1; + else + rgb = lum * ct; + + return rgb; +} + +void main() { + vec2 uv = v_texcoord * 2.0; + + vec4 sum = texture2D(tex, uv) * 4.0; + sum += texture2D(tex, uv - halfpixel.xy * radius); + sum += texture2D(tex, uv + halfpixel.xy * radius); + sum += texture2D(tex, uv + vec2(halfpixel.x, -halfpixel.y) * radius); + sum += texture2D(tex, uv - vec2(halfpixel.x, -halfpixel.y) * radius); + + vec4 color = sum / 8.0; + + if (vibrancy == 0.0) { + gl_FragColor = color; + } else { + // Invert it so that it correctly maps to the config setting + float vibrancy_darkness1 = 1.0 - vibrancy_darkness; + + // Decrease the RGB components based on their perceived brightness, to prevent visually dark colors from overblowing the rest. + vec3 hsl = rgb2hsl(color.rgb); + // Calculate perceived brightness, as not boost visually dark colors like deep blue as much as equally saturated yellow + float perceivedBrightness = doubleCircleSigmoid(sqrt(color.r * color.r * Pr + color.g * color.g * Pg + color.b * color.b * Pb), 0.8 * vibrancy_darkness1); + + float b1 = b * vibrancy_darkness1; + float boostBase = hsl[1] > 0.0 ? smoothstep(b1 - c * 0.5, b1 + c * 0.5, 1.0 - (pow(1.0 - hsl[1] * cos(a), 2.0) + pow(1.0 - perceivedBrightness * sin(a), 2.0))) : 0.0; + + float saturation = clamp(hsl[1] + (boostBase * vibrancy) / float(passes), 0.0, 1.0); + + vec3 newColor = hsl2rgb(vec3(hsl[0], saturation, hsl[2])); + + gl_FragColor = vec4(newColor, color[3]); + } +} +)#"; + +inline const std::string FRAGBLUR2 = R"#( +#version 100 +precision highp float; +varying highp vec2 v_texcoord; // is in 0-1 +uniform sampler2D tex; + +uniform float radius; +uniform vec2 halfpixel; + +void main() { + vec2 uv = v_texcoord / 2.0; + + vec4 sum = texture2D(tex, uv + vec2(-halfpixel.x * 2.0, 0.0) * radius); + + sum += texture2D(tex, uv + vec2(-halfpixel.x, halfpixel.y) * radius) * 2.0; + sum += texture2D(tex, uv + vec2(0.0, halfpixel.y * 2.0) * radius); + sum += texture2D(tex, uv + vec2(halfpixel.x, halfpixel.y) * radius) * 2.0; + sum += texture2D(tex, uv + vec2(halfpixel.x * 2.0, 0.0) * radius); + sum += texture2D(tex, uv + vec2(halfpixel.x, -halfpixel.y) * radius) * 2.0; + sum += texture2D(tex, uv + vec2(0.0, -halfpixel.y * 2.0) * radius); + sum += texture2D(tex, uv + vec2(-halfpixel.x, -halfpixel.y) * radius) * 2.0; + + gl_FragColor = sum / 12.0; +} +)#"; + +inline const std::string FRAGBLURPREPARE = R"#( +precision highp float; +varying vec2 v_texcoord; // is in 0-1 +uniform sampler2D tex; + +uniform float contrast; +uniform float brightness; + +float gain(float x, float k) { + float a = 0.5 * pow(2.0 * ((x < 0.5) ? x : 1.0 - x), k); + return (x < 0.5) ? a : 1.0 - a; +} + +void main() { + vec4 pixColor = texture2D(tex, v_texcoord); + + // contrast + if (contrast != 1.0) { + pixColor.r = gain(pixColor.r, contrast); + pixColor.g = gain(pixColor.g, contrast); + pixColor.b = gain(pixColor.b, contrast); + } + + // brightness + if (brightness > 1.0) { + pixColor.rgb *= brightness; + } + + gl_FragColor = pixColor; +} +)#"; + +inline const std::string FRAGBLURFINISH = R"#( +precision highp float; +varying vec2 v_texcoord; // is in 0-1 +uniform sampler2D tex; + +uniform float noise; +uniform float brightness; + +float hash(vec2 p) { + return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453); +} + +void main() { + vec4 pixColor = texture2D(tex, v_texcoord); + + // noise + float noiseHash = hash(v_texcoord); + float noiseAmount = (mod(noiseHash, 1.0) - 0.5); + pixColor.rgb += noiseAmount * noise; + + // brightness + if (brightness < 1.0) { + pixColor.rgb *= brightness; + } + + gl_FragColor = pixColor; +} +)#"; \ No newline at end of file diff --git a/src/renderer/widgets/Background.cpp b/src/renderer/widgets/Background.cpp index fab24d3..a47ad55 100644 --- a/src/renderer/widgets/Background.cpp +++ b/src/renderer/widgets/Background.cpp @@ -1,8 +1,17 @@ #include "Background.hpp" #include "../Renderer.hpp" -CBackground::CBackground(const Vector2D& viewport_, const std::string& resourceID_, const CColor& color_) : viewport(viewport_), resourceID(resourceID_), color(color_) { - ; +CBackground::CBackground(const Vector2D& viewport_, const std::string& resourceID_, const std::unordered_map& props) : + viewport(viewport_), resourceID(resourceID_) { + + color = std::any_cast(props.at("color")); + blurPasses = std::any_cast(props.at("blur_passes")); + blurSize = std::any_cast(props.at("blur_size")); + vibrancy = std::any_cast(props.at("vibrancy")); + vibrancy_darkness = std::any_cast(props.at("vibrancy_darkness")); + noise = std::any_cast(props.at("noise")); + brightness = std::any_cast(props.at("brightness")); + contrast = std::any_cast(props.at("contrast")); } bool CBackground::draw(const SRenderData& data) { @@ -21,11 +30,21 @@ bool CBackground::draw(const SRenderData& data) { if (!asset) return false; - CBox texbox = {{}, asset->texture.m_vSize}; + if (blurPasses > 0 && !blurredFB.isAllocated()) { + // make it brah + blurredFB.alloc(viewport.x, viewport.y); // TODO 10 bit + blurredFB.bind(); + g_pRenderer->blurTexture(blurredFB, asset->texture, CRenderer::SBlurParams{blurSize, blurPasses, noise, contrast, brightness, vibrancy, vibrancy_darkness}); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + } - Vector2D size = asset->texture.m_vSize; - float scaleX = viewport.x / asset->texture.m_vSize.x; - float scaleY = viewport.y / asset->texture.m_vSize.y; + CTexture* tex = blurredFB.isAllocated() ? &blurredFB.m_cTex : &asset->texture; + + CBox texbox = {{}, asset->texture.m_vSize}; + + Vector2D size = asset->texture.m_vSize; + float scaleX = viewport.x / asset->texture.m_vSize.x; + float scaleY = viewport.y / asset->texture.m_vSize.y; texbox.w *= std::max(scaleX, scaleY); texbox.h *= std::max(scaleX, scaleY); @@ -35,7 +54,7 @@ bool CBackground::draw(const SRenderData& data) { else texbox.x = -(texbox.w - viewport.x) / 2.f; - g_pRenderer->renderTexture(texbox, asset->texture, data.opacity); + g_pRenderer->renderTexture(texbox, *tex, data.opacity); return data.opacity < 1.0; } \ No newline at end of file diff --git a/src/renderer/widgets/Background.hpp b/src/renderer/widgets/Background.hpp index b736aa9..6f83a5f 100644 --- a/src/renderer/widgets/Background.hpp +++ b/src/renderer/widgets/Background.hpp @@ -3,17 +3,30 @@ #include "IWidget.hpp" #include "../../helpers/Vector2D.hpp" #include "../../helpers/Color.hpp" +#include "../Framebuffer.hpp" #include +#include +#include struct SPreloadedAsset; class CBackground : public IWidget { public: - CBackground(const Vector2D& viewport, const std::string& resourceID, const CColor& color); + CBackground(const Vector2D& viewport, const std::string& resourceID, const std::unordered_map& props); virtual bool draw(const SRenderData& data); private: + // if needed + CFramebuffer blurredFB; + + int blurSize = 10; + int blurPasses = 3; + float noise = 0.0117; + float contrast = 0.8916; + float brightness = 0.8172; + float vibrancy = 0.1696; + float vibrancy_darkness = 0.0; Vector2D viewport; std::string resourceID; CColor color;