core: use a screenshot for fade in/out (#726)

This commit is contained in:
Maximilian Seidler 2025-06-22 09:24:39 +02:00 committed by GitHub
parent e67036e8cc
commit a9638986c3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 252 additions and 229 deletions

View file

@ -6,6 +6,7 @@
#include "../auth/Auth.hpp" #include "../auth/Auth.hpp"
#include "../auth/Fingerprint.hpp" #include "../auth/Fingerprint.hpp"
#include "Egl.hpp" #include "Egl.hpp"
#include <chrono>
#include <hyprutils/memory/UniquePtr.hpp> #include <hyprutils/memory/UniquePtr.hpp>
#include <sys/wait.h> #include <sys/wait.h>
#include <sys/poll.h> #include <sys/poll.h>
@ -313,18 +314,52 @@ void CHyprlock::run() {
Debug::log(LOG, "Running on {}", m_sCurrentDesktop); Debug::log(LOG, "Running on {}", m_sCurrentDesktop);
// Hyprland violates the protocol a bit to allow for this. if (!g_pHyprlock->m_bImmediateRender) {
if (m_sCurrentDesktop != "Hyprland") { // Gather background resources and screencopy frames before locking the screen.
// We need to do this because as soon as we lock the screen, workspaces frames can no longer be captured. It either won't work at all, or we will capture hyprlock itself.
// Bypass with --immediate-render (can cause the background first rendering a solid color and missing or inaccurate screencopy frames)
const auto MAXDELAYMS = 2000; // 2 Seconds
const auto STARTGATHERTP = std::chrono::system_clock::now();
int fdcount = 1;
pollfd pollfds[2];
pollfds[0] = {
.fd = wl_display_get_fd(m_sWaylandState.display),
.events = POLLIN,
};
if (g_pRenderer->asyncResourceGatherer->gatheredEventfd.isValid()) {
pollfds[1] = {
.fd = g_pRenderer->asyncResourceGatherer->gatheredEventfd.get(),
.events = POLLIN,
};
fdcount++;
}
while (!g_pRenderer->asyncResourceGatherer->gathered) { while (!g_pRenderer->asyncResourceGatherer->gathered) {
wl_display_flush(m_sWaylandState.display); wl_display_flush(m_sWaylandState.display);
if (wl_display_prepare_read(m_sWaylandState.display) == 0) { if (wl_display_prepare_read(m_sWaylandState.display) == 0) {
if (poll(pollfds, fdcount, /* 100ms timeout */ 100) < 0) {
RASSERT(errno == EINTR, "[core] Polling fds failed with {}", errno);
wl_display_cancel_read(m_sWaylandState.display);
continue;
}
wl_display_read_events(m_sWaylandState.display); wl_display_read_events(m_sWaylandState.display);
wl_display_dispatch_pending(m_sWaylandState.display); wl_display_dispatch_pending(m_sWaylandState.display);
} else { } else {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
wl_display_dispatch(m_sWaylandState.display); wl_display_dispatch(m_sWaylandState.display);
} }
std::this_thread::sleep_for(std::chrono::milliseconds(1));
if (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - STARTGATHERTP).count() > MAXDELAYMS) {
Debug::log(WARN, "Gathering resources timed out after {} milliseconds. Backgrounds may be delayed and render `background:color` at first.", MAXDELAYMS);
break;
}
} }
Debug::log(LOG, "Resources gathered after {} milliseconds",
std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - STARTGATHERTP).count());
} }
// Failed to lock the session // Failed to lock the session
@ -499,16 +534,13 @@ void CHyprlock::unlock() {
return; return;
} }
const bool IMMEDIATE = m_sCurrentDesktop != "Hyprland"; g_pRenderer->startFadeOut(true);
g_pRenderer->startFadeOut(true, IMMEDIATE);
m_bUnlockedCalled = true;
renderAllOutputs(); renderAllOutputs();
} }
bool CHyprlock::isUnlocked() { bool CHyprlock::isUnlocked() {
return m_bUnlockedCalled || m_bTerminate; return !m_bLocked;
} }
void CHyprlock::clearPasswordBuffer() { void CHyprlock::clearPasswordBuffer() {

View file

@ -158,8 +158,6 @@ class CHyprlock {
bool timerEvent = false; bool timerEvent = false;
} m_sLoopState; } m_sLoopState;
bool m_bUnlockedCalled = false;
std::vector<std::shared_ptr<CTimer>> m_vTimers; std::vector<std::shared_ptr<CTimer>> m_vTimers;
std::vector<uint32_t> m_vPressedKeys; std::vector<uint32_t> m_vPressedKeys;

View file

@ -1,16 +1,19 @@
#include "AsyncResourceGatherer.hpp" #include "AsyncResourceGatherer.hpp"
#include "../config/ConfigManager.hpp" #include "../config/ConfigManager.hpp"
#include "../core/Egl.hpp" #include "../core/Egl.hpp"
#include <cairo/cairo.h>
#include <pango/pangocairo.h>
#include <algorithm>
#include <filesystem>
#include "../core/hyprlock.hpp" #include "../core/hyprlock.hpp"
#include "../helpers/Color.hpp"
#include "../helpers/Log.hpp"
#include "../helpers/MiscFunctions.hpp" #include "../helpers/MiscFunctions.hpp"
#include "src/helpers/Color.hpp" #include <algorithm>
#include "src/helpers/Log.hpp" #include <cairo/cairo.h>
#include <filesystem>
#include <pango/pangocairo.h>
#include <sys/eventfd.h>
#include <hyprgraphics/image/Image.hpp> #include <hyprgraphics/image/Image.hpp>
#include <hyprutils/os/FileDescriptor.hpp>
using namespace Hyprgraphics; using namespace Hyprgraphics;
using namespace Hyprutils::OS;
CAsyncResourceGatherer::CAsyncResourceGatherer() { CAsyncResourceGatherer::CAsyncResourceGatherer() {
if (g_pHyprlock->getScreencopy()) if (g_pHyprlock->getScreencopy())
@ -18,45 +21,39 @@ CAsyncResourceGatherer::CAsyncResourceGatherer() {
initialGatherThread = std::thread([this]() { this->gather(); }); initialGatherThread = std::thread([this]() { this->gather(); });
asyncLoopThread = std::thread([this]() { this->asyncAssetSpinLock(); }); asyncLoopThread = std::thread([this]() { this->asyncAssetSpinLock(); });
gatheredEventfd = CFileDescriptor{eventfd(0, EFD_CLOEXEC)};
if (!gatheredEventfd.isValid())
Debug::log(ERR, "Failed to create eventfd: {}", strerror(errno));
} }
void CAsyncResourceGatherer::enqueueScreencopyFrames() { void CAsyncResourceGatherer::enqueueScreencopyFrames() {
// some things can't be done async :( const auto FADEINCFG = g_pConfigManager->m_AnimationTree.getConfig("fadeIn");
// gather background textures when needed const auto FADEOUTCFG = g_pConfigManager->m_AnimationTree.getConfig("fadeOut");
const auto BGSCREENSHOT = std::ranges::any_of(g_pConfigManager->getWidgetConfigs(), [](const auto& w) { //
return w.type == "background" && std::string{std::any_cast<Hyprlang::STRING>(w.values.at("path"))} == "screenshot";
});
const auto CWIDGETS = g_pConfigManager->getWidgetConfigs(); // No screenshot background AND no fade in AND no fade out -> we don't need screencopy
if (!BGSCREENSHOT && (!FADEINCFG->pValues || !FADEINCFG->pValues->internalEnabled) && //
(!FADEOUTCFG->pValues || !FADEOUTCFG->pValues->internalEnabled))
return;
std::vector<std::string> mons; for (const auto& MON : g_pHyprlock->m_vOutputs) {
scframes.emplace_back(makeUnique<CScreencopyFrame>(MON));
for (auto& c : CWIDGETS) {
if (c.type != "background")
continue;
if (std::string{std::any_cast<Hyprlang::STRING>(c.values.at("path"))} != "screenshot")
continue;
// mamma mia
if (c.monitor.empty()) {
mons.clear();
for (auto& m : g_pHyprlock->m_vOutputs) {
mons.push_back(m->stringPort);
}
break;
} else
mons.push_back(c.monitor);
}
for (auto& mon : mons) {
const auto MON = std::ranges::find_if(g_pHyprlock->m_vOutputs, [mon](const auto& other) { return other->stringPort == mon || other->stringDesc.starts_with(mon); });
if (MON == g_pHyprlock->m_vOutputs.end())
continue;
scframes.emplace_back(makeUnique<CScreencopyFrame>(*MON));
} }
} }
SPreloadedAsset* CAsyncResourceGatherer::getAssetByID(const std::string& id) { SPreloadedAsset* CAsyncResourceGatherer::getAssetByID(const std::string& id) {
if (id.contains(CScreencopyFrame::RESOURCEIDPREFIX)) {
for (auto& frame : scframes) {
if (id == frame->m_resourceID)
return frame->m_asset.ready ? &frame->m_asset : nullptr;
}
return nullptr;
}
for (auto& a : assets) { for (auto& a : assets) {
if (a.first == id) if (a.first == id)
return &a.second; return &a.second;
@ -69,16 +66,10 @@ SPreloadedAsset* CAsyncResourceGatherer::getAssetByID(const std::string& id) {
} }
}; };
for (auto& frame : scframes) {
if (id == frame->m_resourceID)
return frame->m_asset.ready ? &frame->m_asset : nullptr;
}
return nullptr; return nullptr;
} }
static SP<CCairoSurface> getCairoSurfaceFromImageFile(const std::filesystem::path& path) { static SP<CCairoSurface> getCairoSurfaceFromImageFile(const std::filesystem::path& path) {
auto image = CImage(path); auto image = CImage(path);
if (!image.success()) { if (!image.success()) {
Debug::log(ERR, "Image {} could not be loaded: {}", path.string(), image.getError()); Debug::log(ERR, "Image {} could not be loaded: {}", path.string(), image.getError());
@ -131,6 +122,9 @@ void CAsyncResourceGatherer::gather() {
} }
gathered = true; gathered = true;
// wake hyprlock from poll
if (gatheredEventfd.isValid())
eventfd_write(gatheredEventfd.get(), 1);
} }
bool CAsyncResourceGatherer::apply() { bool CAsyncResourceGatherer::apply() {

View file

@ -9,13 +9,15 @@
#include <any> #include <any>
#include "Shared.hpp" #include "Shared.hpp"
#include <hyprgraphics/cairo/CairoSurface.hpp> #include <hyprgraphics/cairo/CairoSurface.hpp>
#include <hyprutils/os/FileDescriptor.hpp>
class CAsyncResourceGatherer { class CAsyncResourceGatherer {
public: public:
CAsyncResourceGatherer(); CAsyncResourceGatherer();
std::atomic<bool> gathered = false; std::atomic<bool> gathered = false;
Hyprutils::OS::CFileDescriptor gatheredEventfd;
std::atomic<float> progress = 0; std::atomic<float> progress = 0;
/* only call from ogl thread */ /* only call from ogl thread */
SPreloadedAsset* getAssetByID(const std::string& id); SPreloadedAsset* getAssetByID(const std::string& id);

View file

@ -1,6 +1,8 @@
#include "Framebuffer.hpp" #include "Framebuffer.hpp"
#include "../helpers/Log.hpp" #include "../helpers/Log.hpp"
#include <hyprutils/os/FileDescriptor.hpp>
#include <libdrm/drm_fourcc.h> #include <libdrm/drm_fourcc.h>
#include <utility>
static uint32_t drmFormatToGL(uint32_t drm) { static uint32_t drmFormatToGL(uint32_t drm) {
switch (drm) { switch (drm) {
@ -97,7 +99,7 @@ void CFramebuffer::bind() const {
glViewport(0, 0, m_vSize.x, m_vSize.y); glViewport(0, 0, m_vSize.x, m_vSize.y);
} }
void CFramebuffer::release() { void CFramebuffer::destroyBuffer() {
if (m_iFb != (uint32_t)-1 && m_iFb) if (m_iFb != (uint32_t)-1 && m_iFb)
glDeleteFramebuffers(1, &m_iFb); glDeleteFramebuffers(1, &m_iFb);
@ -114,7 +116,7 @@ void CFramebuffer::release() {
} }
CFramebuffer::~CFramebuffer() { CFramebuffer::~CFramebuffer() {
release(); destroyBuffer();
} }
bool CFramebuffer::isAllocated() const { bool CFramebuffer::isAllocated() const {

View file

@ -8,17 +8,19 @@ class CFramebuffer {
public: public:
~CFramebuffer(); ~CFramebuffer();
bool alloc(int w, int h, bool highres = false); bool alloc(int w, int h, bool highres = false);
void addStencil(); void addStencil();
void bind() const; void bind() const;
void release(); void destroyBuffer();
void reset(); bool isAllocated() const;
bool isAllocated() const;
Vector2D m_vSize; Vector2D m_vSize;
CTexture m_cTex; CTexture m_cTex;
GLuint m_iFb = -1; GLuint m_iFb = -1;
CTexture* m_pStencilTex = nullptr; CTexture* m_pStencilTex = nullptr;
CFramebuffer& operator=(CFramebuffer&&) = delete;
CFramebuffer& operator=(const CFramebuffer&) = delete;
}; };

View file

@ -613,11 +613,8 @@ void CRenderer::startFadeIn() {
opacity->setCallbackOnEnd([this](auto) { opacity->setConfig(g_pConfigManager->m_AnimationTree.getConfig("fadeOut")); }, true); opacity->setCallbackOnEnd([this](auto) { opacity->setConfig(g_pConfigManager->m_AnimationTree.getConfig("fadeOut")); }, true);
} }
void CRenderer::startFadeOut(bool unlock, bool immediate) { void CRenderer::startFadeOut(bool unlock) {
if (immediate) *opacity = 0.f;
opacity->setValueAndWarp(0.f);
else
*opacity = 0.f;
if (unlock) if (unlock)
opacity->setCallbackOnEnd([](auto) { g_pHyprlock->releaseSessionLock(); }, true); opacity->setCallbackOnEnd([](auto) { g_pHyprlock->releaseSessionLock(); }, true);

View file

@ -47,7 +47,7 @@ class CRenderer {
void reconfigureWidgetsFor(OUTPUTID id); void reconfigureWidgetsFor(OUTPUTID id);
void startFadeIn(); void startFadeIn();
void startFadeOut(bool unlock = false, bool immediate = true); void startFadeOut(bool unlock = false);
std::vector<SP<IWidget>>& getOrCreateWidgetsFor(const CSessionLockSurface& surf); std::vector<SP<IWidget>>& getOrCreateWidgetsFor(const CSessionLockSurface& surf);
private: private:

View file

@ -24,7 +24,7 @@ static PFNEGLQUERYDMABUFMODIFIERSEXTPROC eglQueryDmaBufModifiersEXT = nullpt
// //
std::string CScreencopyFrame::getResourceId(SP<COutput> pOutput) { std::string CScreencopyFrame::getResourceId(SP<COutput> pOutput) {
return std::format("screencopy:{}-{}x{}", pOutput->stringPort, pOutput->size.x, pOutput->size.y); return RESOURCEIDPREFIX + std::format(":{}-{}x{}", pOutput->stringPort, pOutput->size.x, pOutput->size.y);
} }
CScreencopyFrame::CScreencopyFrame(SP<COutput> pOutput) : m_outputRef(pOutput) { CScreencopyFrame::CScreencopyFrame(SP<COutput> pOutput) : m_outputRef(pOutput) {

View file

@ -22,7 +22,8 @@ class ISCFrame {
class CScreencopyFrame { class CScreencopyFrame {
public: public:
static std::string getResourceId(SP<COutput> pOutput); static std::string getResourceId(SP<COutput> pOutput);
static constexpr const std::string RESOURCEIDPREFIX = "screencopy";
CScreencopyFrame(SP<COutput> pOutput); CScreencopyFrame(SP<COutput> pOutput);
~CScreencopyFrame() = default; ~CScreencopyFrame() = default;

View file

@ -1,14 +1,23 @@
#include "Background.hpp" #include "Background.hpp"
#include "../Renderer.hpp" #include "../Renderer.hpp"
#include "../Framebuffer.hpp"
#include "../Shared.hpp"
#include "../../core/hyprlock.hpp" #include "../../core/hyprlock.hpp"
#include "../../helpers/Log.hpp" #include "../../helpers/Log.hpp"
#include "../../helpers/MiscFunctions.hpp" #include "../../helpers/MiscFunctions.hpp"
#include "../../core/AnimationManager.hpp"
#include "../../config/ConfigManager.hpp"
#include <chrono> #include <chrono>
#include <hyprlang.hpp> #include <hyprlang.hpp>
#include <filesystem> #include <filesystem>
#include <memory> #include <memory>
#include <GLES3/gl32.h> #include <GLES3/gl32.h>
CBackground::CBackground() {
blurredFB = makeUnique<CFramebuffer>();
pendingBlurredFB = makeUnique<CFramebuffer>();
}
CBackground::~CBackground() { CBackground::~CBackground() {
reset(); reset();
} }
@ -32,7 +41,6 @@ void CBackground::configure(const std::unordered_map<std::string, std::any>& pro
path = std::any_cast<Hyprlang::STRING>(props.at("path")); path = std::any_cast<Hyprlang::STRING>(props.at("path"));
reloadCommand = std::any_cast<Hyprlang::STRING>(props.at("reload_cmd")); reloadCommand = std::any_cast<Hyprlang::STRING>(props.at("reload_cmd"));
reloadTime = std::any_cast<Hyprlang::INT>(props.at("reload_time")); reloadTime = std::any_cast<Hyprlang::INT>(props.at("reload_time"));
crossFadeTime = std::any_cast<Hyprlang::FLOAT>(props.at("crossfade_time"));
} catch (const std::bad_any_cast& e) { } catch (const std::bad_any_cast& e) {
RASSERT(false, "Failed to construct CBackground: {}", e.what()); // RASSERT(false, "Failed to construct CBackground: {}", e.what()); //
@ -42,12 +50,15 @@ void CBackground::configure(const std::unordered_map<std::string, std::any>& pro
isScreenshot = path == "screenshot"; isScreenshot = path == "screenshot";
viewport = pOutput->getViewport(); viewport = pOutput->getViewport();
outputPort = pOutput->stringPort; outputPort = pOutput->stringPort;
transform = isScreenshot ? wlTransformToHyprutils(invertTransform(pOutput->transform)) : HYPRUTILS_TRANSFORM_NORMAL; transform = isScreenshot ? wlTransformToHyprutils(invertTransform(pOutput->transform)) : HYPRUTILS_TRANSFORM_NORMAL;
scResourceID = CScreencopyFrame::getResourceId(pOutput);
g_pAnimationManager->createAnimation(0.f, crossFadeProgress, g_pConfigManager->m_AnimationTree.getConfig("fadeIn"));
if (isScreenshot) { if (isScreenshot) {
resourceID = CScreencopyFrame::getResourceId(pOutput); resourceID = scResourceID;
// When the initial gather of the asyncResourceGatherer is completed (ready), all DMAFrames are available. // When the initial gather of the asyncResourceGatherer is completed (ready), all DMAFrames are available.
// Dynamic ones are tricky, because a screencopy would copy hyprlock itself. // Dynamic ones are tricky, because a screencopy would copy hyprlock itself.
if (g_pRenderer->asyncResourceGatherer->gathered) { if (g_pRenderer->asyncResourceGatherer->gathered) {
@ -78,13 +89,8 @@ void CBackground::reset() {
reloadTimer.reset(); reloadTimer.reset();
} }
if (fade) { blurredFB->destroyBuffer();
if (fade->crossFadeTimer) { pendingBlurredFB->destroyBuffer();
fade->crossFadeTimer->cancel();
fade->crossFadeTimer.reset();
}
fade.reset();
}
} }
void CBackground::renderRect(CHyprColor color) { void CBackground::renderRect(CHyprColor color) {
@ -99,98 +105,26 @@ static void onReloadTimer(WP<CBackground> ref) {
} }
} }
static void onCrossFadeTimer(WP<CBackground> ref) {
if (auto PBG = ref.lock(); PBG)
PBG->onCrossFadeTimerUpdate();
}
static void onAssetCallback(WP<CBackground> ref) { static void onAssetCallback(WP<CBackground> ref) {
if (auto PBG = ref.lock(); PBG) if (auto PBG = ref.lock(); PBG)
PBG->startCrossFadeOrUpdateRender(); PBG->startCrossFade();
} }
bool CBackground::draw(const SRenderData& data) { void CBackground::renderBlur(const CTexture& tex, CFramebuffer& fb) {
if (firstRender)
firstRender = false;
if (resourceID.empty()) { // make it brah
CHyprColor col = color; Vector2D size = asset->texture.m_vSize;
col.a *= data.opacity; if (transform % 2 == 1 && isScreenshot) {
renderRect(col); size.x = asset->texture.m_vSize.y;
return data.opacity < 1.0; size.y = asset->texture.m_vSize.x;
} }
if (!asset) CBox texbox = {{}, size};
asset = g_pRenderer->asyncResourceGatherer->getAssetByID(resourceID);
if (!asset) { float scaleX = viewport.x / size.x;
CHyprColor col = color; float scaleY = viewport.y / size.y;
col.a *= data.opacity;
renderRect(col);
return true;
}
if (asset->texture.m_iType == TEXTURE_INVALID) {
g_pRenderer->asyncResourceGatherer->unloadAsset(asset);
resourceID = "";
return true;
}
if (fade || ((blurPasses > 0 || isScreenshot) && (!blurredFB.isAllocated() || firstRender))) {
if (firstRender)
firstRender = false;
// make it brah
Vector2D size = asset->texture.m_vSize;
if (transform % 2 == 1 && isScreenshot) {
size.x = asset->texture.m_vSize.y;
size.y = asset->texture.m_vSize.x;
}
CBox texbox = {{}, size};
float scaleX = viewport.x / size.x;
float scaleY = viewport.y / size.y;
texbox.w *= std::max(scaleX, scaleY);
texbox.h *= std::max(scaleX, scaleY);
if (scaleX > scaleY)
texbox.y = -(texbox.h - viewport.y) / 2.f;
else
texbox.x = -(texbox.w - viewport.x) / 2.f;
texbox.round();
if (!blurredFB.isAllocated())
blurredFB.alloc(viewport.x, viewport.y); // TODO 10 bit
blurredFB.bind();
if (fade)
g_pRenderer->renderTextureMix(texbox, asset->texture, pendingAsset->texture, 1.0,
std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - fade->start).count() / (1000 * crossFadeTime), 0,
transform);
else
g_pRenderer->renderTexture(texbox, asset->texture, 1.0, 0, transform);
if (blurPasses > 0)
g_pRenderer->blurFB(blurredFB,
CRenderer::SBlurParams{.size = blurSize,
.passes = blurPasses,
.noise = noise,
.contrast = contrast,
.brightness = brightness,
.vibrancy = vibrancy,
.vibrancy_darkness = vibrancy_darkness});
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
}
CTexture* tex = blurredFB.isAllocated() ? &blurredFB.m_cTex : &asset->texture;
CBox texbox = {{}, tex->m_vSize};
Vector2D size = tex->m_vSize;
float scaleX = viewport.x / tex->m_vSize.x;
float scaleY = viewport.y / tex->m_vSize.y;
texbox.w *= std::max(scaleX, scaleY); texbox.w *= std::max(scaleX, scaleY);
texbox.h *= std::max(scaleX, scaleY); texbox.h *= std::max(scaleX, scaleY);
@ -200,9 +134,94 @@ bool CBackground::draw(const SRenderData& data) {
else else
texbox.x = -(texbox.w - viewport.x) / 2.f; texbox.x = -(texbox.w - viewport.x) / 2.f;
texbox.round(); texbox.round();
g_pRenderer->renderTexture(texbox, *tex, data.opacity, 0, HYPRUTILS_TRANSFORM_FLIPPED_180);
return fade || data.opacity < 1.0; // actively render during fading if (!fb.isAllocated())
fb.alloc(viewport.x, viewport.y); // TODO 10 bit
fb.bind();
g_pRenderer->renderTexture(texbox, tex, 1.0, 0, transform);
if (blurPasses > 0)
g_pRenderer->blurFB(fb,
CRenderer::SBlurParams{.size = blurSize,
.passes = blurPasses,
.noise = noise,
.contrast = contrast,
.brightness = brightness,
.vibrancy = vibrancy,
.vibrancy_darkness = vibrancy_darkness});
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
}
static CBox getScaledBoxForTexture(const CTexture& tex, const Vector2D& viewport) {
CBox texbox = {{}, tex.m_vSize};
Vector2D size = tex.m_vSize;
float scaleX = viewport.x / tex.m_vSize.x;
float scaleY = viewport.y / tex.m_vSize.y;
texbox.w *= std::max(scaleX, scaleY);
texbox.h *= std::max(scaleX, scaleY);
if (scaleX > scaleY)
texbox.y = -(texbox.h - viewport.y) / 2.f;
else
texbox.x = -(texbox.w - viewport.x) / 2.f;
texbox.round();
return texbox;
}
bool CBackground::draw(const SRenderData& data) {
if (!asset && !resourceID.empty())
asset = g_pRenderer->asyncResourceGatherer->getAssetByID(resourceID);
// path=screenshot -> scAsset = asset
if (!scAsset)
scAsset = (asset && isScreenshot) ? asset : g_pRenderer->asyncResourceGatherer->getAssetByID(scResourceID);
if (!asset || resourceID.empty()) {
// fade in/out with a solid color
if (data.opacity < 1.0 && scAsset) {
const auto SCTEXBOX = getScaledBoxForTexture(scAsset->texture, viewport);
g_pRenderer->renderTexture(SCTEXBOX, scAsset->texture, 1, 0, HYPRUTILS_TRANSFORM_FLIPPED_180);
CHyprColor col = color;
col.a *= data.opacity;
renderRect(col);
return true;
}
renderRect(color);
return !asset && !resourceID.empty(); // resource not ready
}
if (asset->texture.m_iType == TEXTURE_INVALID) {
g_pRenderer->asyncResourceGatherer->unloadAsset(asset);
resourceID = "";
renderRect(color);
return false;
}
if (asset && (blurPasses > 0 || isScreenshot) && (!blurredFB->isAllocated() || firstRender))
renderBlur(asset->texture, *blurredFB);
// For crossfading a new asset
if (pendingAsset && blurPasses > 0 && !pendingBlurredFB->isAllocated())
renderBlur(pendingAsset->texture, *pendingBlurredFB);
const auto& TEX = blurredFB->isAllocated() ? blurredFB->m_cTex : asset->texture;
const auto TEXBOX = getScaledBoxForTexture(TEX, viewport);
if (data.opacity < 1.0 && scAsset)
g_pRenderer->renderTextureMix(TEXBOX, scAsset->texture, TEX, 1.0, data.opacity, 0);
else if (crossFadeProgress->isBeingAnimated()) {
const auto& PENDINGTEX = pendingBlurredFB->isAllocated() ? pendingBlurredFB->m_cTex : pendingAsset->texture;
g_pRenderer->renderTextureMix(TEXBOX, TEX, PENDINGTEX, 1.0, crossFadeProgress->value(), 0);
} else
g_pRenderer->renderTexture(TEXBOX, TEX, 1, 0, HYPRUTILS_TRANSFORM_FLIPPED_180);
return crossFadeProgress->isBeingAnimated() || data.opacity < 1.0;
} }
void CBackground::plantReloadTimer() { void CBackground::plantReloadTimer() {
@ -213,27 +232,6 @@ void CBackground::plantReloadTimer() {
reloadTimer = g_pHyprlock->addTimer(std::chrono::seconds(reloadTime), [REF = m_self](auto, auto) { onReloadTimer(REF); }, nullptr, true); reloadTimer = g_pHyprlock->addTimer(std::chrono::seconds(reloadTime), [REF = m_self](auto, auto) { onReloadTimer(REF); }, nullptr, true);
} }
void CBackground::onCrossFadeTimerUpdate() {
// Animation done: Unload previous asset, deinitialize the fade and pass the asset
if (fade) {
fade->crossFadeTimer.reset();
fade.reset();
}
if (blurPasses <= 0 && !isScreenshot)
blurredFB.release();
asset = pendingAsset;
resourceID = pendingResourceID;
pendingResourceID = "";
pendingAsset = nullptr;
firstRender = true;
g_pHyprlock->renderOutput(outputPort);
}
void CBackground::onReloadTimerUpdate() { void CBackground::onReloadTimerUpdate() {
const std::string OLDPATH = path; const std::string OLDPATH = path;
@ -279,37 +277,37 @@ void CBackground::onReloadTimerUpdate() {
g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request); g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request);
} }
void CBackground::startCrossFadeOrUpdateRender() { void CBackground::startCrossFade() {
auto newAsset = g_pRenderer->asyncResourceGatherer->getAssetByID(pendingResourceID); auto newAsset = g_pRenderer->asyncResourceGatherer->getAssetByID(pendingResourceID);
if (newAsset) { if (newAsset) {
if (newAsset->texture.m_iType == TEXTURE_INVALID) { if (newAsset->texture.m_iType == TEXTURE_INVALID) {
g_pRenderer->asyncResourceGatherer->unloadAsset(newAsset); g_pRenderer->asyncResourceGatherer->unloadAsset(newAsset);
Debug::log(ERR, "New asset had an invalid texture!"); Debug::log(ERR, "New asset had an invalid texture!");
pendingResourceID = "";
} else if (resourceID != pendingResourceID) { } else if (resourceID != pendingResourceID) {
pendingAsset = newAsset; pendingAsset = newAsset;
if (crossFadeTime > 0) { crossFadeProgress->setValueAndWarp(0);
// Start a fade *crossFadeProgress = 1.0;
if (!fade)
fade = makeUnique<SFade>(std::chrono::system_clock::now(), 0, nullptr); crossFadeProgress->setCallbackOnEnd(
else { [REF = m_self](auto) {
// Maybe we where already fading so reset it just in case, but should'nt be happening. if (const auto PSELF = REF.lock()) {
if (fade->crossFadeTimer) { PSELF->asset = PSELF->pendingAsset;
fade->crossFadeTimer->cancel(); PSELF->pendingAsset = nullptr;
fade->crossFadeTimer.reset(); g_pRenderer->asyncResourceGatherer->unloadAsset(PSELF->pendingAsset);
PSELF->resourceID = PSELF->pendingResourceID;
PSELF->pendingResourceID = "";
PSELF->blurredFB->destroyBuffer();
PSELF->blurredFB = std::move(PSELF->pendingBlurredFB);
} }
} },
fade->start = std::chrono::system_clock::now(); true);
fade->a = 0;
fade->crossFadeTimer = g_pHyprlock->renderOutput(outputPort);
g_pHyprlock->addTimer(std::chrono::milliseconds((int)(1000.0 * crossFadeTime)), [REF = m_self](auto, auto) { onCrossFadeTimer(REF); }, nullptr);
} else {
onCrossFadeTimerUpdate();
}
} }
} else if (!pendingResourceID.empty()) { } else if (!pendingResourceID.empty()) {
Debug::log(WARN, "Asset {} not available after the asyncResourceGatherer's callback!", pendingResourceID); Debug::log(WARN, "Asset {} not available after the asyncResourceGatherer's callback!", pendingResourceID);
g_pHyprlock->addTimer(std::chrono::milliseconds(100), [REF = m_self](auto, auto) { onAssetCallback(REF); }, nullptr); g_pHyprlock->addTimer(std::chrono::milliseconds(100), [REF = m_self](auto, auto) { onAssetCallback(REF); }, nullptr);
} }
g_pHyprlock->renderOutput(outputPort);
} }

View file

@ -1,6 +1,7 @@
#pragma once #pragma once
#include "IWidget.hpp" #include "IWidget.hpp"
#include "../../helpers/AnimatedVariable.hpp"
#include "../../helpers/Color.hpp" #include "../../helpers/Color.hpp"
#include "../../helpers/Math.hpp" #include "../../helpers/Math.hpp"
#include "../../core/Timer.hpp" #include "../../core/Timer.hpp"
@ -16,15 +17,9 @@
struct SPreloadedAsset; struct SPreloadedAsset;
class COutput; class COutput;
struct SFade {
std::chrono::system_clock::time_point start;
float a = 0;
std::shared_ptr<CTimer> crossFadeTimer = nullptr;
};
class CBackground : public IWidget { class CBackground : public IWidget {
public: public:
CBackground() = default; CBackground();
~CBackground(); ~CBackground();
void registerSelf(const SP<CBackground>& self); void registerSelf(const SP<CBackground>& self);
@ -36,16 +31,18 @@ class CBackground : public IWidget {
void renderRect(CHyprColor color); void renderRect(CHyprColor color);
void renderBlur(const CTexture& text, CFramebuffer& fb);
void onReloadTimerUpdate(); void onReloadTimerUpdate();
void onCrossFadeTimerUpdate();
void plantReloadTimer(); void plantReloadTimer();
void startCrossFadeOrUpdateRender(); void startCrossFade();
private: private:
WP<CBackground> m_self; WP<CBackground> m_self;
// if needed // if needed
CFramebuffer blurredFB; UP<CFramebuffer> blurredFB;
UP<CFramebuffer> pendingBlurredFB;
int blurSize = 10; int blurSize = 10;
int blurPasses = 3; int blurPasses = 3;
@ -61,18 +58,18 @@ class CBackground : public IWidget {
Hyprutils::Math::eTransform transform; Hyprutils::Math::eTransform transform;
std::string resourceID; std::string resourceID;
std::string scResourceID;
std::string pendingResourceID; std::string pendingResourceID;
float crossFadeTime = -1.0; PHLANIMVAR<float> crossFadeProgress;
CHyprColor color; CHyprColor color;
SPreloadedAsset* asset = nullptr; SPreloadedAsset* asset = nullptr;
bool isScreenshot = false; SPreloadedAsset* scAsset = nullptr;
SPreloadedAsset* pendingAsset = nullptr; SPreloadedAsset* pendingAsset = nullptr;
bool isScreenshot = false;
bool firstRender = true; bool firstRender = true;
UP<SFade> fade;
int reloadTime = -1; int reloadTime = -1;
std::string reloadCommand; std::string reloadCommand;
CAsyncResourceGatherer::SPreloadRequest request; CAsyncResourceGatherer::SPreloadRequest request;

View file

@ -125,7 +125,7 @@ void CImage::reset() {
if (g_pHyprlock->m_bTerminate) if (g_pHyprlock->m_bTerminate)
return; return;
imageFB.release(); imageFB.destroyBuffer();
if (asset && reloadTime > -1) // Don't unload asset if it's a static image if (asset && reloadTime > -1) // Don't unload asset if it's a static image
g_pRenderer->asyncResourceGatherer->unloadAsset(asset); g_pRenderer->asyncResourceGatherer->unloadAsset(asset);
@ -217,7 +217,7 @@ void CImage::renderUpdate() {
g_pRenderer->asyncResourceGatherer->unloadAsset(newAsset); g_pRenderer->asyncResourceGatherer->unloadAsset(newAsset);
} else if (resourceID != pendingResourceID) { } else if (resourceID != pendingResourceID) {
g_pRenderer->asyncResourceGatherer->unloadAsset(asset); g_pRenderer->asyncResourceGatherer->unloadAsset(asset);
imageFB.release(); imageFB.destroyBuffer();
asset = newAsset; asset = newAsset;
resourceID = pendingResourceID; resourceID = pendingResourceID;