diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index 52f9526..0c8f9ab 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -191,6 +191,9 @@ void CConfigManager::init() { m_config.addSpecialConfigValue("background", "vibrancy", Hyprlang::FLOAT{0.1686}); m_config.addSpecialConfigValue("background", "vibrancy_darkness", Hyprlang::FLOAT{0.05}); m_config.addSpecialConfigValue("background", "zindex", Hyprlang::INT{-1}); + m_config.addSpecialConfigValue("background", "reload_time", Hyprlang::INT{-1}); + m_config.addSpecialConfigValue("background", "reload_cmd", Hyprlang::STRING{""}); + m_config.addSpecialConfigValue("background", "crossfade_time", Hyprlang::FLOAT{-1.0}); m_config.addSpecialCategory("shape", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true}); m_config.addSpecialConfigValue("shape", "monitor", Hyprlang::STRING{""}); @@ -318,6 +321,9 @@ std::vector CConfigManager::getWidgetConfigs() { {"brightness", m_config.getSpecialConfigValue("background", "brightness", k.c_str())}, {"vibrancy_darkness", m_config.getSpecialConfigValue("background", "vibrancy_darkness", k.c_str())}, {"zindex", m_config.getSpecialConfigValue("background", "zindex", k.c_str())}, + {"reload_time", m_config.getSpecialConfigValue("background", "reload_time", k.c_str())}, + {"reload_cmd", m_config.getSpecialConfigValue("background", "reload_cmd", k.c_str())}, + {"crossfade_time", m_config.getSpecialConfigValue("background", "crossfade_time", k.c_str())}, } }); // clang-format on diff --git a/src/renderer/Renderer.cpp b/src/renderer/Renderer.cpp index cbbfdb8..035982a 100644 --- a/src/renderer/Renderer.cpp +++ b/src/renderer/Renderer.cpp @@ -106,6 +106,27 @@ CRenderer::CRenderer() { texShader.tint = glGetUniformLocation(prog, "tint"); texShader.useAlphaMatte = glGetUniformLocation(prog, "useAlphaMatte"); + prog = createProgram(TEXVERTSRC, TEXMIXFRAGSRCRGBA); + texMixShader.program = prog; + texMixShader.proj = glGetUniformLocation(prog, "proj"); + texMixShader.tex = glGetUniformLocation(prog, "tex1"); + texMixShader.tex2 = glGetUniformLocation(prog, "tex2"); + texMixShader.alphaMatte = glGetUniformLocation(prog, "texMatte"); + texMixShader.alpha = glGetUniformLocation(prog, "alpha"); + texMixShader.mixFactor = glGetUniformLocation(prog, "mixFactor"); + texMixShader.texAttrib = glGetAttribLocation(prog, "texcoord"); + texMixShader.matteTexAttrib = glGetAttribLocation(prog, "texcoordMatte"); + texMixShader.posAttrib = glGetAttribLocation(prog, "pos"); + texMixShader.discardOpaque = glGetUniformLocation(prog, "discardOpaque"); + texMixShader.discardAlpha = glGetUniformLocation(prog, "discardAlpha"); + texMixShader.discardAlphaValue = glGetUniformLocation(prog, "discardAlphaValue"); + texMixShader.topLeft = glGetUniformLocation(prog, "topLeft"); + texMixShader.fullSize = glGetUniformLocation(prog, "fullSize"); + texMixShader.radius = glGetUniformLocation(prog, "radius"); + texMixShader.applyTint = glGetUniformLocation(prog, "applyTint"); + texMixShader.tint = glGetUniformLocation(prog, "tint"); + texMixShader.useAlphaMatte = glGetUniformLocation(prog, "useAlphaMatte"); + prog = createProgram(TEXVERTSRC, FRAGBLUR1); blurShader1.program = prog; blurShader1.tex = glGetUniformLocation(prog, "tex"); @@ -347,6 +368,52 @@ void CRenderer::renderTexture(const CBox& box, const CTexture& tex, float a, int glBindTexture(tex.m_iTarget, 0); } +void CRenderer::renderTextureMix(const CBox& box, const CTexture& tex, const CTexture& tex2, float a, float mixFactor, int rounding, std::optional tr) { + const auto ROUNDEDBOX = box.copy().round(); + Mat3x3 matrix = projMatrix.projectBox(ROUNDEDBOX, tr.value_or(HYPRUTILS_TRANSFORM_FLIPPED_180), box.rot); + Mat3x3 glMatrix = projection.copy().multiply(matrix); + + CShader* shader = &texMixShader; + + glActiveTexture(GL_TEXTURE0); + glBindTexture(tex.m_iTarget, tex.m_iTexID); + + glActiveTexture(GL_TEXTURE1); + glBindTexture(tex2.m_iTarget, tex2.m_iTexID); + + glUseProgram(shader->program); + + glUniformMatrix3fv(shader->proj, 1, GL_TRUE, glMatrix.getMatrix().data()); + glUniform1i(shader->tex, 0); + glUniform1i(shader->tex2, 1); + glUniform1f(shader->alpha, a); + glUniform1f(shader->mixFactor, mixFactor); + const auto TOPLEFT = Vector2D(ROUNDEDBOX.x, ROUNDEDBOX.y); + const auto FULLSIZE = Vector2D(ROUNDEDBOX.width, ROUNDEDBOX.height); + + // Rounded corners + glUniform2f(shader->topLeft, TOPLEFT.x, TOPLEFT.y); + glUniform2f(shader->fullSize, FULLSIZE.x, FULLSIZE.y); + glUniform1f(shader->radius, rounding); + + glUniform1i(shader->discardOpaque, 0); + glUniform1i(shader->discardAlpha, 0); + glUniform1i(shader->applyTint, 0); + + glVertexAttribPointer(shader->posAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts); + glVertexAttribPointer(shader->texAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts); + + glEnableVertexAttribArray(shader->posAttrib); + glEnableVertexAttribArray(shader->texAttrib); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + glDisableVertexAttribArray(shader->posAttrib); + glDisableVertexAttribArray(shader->texAttrib); + + glBindTexture(tex.m_iTarget, 0); +} + std::vector>* CRenderer::getOrCreateWidgetsFor(const CSessionLockSurface* surf) { if (!widgets.contains(surf)) { diff --git a/src/renderer/Renderer.hpp b/src/renderer/Renderer.hpp index 55501d7..edc8815 100644 --- a/src/renderer/Renderer.hpp +++ b/src/renderer/Renderer.hpp @@ -33,6 +33,7 @@ class CRenderer { void renderRect(const CBox& box, const CColor& col, int rounding = 0); void renderBorder(const CBox& box, const CGradientValueData& gradient, int thickness, int rounding = 0, float alpha = 1.0); void renderTexture(const CBox& box, const CTexture& tex, float a = 1.0, int rounding = 0, std::optional tr = {}); + void renderTextureMix(const CBox& box, const CTexture& tex, const CTexture& tex2, float a = 1.0, float mixFactor = 0.0, int rounding = 0, std::optional tr = {}); void blurFB(const CFramebuffer& outfb, SBlurParams params); std::unique_ptr asyncResourceGatherer; @@ -50,6 +51,7 @@ class CRenderer { CShader rectShader; CShader texShader; + CShader texMixShader; CShader blurShader1; CShader blurShader2; CShader blurPrepareShader; diff --git a/src/renderer/Shader.hpp b/src/renderer/Shader.hpp index bb1d5e8..8425167 100644 --- a/src/renderer/Shader.hpp +++ b/src/renderer/Shader.hpp @@ -13,7 +13,9 @@ class CShader { GLint color = -1; GLint alphaMatte = -1; GLint tex = -1; + GLint tex2 = -1; GLint alpha = -1; + GLfloat mixFactor = -1; GLint posAttrib = -1; GLint texAttrib = -1; GLint matteTexAttrib = -1; diff --git a/src/renderer/Shaders.hpp b/src/renderer/Shaders.hpp index 9a2f417..15b6c23 100644 --- a/src/renderer/Shaders.hpp +++ b/src/renderer/Shaders.hpp @@ -129,6 +129,49 @@ void main() { gl_FragColor = pixColor * alpha; })#"; +inline const std::string TEXMIXFRAGSRCRGBA = R"#( +precision highp float; +varying vec2 v_texcoord; // is in 0-1 +uniform sampler2D tex1; +uniform sampler2D tex2; +uniform float mixFactor; +uniform float alpha; + +uniform vec2 topLeft; +uniform vec2 fullSize; +uniform float radius; + +uniform int discardOpaque; +uniform int discardAlpha; +uniform float discardAlphaValue; + +uniform int applyTint; +uniform vec3 tint; + +void main() { + + vec4 pixColor = mix(texture2D(tex1, v_texcoord), texture2D(tex2, v_texcoord), smoothstep(0.0, 1.0, mixFactor)); + + if (discardOpaque == 1 && pixColor[3] * alpha == 1.0) + discard; + + if (discardAlpha == 1 && pixColor[3] <= discardAlphaValue) + discard; + + if (applyTint == 1) { + pixColor[0] = pixColor[0] * tint[0]; + pixColor[1] = pixColor[1] * tint[1]; + pixColor[2] = pixColor[2] * tint[2]; + } + + if (radius > 0.0) { + )#" + + ROUNDED_SHADER_FUNC("pixColor") + R"#( + } + + gl_FragColor = pixColor * alpha; +})#"; + inline const std::string FRAGBLUR1 = R"#( #version 100 precision highp float; diff --git a/src/renderer/widgets/Background.cpp b/src/renderer/widgets/Background.cpp index ac7ea0b..f342474 100644 --- a/src/renderer/widgets/Background.cpp +++ b/src/renderer/widgets/Background.cpp @@ -1,18 +1,58 @@ #include "Background.hpp" #include "../Renderer.hpp" +#include "../../core/hyprlock.hpp" +#include "src/helpers/Log.hpp" +#include #include +#include +#include +#include + +CBackground::~CBackground() { + + if (reloadTimer) { + reloadTimer->cancel(); + reloadTimer.reset(); + } + + if (fade) { + if (fade->crossFadeTimer) { + fade->crossFadeTimer->cancel(); + fade->crossFadeTimer.reset(); + } + fade.reset(); + } +} CBackground::CBackground(const Vector2D& viewport_, COutput* output_, const std::string& resourceID_, const std::unordered_map& props, bool ss) : viewport(viewport_), resourceID(resourceID_), output(output_), isScreenshot(ss) { - 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")); + try { + 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")); + path = std::any_cast(props.at("path")); + reloadCommand = std::any_cast(props.at("reload_cmd")); + reloadTime = std::any_cast(props.at("reload_time")); + crossFadeTime = std::any_cast(props.at("crossfade_time")); + + } catch (const std::bad_any_cast& e) { + RASSERT(false, "Failed to construct CBackground: {}", e.what()); // + } catch (const std::out_of_range& e) { + RASSERT(false, "Missing propperty for CBackground: {}", e.what()); // + } + + try { + modificationTime = std::filesystem::last_write_time(path); + } catch (std::exception& e) { Debug::log(ERR, "{}", e.what()); } + + if (!isScreenshot) + plantReloadTimer(); // No reloads for screenshots. } void CBackground::renderRect(CColor color) { @@ -20,6 +60,27 @@ void CBackground::renderRect(CColor color) { g_pRenderer->renderRect(monbox, color, 0); } +static void onReloadTimer(std::shared_ptr self, void* data) { + const auto PBG = (CBackground*)data; + + PBG->onReloadTimerUpdate(); + PBG->plantReloadTimer(); +} + +static void onCrossFadeTimer(std::shared_ptr self, void* data) { + const auto PBG = (CBackground*)data; + PBG->onCrossFadeTimerUpdate(); +} + +static void onAssetCallback(void* data) { + const auto PBG = (CBackground*)data; + PBG->startCrossFadeOrUpdateRender(); +} + +static void onAssetCallbackTimer(std::shared_ptr self, void* data) { + onAssetCallback(data); +} + bool CBackground::draw(const SRenderData& data) { if (resourceID.empty()) { @@ -45,10 +106,13 @@ bool CBackground::draw(const SRenderData& data) { return true; } - if ((blurPasses > 0 || isScreenshot) && !blurredFB.isAllocated()) { + if (fade || ((blurPasses > 0 || isScreenshot) && (!blurredFB.isAllocated() || firstRender))) { + + if (firstRender) + firstRender = false; + // make it brah Vector2D size = asset->texture.m_vSize; - if (output->transform % 2 == 1 && isScreenshot) { size.x = asset->texture.m_vSize.y; size.y = asset->texture.m_vSize.x; @@ -67,13 +131,23 @@ bool CBackground::draw(const SRenderData& data) { else texbox.x = -(texbox.w - viewport.x) / 2.f; texbox.round(); - blurredFB.alloc(viewport.x, viewport.y); // TODO 10 bit + + if (!blurredFB.isAllocated()) + blurredFB.alloc(viewport.x, viewport.y); // TODO 10 bit + blurredFB.bind(); - g_pRenderer->renderTexture(texbox, asset->texture, 1.0, 0, - isScreenshot ? - wlTransformToHyprutils(invertTransform(output->transform)) : - HYPRUTILS_TRANSFORM_NORMAL); // this could be omitted but whatever it's only once and makes code cleaner plus less blurring on large texs + if (fade) + g_pRenderer->renderTextureMix(texbox, asset->texture, pendingAsset->texture, 1.0, + std::chrono::duration_cast(std::chrono::system_clock::now() - fade->start).count() / (1000 * crossFadeTime), + 0, HYPRUTILS_TRANSFORM_NORMAL); + else + g_pRenderer->renderTexture(texbox, asset->texture, 1.0, 0, + isScreenshot ? + wlTransformToHyprutils(invertTransform(output->transform)) : + HYPRUTILS_TRANSFORM_NORMAL); // this could be omitted but whatever it's only once and makes code cleaner plus less blurring on large texs + + if (blurPasses > 0) g_pRenderer->blurFB(blurredFB, CRenderer::SBlurParams{blurSize, blurPasses, noise, contrast, brightness, vibrancy, vibrancy_darkness}); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); @@ -97,5 +171,114 @@ bool CBackground::draw(const SRenderData& data) { texbox.round(); g_pRenderer->renderTexture(texbox, *tex, data.opacity, 0, HYPRUTILS_TRANSFORM_FLIPPED_180); - return data.opacity < 1.0; + return fade || data.opacity < 1.0; // actively render during fading +} + +void CBackground::plantReloadTimer() { + + if (reloadTime == 0) + reloadTimer = g_pHyprlock->addTimer(std::chrono::hours(1), onReloadTimer, this, true); + else if (reloadTime > 0) + reloadTimer = g_pHyprlock->addTimer(std::chrono::seconds(reloadTime), onReloadTimer, this, false); +} + +void CBackground::onCrossFadeTimerUpdate() { + + // Animation done: Unload previous asset, deinitialize the fade and pass the asset + + if (fade) { + fade->crossFadeTimer.reset(); + fade.reset(nullptr); + } + + if (!(blurPasses > 0 || isScreenshot)) + blurredFB.release(); + + asset = pendingAsset; + resourceID = pendingResourceID; + pendingResourceID = ""; + pendingAsset = nullptr; + firstRender = true; + + g_pHyprlock->renderOutput(output->stringPort); +} + +void CBackground::onReloadTimerUpdate() { + const std::string OLDPATH = path; + + // Path parsing and early returns + + if (!reloadCommand.empty()) { + path = g_pHyprlock->spawnSync(reloadCommand); + + if (path.ends_with('\n')) + path.pop_back(); + + if (path.starts_with("file://")) + path = path.substr(7); + + if (path.empty()) + return; + } + + try { + const auto MTIME = std::filesystem::last_write_time(path); + if (OLDPATH == path && MTIME == modificationTime) + return; + + modificationTime = MTIME; + } catch (std::exception& e) { + path = OLDPATH; + Debug::log(ERR, "{}", e.what()); + return; + } + + if (!pendingResourceID.empty()) + return; + + // Issue the next request + + request.id = std::string{"background:"} + path + ",time:" + std::to_string((uint64_t)modificationTime.time_since_epoch().count()); + pendingResourceID = request.id; + request.asset = path; + request.type = CAsyncResourceGatherer::eTargetType::TARGET_IMAGE; + + request.callback = onAssetCallback; + request.callbackData = this; + + g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request); +} + +void CBackground::startCrossFadeOrUpdateRender() { + auto newAsset = g_pRenderer->asyncResourceGatherer->getAssetByID(pendingResourceID); + if (newAsset) { + if (newAsset->texture.m_iType == TEXTURE_INVALID) { + g_pRenderer->asyncResourceGatherer->unloadAsset(newAsset); + Debug::log(ERR, "New asset had an invalid texture!"); + } else if (resourceID != pendingResourceID) { + pendingAsset = newAsset; + if (crossFadeTime > 0) { + // Start a fade + if (!fade) + fade = std::make_unique(std::chrono::system_clock::now(), 0, nullptr); + else { + // Maybe we where already fading so reset it just in case, but should'nt be happening. + if (fade->crossFadeTimer) { + fade->crossFadeTimer->cancel(); + fade->crossFadeTimer.reset(); + } + } + fade->start = std::chrono::system_clock::now(); + fade->a = 0; + fade->crossFadeTimer = g_pHyprlock->addTimer(std::chrono::milliseconds((int)(1000.0 * crossFadeTime)), onCrossFadeTimer, this); + } else { + onCrossFadeTimerUpdate(); + } + } + } else if (!pendingResourceID.empty()) { + Debug::log(WARN, "Asset {} not available after the asyncResourceGatherer's callback!", pendingResourceID); + g_pHyprlock->addTimer(std::chrono::milliseconds(100), onAssetCallbackTimer, this); + } + + g_pHyprlock->renderOutput(output->stringPort); } \ No newline at end of file diff --git a/src/renderer/widgets/Background.hpp b/src/renderer/widgets/Background.hpp index bc3d981..9e11aff 100644 --- a/src/renderer/widgets/Background.hpp +++ b/src/renderer/widgets/Background.hpp @@ -3,36 +3,68 @@ #include "IWidget.hpp" #include "../../helpers/Color.hpp" #include "../../helpers/Math.hpp" +#include "../../core/Timer.hpp" #include "../Framebuffer.hpp" +#include "../AsyncResourceGatherer.hpp" #include #include #include +#include +#include struct SPreloadedAsset; class COutput; +struct SFade { + std::chrono::system_clock::time_point start; + float a = 0; + std::shared_ptr crossFadeTimer = nullptr; +}; + class CBackground : public IWidget { public: CBackground(const Vector2D& viewport, COutput* output_, const std::string& resourceID, const std::unordered_map& props, bool ss_); + ~CBackground(); virtual bool draw(const SRenderData& data); void renderRect(CColor color); + void onReloadTimerUpdate(); + void onCrossFadeTimerUpdate(); + void plantReloadTimer(); + void startCrossFadeOrUpdateRender(); + private: // if needed - CFramebuffer blurredFB; + 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; - SPreloadedAsset* asset = nullptr; - COutput* output = nullptr; - bool isScreenshot = false; + 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 path = ""; + + std::string resourceID; + std::string pendingResourceID; + + float crossFadeTime = -1.0; + + CColor color; + SPreloadedAsset* asset = nullptr; + COutput* output = nullptr; + bool isScreenshot = false; + SPreloadedAsset* pendingAsset = nullptr; + bool firstRender = true; + + std::unique_ptr fade; + + int reloadTime = -1; + std::string reloadCommand; + CAsyncResourceGatherer::SPreloadRequest request; + std::shared_ptr reloadTimer; + std::filesystem::file_time_type modificationTime; }; \ No newline at end of file