From ed8ff11641206cb4b334fe036b3e086da4c4bcac Mon Sep 17 00:00:00 2001 From: Maximilian Seidler Date: Sun, 21 Sep 2025 12:28:21 +0200 Subject: [PATCH 01/19] core: use Hyprgraphics::CAsyncResourceGatherer --- flake.lock | 35 ++-- src/core/hyprlock.cpp | 4 +- src/renderer/AsyncResourceGatherer.cpp | 30 +-- src/renderer/AsyncResourceGatherer.hpp | 8 +- src/renderer/AsyncResourceManager.cpp | 212 ++++++++++++++++++++ src/renderer/AsyncResourceManager.hpp | 52 +++++ src/renderer/TextCmdResource.cpp | 31 +++ src/renderer/TextCmdResource.hpp | 15 ++ src/renderer/widgets/Background.cpp | 2 +- src/renderer/widgets/Background.hpp | 70 +++---- src/renderer/widgets/IWidget.cpp | 11 + src/renderer/widgets/IWidget.hpp | 4 +- src/renderer/widgets/Image.cpp | 49 ++--- src/renderer/widgets/Image.hpp | 46 ++--- src/renderer/widgets/Label.cpp | 55 +++-- src/renderer/widgets/Label.hpp | 39 ++-- src/renderer/widgets/PasswordInputField.cpp | 82 ++++---- src/renderer/widgets/PasswordInputField.hpp | 26 +-- 18 files changed, 539 insertions(+), 232 deletions(-) create mode 100644 src/renderer/AsyncResourceManager.cpp create mode 100644 src/renderer/AsyncResourceManager.hpp create mode 100644 src/renderer/TextCmdResource.cpp create mode 100644 src/renderer/TextCmdResource.hpp diff --git a/flake.lock b/flake.lock index 523db0c8..836f3a20 100644 --- a/flake.lock +++ b/flake.lock @@ -13,11 +13,11 @@ ] }, "locked": { - "lastModified": 1750621377, - "narHash": "sha256-8u6b5oAdX0rCuoR8wFenajBRmI+mzbpNig6hSCuWUzE=", + "lastModified": 1758112956, + "narHash": "sha256-noG/SglFnGs+pdND/0XXu5vzjpgE5SwOrCM/6pa9H+Y=", "owner": "hyprwm", "repo": "hyprgraphics", - "rev": "b3d628d01693fb9bb0a6690cd4e7b80abda04310", + "rev": "b86c4d9ed353073e764fef286423c5cc6fb9318b", "type": "github" }, "original": { @@ -39,11 +39,11 @@ ] }, "locked": { - "lastModified": 1750371198, - "narHash": "sha256-/iuJ1paQOBoSLqHflRNNGyroqfF/yvPNurxzcCT0cAE=", + "lastModified": 1756810301, + "narHash": "sha256-wgZ3VW4VVtjK5dr0EiK9zKdJ/SOqGIBXVG85C3LVxQA=", "owner": "hyprwm", "repo": "hyprlang", - "rev": "cee01452bca58d6cadb3224e21e370de8bc20f0b", + "rev": "3d63fb4a42c819f198deabd18c0c2c1ded1de931", "type": "github" }, "original": { @@ -62,11 +62,11 @@ ] }, "locked": { - "lastModified": 1751061882, - "narHash": "sha256-g9n8Vrbx+2JYM170P9BbvGHN39Wlkr4U+V2WLHQsXL8=", + "lastModified": 1756117388, + "narHash": "sha256-oRDel6pNl/T2tI+nc/USU9ZP9w08dxtl7hiZxa0C/Wc=", "owner": "hyprwm", "repo": "hyprutils", - "rev": "4737241eaf8a1e51671a2a088518071f9a265cf4", + "rev": "b2ae3204845f5f2f79b4703b441252d8ad2ecfd0", "type": "github" }, "original": { @@ -85,11 +85,11 @@ ] }, "locked": { - "lastModified": 1750371869, - "narHash": "sha256-lGk4gLjgZQ/rndUkzmPYcgbHr8gKU5u71vyrjnwfpB4=", + "lastModified": 1755184602, + "narHash": "sha256-RCBQN8xuADB0LEgaKbfRqwm6CdyopE1xIEhNc67FAbw=", "owner": "hyprwm", "repo": "hyprwayland-scanner", - "rev": "aa38edd6e3e277ae6a97ea83a69261a5c3aab9fd", + "rev": "b3b0f1f40ae09d4447c20608e5a4faf8bf3c492d", "type": "github" }, "original": { @@ -100,12 +100,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1751011381, - "narHash": "sha256-krGXKxvkBhnrSC/kGBmg5MyupUUT5R6IBCLEzx9jhMM=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "30e2e2857ba47844aa71991daa6ed1fc678bcbb7", - "type": "github" + "lastModified": 1757745802, + "narHash": "sha256-hLEO2TPj55KcUFUU1vgtHE9UEIOjRcH/4QbmfHNF820=", + "path": "/nix/store/lvwgkdy9nrki8qcrdsqnpfrk7562m1dc-source", + "rev": "c23193b943c6c689d70ee98ce3128239ed9e32d1", + "type": "path" }, "original": { "owner": "NixOS", diff --git a/src/core/hyprlock.cpp b/src/core/hyprlock.cpp index 87e33d1f..c3c20331 100644 --- a/src/core/hyprlock.cpp +++ b/src/core/hyprlock.cpp @@ -3,6 +3,7 @@ #include "../config/ConfigManager.hpp" #include "../renderer/Renderer.hpp" #include "../renderer/AsyncResourceGatherer.hpp" +#include "../renderer/AsyncResourceManager.hpp" #include "../auth/Auth.hpp" #include "../auth/Fingerprint.hpp" #include "Egl.hpp" @@ -331,7 +332,8 @@ void CHyprlock::run() { wl_display_roundtrip(m_sWaylandState.display); g_pRenderer = makeUnique(); - g_pAsyncResourceGatherer = makeUnique(); + g_pAsyncResourceGatherer = makeUnique(); + g_asyncResourceManager = makeUnique(); g_pAuth = makeUnique(); g_pAuth->start(); diff --git a/src/renderer/AsyncResourceGatherer.cpp b/src/renderer/AsyncResourceGatherer.cpp index 943e3e6d..df80d44b 100644 --- a/src/renderer/AsyncResourceGatherer.cpp +++ b/src/renderer/AsyncResourceGatherer.cpp @@ -15,7 +15,7 @@ using namespace Hyprgraphics; using namespace Hyprutils::OS; -CAsyncResourceGatherer::CAsyncResourceGatherer() { +CAsyncResourceGatherer_::CAsyncResourceGatherer_() { if (g_pHyprlock->getScreencopy()) enqueueScreencopyFrames(); @@ -27,12 +27,12 @@ CAsyncResourceGatherer::CAsyncResourceGatherer() { Debug::log(ERR, "Failed to create eventfd: {}", strerror(errno)); } -CAsyncResourceGatherer::~CAsyncResourceGatherer() { +CAsyncResourceGatherer_::~CAsyncResourceGatherer_() { notify(); await(); } -void CAsyncResourceGatherer::enqueueScreencopyFrames() { +void CAsyncResourceGatherer_::enqueueScreencopyFrames() { if (g_pHyprlock->m_vOutputs.empty()) return; @@ -59,7 +59,7 @@ void CAsyncResourceGatherer::enqueueScreencopyFrames() { } } -ASP CAsyncResourceGatherer::getAssetByID(const std::string& id) { +ASP CAsyncResourceGatherer_::getAssetByID(const std::string& id) { if (id.contains(CScreencopyFrame::RESOURCEIDPREFIX)) { for (auto& frame : scframes) { if (id == frame->m_resourceID) @@ -94,7 +94,7 @@ static SP getCairoSurfaceFromImageFile(const std::filesystem::pat return image.cairoSurface(); } -void CAsyncResourceGatherer::gather() { +void CAsyncResourceGatherer_::gather() { const auto CWIDGETS = g_pConfigManager->getWidgetConfigs(); g_pEGL->makeCurrent(nullptr); @@ -123,8 +123,8 @@ void CAsyncResourceGatherer::gather() { std::string id = (c.type == "background" ? std::string{"background:"} : std::string{"image:"}) + path; // render the image directly, since we are in a seperate thread - CAsyncResourceGatherer::SPreloadRequest rq; - rq.type = CAsyncResourceGatherer::TARGET_IMAGE; + CAsyncResourceGatherer_::SPreloadRequest rq; + rq.type = CAsyncResourceGatherer_::TARGET_IMAGE; rq.asset = path; rq.id = id; @@ -147,7 +147,7 @@ void CAsyncResourceGatherer::gather() { eventfd_write(gatheredEventfd.get(), 1); } -bool CAsyncResourceGatherer::apply() { +bool CAsyncResourceGatherer_::apply() { preloadTargetsMutex.lock(); if (preloadTargets.empty()) { @@ -198,7 +198,7 @@ bool CAsyncResourceGatherer::apply() { return true; } -void CAsyncResourceGatherer::renderImage(const SPreloadRequest& rq) { +void CAsyncResourceGatherer_::renderImage(const SPreloadRequest& rq) { SPreloadTarget target; target.type = TARGET_IMAGE; target.id = rq.id; @@ -223,7 +223,7 @@ void CAsyncResourceGatherer::renderImage(const SPreloadRequest& rq) { preloadTargets.push_back(target); } -void CAsyncResourceGatherer::renderText(const SPreloadRequest& rq) { +void CAsyncResourceGatherer_::renderText(const SPreloadRequest& rq) { SPreloadTarget target; target.type = TARGET_IMAGE; /* text is just an image lol */ target.id = rq.id; @@ -317,7 +317,7 @@ void CAsyncResourceGatherer::renderText(const SPreloadRequest& rq) { preloadTargets.push_back(target); } -void CAsyncResourceGatherer::asyncAssetSpinLock() { +void CAsyncResourceGatherer_::asyncAssetSpinLock() { while (!g_pHyprlock->m_bTerminate) { std::unique_lock lk(asyncLoopState.requestsMutex); @@ -356,7 +356,7 @@ void CAsyncResourceGatherer::asyncAssetSpinLock() { } } -void CAsyncResourceGatherer::requestAsyncAssetPreload(const SPreloadRequest& request) { +void CAsyncResourceGatherer_::requestAsyncAssetPreload(const SPreloadRequest& request) { Debug::log(TRACE, "Requesting label resource {}", request.id); std::lock_guard lg(asyncLoopState.requestsMutex); @@ -365,18 +365,18 @@ void CAsyncResourceGatherer::requestAsyncAssetPreload(const SPreloadRequest& req asyncLoopState.requestsCV.notify_all(); } -void CAsyncResourceGatherer::unloadAsset(ASP asset) { +void CAsyncResourceGatherer_::unloadAsset(ASP asset) { std::erase_if(assets, [asset](const auto& a) { return a.second == asset; }); } -void CAsyncResourceGatherer::notify() { +void CAsyncResourceGatherer_::notify() { std::lock_guard lg(asyncLoopState.requestsMutex); asyncLoopState.requests.clear(); asyncLoopState.pending = true; asyncLoopState.requestsCV.notify_all(); } -void CAsyncResourceGatherer::await() { +void CAsyncResourceGatherer_::await() { if (initialGatherThread.joinable()) initialGatherThread.join(); if (asyncLoopThread.joinable()) diff --git a/src/renderer/AsyncResourceGatherer.hpp b/src/renderer/AsyncResourceGatherer.hpp index e5616df3..5f9339b3 100644 --- a/src/renderer/AsyncResourceGatherer.hpp +++ b/src/renderer/AsyncResourceGatherer.hpp @@ -12,10 +12,10 @@ #include #include -class CAsyncResourceGatherer { +class CAsyncResourceGatherer_ { public: - CAsyncResourceGatherer(); - ~CAsyncResourceGatherer(); + CAsyncResourceGatherer_(); + ~CAsyncResourceGatherer_(); std::atomic gathered = false; Hyprutils::OS::CFileDescriptor gatheredEventfd; @@ -90,4 +90,4 @@ class CAsyncResourceGatherer { void enqueueScreencopyFrames(); }; -inline UP g_pAsyncResourceGatherer; +inline UP g_pAsyncResourceGatherer; diff --git a/src/renderer/AsyncResourceManager.cpp b/src/renderer/AsyncResourceManager.cpp new file mode 100644 index 00000000..7ac1a765 --- /dev/null +++ b/src/renderer/AsyncResourceManager.cpp @@ -0,0 +1,212 @@ +#include "AsyncResourceManager.hpp" + +#include "./TextCmdResource.hpp" +#include "../helpers/Log.hpp" +#include "../helpers/MiscFunctions.hpp" +#include "../core/hyprlock.hpp" + +#include + +using namespace Hyprgraphics; + +template <> +struct std::hash { + std::size_t operator()(CTextResource::STextResourceData const& s) const noexcept { + const auto H1 = std::hash{}(s.text); + const auto H2 = std::hash{}(s.color.asRgb().r); + const auto H3 = std::hash{}(s.color.asRgb().g); + const auto H4 = std::hash{}(s.color.asRgb().b); + + return H1 ^ H2 ^ H3 ^ H4; + } +}; + +size_t CAsyncResourceManager::requestText(const CTextResource::STextResourceData& request, std::function callback) { + const auto RESOURCEID = std::hash{}(request); + if (m_textures.contains(RESOURCEID)) { + Debug::log(TRACE, "Text resource text:\"{}\" (resourceID: {}) already requested, incrementing refcount!", request.text, RESOURCEID); + m_textures[RESOURCEID].refs++; + return RESOURCEID; + } else { + m_textures[RESOURCEID] = { + .texture = nullptr, + .refs = 1, + }; + } + + auto resource = makeAtomicShared(CTextResource::STextResourceData{request}); + CAtomicSharedPointer resourceGeneric{resource}; + + m_gatherer.enqueue(resourceGeneric); + + m_resourcesMutex.lock(); + if (m_resources.contains(RESOURCEID)) { + m_resourcesMutex.unlock(); + return RESOURCEID; + } + m_resources[RESOURCEID] = std::move(resourceGeneric); + m_resourcesMutex.unlock(); + + resource->m_events.finished.listenStatic([RESOURCEID, callback]() { + g_pHyprlock->addTimer( + std::chrono::milliseconds(0), + [RESOURCEID, callback](auto, auto) { + g_asyncResourceManager->resourceToTexture(RESOURCEID); + callback(); + }, + nullptr); + }); + + Debug::log(TRACE, "Enqueued text:\"{}\" (resourceID: {}) successfully.", request.text, RESOURCEID); + + return RESOURCEID; +} + +size_t CAsyncResourceManager::requestTextCmd(const CTextResource::STextResourceData& request, std::function callback) { + const auto RESOURCEID = std::hash{}(request); + if (m_textures.contains(RESOURCEID)) { + Debug::log(TRACE, "Text cmd resource text:\"{}\" (resourceID: {}) already requested, incrementing refcount!", request.text, RESOURCEID); + m_textures[RESOURCEID].refs++; + return RESOURCEID; + } else { + m_textures[RESOURCEID] = { + .texture = nullptr, + .refs = 1, + }; + } + + auto resource = makeAtomicShared(CTextResource::STextResourceData{request}); + CAtomicSharedPointer resourceGeneric{resource}; + + m_gatherer.enqueue(resourceGeneric); + + m_resourcesMutex.lock(); + if (m_resources.contains(RESOURCEID)) + Debug::log(ERR, "Resource already enqueued! This is a bug."); + + m_resources[RESOURCEID] = std::move(resourceGeneric); + m_resourcesMutex.unlock(); + + resource->m_events.finished.listenStatic([RESOURCEID, callback]() { + g_pHyprlock->addTimer( + std::chrono::milliseconds(0), + [RESOURCEID, callback](auto, auto) { + g_asyncResourceManager->resourceToTexture(RESOURCEID); + callback(); + }, + nullptr); + }); + + Debug::log(TRACE, "Enqueued text cmd:\"{}\" (resourceID: {}) successfully.", request.text, RESOURCEID); + + return RESOURCEID; +} + +size_t CAsyncResourceManager::requestImage(const std::string& path, std::function callback) { + const auto RESOURCEID = std::hash{}(path); + if (m_textures.contains(RESOURCEID)) { + Debug::log(TRACE, "Image resource image:\"{}\" (resourceID: {}) already requested, incrementing refcount!", path, RESOURCEID); + m_textures[RESOURCEID].refs++; + return RESOURCEID; + } else { + m_textures[RESOURCEID] = { + .texture = nullptr, + .refs = 1, + }; + } + + auto resource = makeAtomicShared(absolutePath(path, "")); + CAtomicSharedPointer resourceGeneric{resource}; + + m_gatherer.enqueue(resourceGeneric); + + m_resourcesMutex.lock(); + if (m_resources.contains(RESOURCEID)) + Debug::log(ERR, "Resource already enqueued! This is a bug."); + + m_resources[RESOURCEID] = std::move(resourceGeneric); + m_resourcesMutex.unlock(); + + resource->m_events.finished.listenStatic([RESOURCEID, callback]() { + g_pHyprlock->addTimer( + std::chrono::milliseconds(0), + [RESOURCEID, callback](auto, auto) { + g_asyncResourceManager->resourceToTexture(RESOURCEID); + callback(); + }, + nullptr); + }); + + Debug::log(TRACE, "Enqueued image:\"{}\" (resourceID: {}) successfully.", path, RESOURCEID); + + return RESOURCEID; +} + +void CAsyncResourceManager::unload(ASP texture) { + auto preload = std::ranges::find_if(m_textures, [texture](const auto& a) { return a.second.texture == texture; }); + if (preload == m_textures.end()) + return; + + preload->second.refs--; + if (preload->second.refs == 0) + m_textures.erase(preload->first); +} + +void CAsyncResourceManager::unloadById(size_t id) { + if (!m_textures.contains(id)) + return; + + m_textures.erase(id); + m_textures[id].refs--; + if (m_textures[id].refs == 0) + m_textures.erase(id); +} + +ASP CAsyncResourceManager::getAssetById(size_t id) { + if (!m_textures.contains(id)) + return nullptr; + + return m_textures[id].texture; +} + +void CAsyncResourceManager::resourceToTexture(size_t id) { + if (!m_resources.contains(id)) + return; + + m_resourcesMutex.lock(); + const auto RESOURCE = m_resources[id]; + m_resources.erase(id); + m_resourcesMutex.unlock(); + + if (!RESOURCE || !RESOURCE->m_asset.cairoSurface) + return; + + Debug::log(TRACE, "Resource to texture id:{}", id); + + const auto texture = makeAtomicShared(); + + const cairo_status_t SURFACESTATUS = (cairo_status_t)RESOURCE->m_asset.cairoSurface->status(); + const auto CAIROFORMAT = cairo_image_surface_get_format(RESOURCE->m_asset.cairoSurface->cairo()); + const GLint glIFormat = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_RGB32F : GL_RGBA; + const GLint glFormat = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_RGB : GL_RGBA; + const GLint glType = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_FLOAT : GL_UNSIGNED_BYTE; + + if (SURFACESTATUS != CAIRO_STATUS_SUCCESS) { + Debug::log(ERR, "RESOURCE {} invalid ({})", id, cairo_status_to_string(SURFACESTATUS)); + texture->m_iType = TEXTURE_INVALID; + } + + texture->m_vSize = RESOURCE->m_asset.pixelSize; + texture->allocate(); + + glBindTexture(GL_TEXTURE_2D, texture->m_iTexID); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + if (CAIROFORMAT != CAIRO_FORMAT_RGB96F) { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_BLUE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED); + } + glTexImage2D(GL_TEXTURE_2D, 0, glIFormat, texture->m_vSize.x, texture->m_vSize.y, 0, glFormat, glType, RESOURCE->m_asset.cairoSurface->data()); + + m_textures[id].texture = texture; +} diff --git a/src/renderer/AsyncResourceManager.hpp b/src/renderer/AsyncResourceManager.hpp new file mode 100644 index 00000000..a1542a4e --- /dev/null +++ b/src/renderer/AsyncResourceManager.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include "../defines.hpp" +#include "./Texture.hpp" + +#include +#include +#include +#include + +class CAsyncResourceManager { + public: + struct SPreloadedTexture { + ASP texture; + size_t refs = 0; + }; + + CAsyncResourceManager() = default; + ~CAsyncResourceManager() = default; + + // requesting an asset returns a unique id used to retrieve it later + size_t requestText(const CTextResource::STextResourceData& request, std::function callback); + // same as requestText but substitute the text with what launching sh -c request.text returns + size_t requestTextCmd(const CTextResource::STextResourceData& request, std::function callback); + size_t requestImage(const std::string& path, std::function callback); + + ASP getAssetById(size_t id); + + void unload(ASP resource); + void unloadById(size_t id); + + private: + bool apply(); + void resourceToTexture(size_t id); + + //std::mutex m_wakeupMutex; + //std::condition_variable m_wakeup; + + bool m_exit = false; + + int m_loadedAssets = 0; + + // shared between threads + std::mutex m_resourcesMutex; + std::unordered_map> m_resources; + // not shared between threads + std::unordered_map m_textures; + + Hyprgraphics::CAsyncResourceGatherer m_gatherer; +}; + +inline UP g_asyncResourceManager; diff --git a/src/renderer/TextCmdResource.cpp b/src/renderer/TextCmdResource.cpp new file mode 100644 index 00000000..75fa1028 --- /dev/null +++ b/src/renderer/TextCmdResource.cpp @@ -0,0 +1,31 @@ + +#include "TextCmdResource.hpp" + +#include "../config/ConfigManager.hpp" +#include "../helpers/MiscFunctions.hpp" +#include + +using namespace Hyprgraphics; + +CTextCmdResource::CTextCmdResource(CTextResource::STextResourceData&& data) : m_data(std::move(data)) { + ; +} + +void CTextCmdResource::render() { + static const auto TRIM = g_pConfigManager->getValue("general:text_trim"); + + CTextResource::STextResourceData textData = m_data; + + textData.text = spawnSync(m_data.text); + + if (*TRIM) { + textData.text.erase(0, textData.text.find_first_not_of(" \n\r\t")); + textData.text.erase(textData.text.find_last_not_of(" \n\r\t") + 1); + } + + Hyprgraphics::CTextResource textResource(std::move(textData)); + + textResource.render(); + + std::swap(m_asset, textResource.m_asset); +} diff --git a/src/renderer/TextCmdResource.hpp b/src/renderer/TextCmdResource.hpp new file mode 100644 index 00000000..39a31857 --- /dev/null +++ b/src/renderer/TextCmdResource.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + +class CTextCmdResource : public Hyprgraphics::IAsyncResource { + public: + CTextCmdResource(Hyprgraphics::CTextResource::STextResourceData&& data); + virtual ~CTextCmdResource() = default; + + virtual void render(); + + private: + Hyprgraphics::CTextResource::STextResourceData m_data; +}; diff --git a/src/renderer/widgets/Background.cpp b/src/renderer/widgets/Background.cpp index 3fb61bca..d8be6d29 100644 --- a/src/renderer/widgets/Background.cpp +++ b/src/renderer/widgets/Background.cpp @@ -302,7 +302,7 @@ void CBackground::onReloadTimerUpdate() { 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.type = CAsyncResourceGatherer_::eTargetType::TARGET_IMAGE; request.callback = [REF = m_self]() { onAssetCallback(REF); }; diff --git a/src/renderer/widgets/Background.hpp b/src/renderer/widgets/Background.hpp index 85270cdf..51477d95 100644 --- a/src/renderer/widgets/Background.hpp +++ b/src/renderer/widgets/Background.hpp @@ -50,39 +50,39 @@ class CBackground : public IWidget { AWP m_self; // if needed - UP blurredFB; - UP pendingBlurredFB; - UP transformedScFB; - - 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 outputPort; - Hyprutils::Math::eTransform transform; - - std::string resourceID; - std::string scResourceID; - std::string pendingResourceID; - - PHLANIMVAR crossFadeProgress; - - CHyprColor color; - ASP asset = nullptr; - ASP scAsset = nullptr; - ASP pendingAsset = nullptr; - bool isScreenshot = false; - bool firstRender = true; - - int reloadTime = -1; - std::string reloadCommand; - CAsyncResourceGatherer::SPreloadRequest request; - ASP reloadTimer; - std::filesystem::file_time_type modificationTime; + UP blurredFB; + UP pendingBlurredFB; + UP transformedScFB; + + 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 outputPort; + Hyprutils::Math::eTransform transform; + + std::string resourceID; + std::string scResourceID; + std::string pendingResourceID; + + PHLANIMVAR crossFadeProgress; + + CHyprColor color; + ASP asset = nullptr; + ASP scAsset = nullptr; + ASP pendingAsset = nullptr; + bool isScreenshot = false; + bool firstRender = true; + + int reloadTime = -1; + std::string reloadCommand; + CAsyncResourceGatherer_::SPreloadRequest request; + ASP reloadTimer; + std::filesystem::file_time_type modificationTime; }; diff --git a/src/renderer/widgets/IWidget.cpp b/src/renderer/widgets/IWidget.cpp index 5fe90b86..c91ab156 100644 --- a/src/renderer/widgets/IWidget.cpp +++ b/src/renderer/widgets/IWidget.cpp @@ -3,6 +3,7 @@ #include "../../core/hyprlock.hpp" #include "../../auth/Auth.hpp" #include +#include #include #include #include @@ -76,6 +77,16 @@ int IWidget::roundingForBorderBox(const CBox& borderBox, int roundingConfig, int return std::clamp(roundingConfig + thickness, 0, MINHALFBORDER); } +Hyprgraphics::CTextResource::eTextAlignmentMode IWidget::parseTextAlignment(const std::string& alignment) { + Hyprgraphics::CTextResource::eTextAlignmentMode align = Hyprgraphics::CTextResource::TEXT_ALIGN_LEFT; + if (alignment == "center") + align = Hyprgraphics::CTextResource::TEXT_ALIGN_CENTER; + else if (alignment == "right") + align = Hyprgraphics::CTextResource::TEXT_ALIGN_RIGHT; + + return align; +} + static void replaceAllAttempts(std::string& str) { const size_t ATTEMPTS = g_pAuth->getFailedAttempts(); diff --git a/src/renderer/widgets/IWidget.hpp b/src/renderer/widgets/IWidget.hpp index 2ef45b38..50a1d134 100644 --- a/src/renderer/widgets/IWidget.hpp +++ b/src/renderer/widgets/IWidget.hpp @@ -2,6 +2,7 @@ #include "../../defines.hpp" #include "../../helpers/Math.hpp" +#include #include #include #include @@ -23,8 +24,9 @@ class IWidget { const double& ang = 0); static int roundingForBox(const CBox& box, int roundingConfig); static int roundingForBorderBox(const CBox& borderBox, int roundingConfig, int thickness); + static Hyprgraphics::CTextResource::eTextAlignmentMode parseTextAlignment(const std::string& alignment); - virtual CBox getBoundingBoxWl() const { + virtual CBox getBoundingBoxWl() const { return CBox(); }; virtual void onClick(uint32_t button, bool down, const Vector2D& pos) {} diff --git a/src/renderer/widgets/Image.cpp b/src/renderer/widgets/Image.cpp index 874fc1c2..1d8ebe19 100644 --- a/src/renderer/widgets/Image.cpp +++ b/src/renderer/widgets/Image.cpp @@ -4,6 +4,7 @@ #include "../../helpers/Log.hpp" #include "../../helpers/MiscFunctions.hpp" #include "../../config/ConfigDataValues.hpp" +#include "src/renderer/AsyncResourceManager.hpp" #include #include #include @@ -56,16 +57,10 @@ void CImage::onTimerUpdate() { return; } - if (!pendingResourceID.empty()) + if (pendingResourceID > 0) return; - request.id = std::string{"image:"} + 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 = [REF = m_self]() { onAssetCallback(REF); }; - - g_pAsyncResourceGatherer->requestAsyncAssetPreload(request); + pendingResourceID = g_asyncResourceManager->requestImage(path, [REF = m_self]() { onAssetCallback(REF); }); } void CImage::plantTimer() { @@ -104,7 +99,7 @@ void CImage::configure(const std::unordered_map& props, c RASSERT(false, "Missing propperty for CImage: {}", e.what()); // } - resourceID = "image:" + path; + resourceID = g_asyncResourceManager->requestImage(path, [REF = m_self]() { onAssetCallback(REF); }); angle = angle * M_PI / 180.0; if (reloadTime > -1) { @@ -128,27 +123,27 @@ void CImage::reset() { imageFB.destroyBuffer(); if (asset && reloadTime > -1) // Don't unload asset if it's a static image - g_pAsyncResourceGatherer->unloadAsset(asset); + g_asyncResourceManager->unload(asset); asset = nullptr; - pendingResourceID = ""; - resourceID = ""; + pendingResourceID = 0; + resourceID = 0; } bool CImage::draw(const SRenderData& data) { - if (resourceID.empty()) + if (resourceID == 0) return false; if (!asset) - asset = g_pAsyncResourceGatherer->getAssetByID(resourceID); + asset = g_asyncResourceManager->getAssetById(resourceID); if (!asset) return true; - if (asset->texture.m_iType == TEXTURE_INVALID) { - g_pAsyncResourceGatherer->unloadAsset(asset); - resourceID = ""; + if (asset->m_iType == TEXTURE_INVALID) { + g_asyncResourceManager->unload(asset); + resourceID = 0; return false; } @@ -156,7 +151,7 @@ bool CImage::draw(const SRenderData& data) { const Vector2D IMAGEPOS = {border, border}; const Vector2D BORDERPOS = {0.0, 0.0}; - const Vector2D TEXSIZE = asset->texture.m_vSize; + const Vector2D TEXSIZE = asset->m_vSize; const float SCALEX = size / TEXSIZE.x; const float SCALEY = size / TEXSIZE.y; @@ -184,7 +179,7 @@ bool CImage::draw(const SRenderData& data) { g_pRenderer->renderBorder(borderBox, color, border, BORDERROUND, 1.0); texbox.round(); - g_pRenderer->renderTexture(texbox, asset->texture, 1.0, ROUND, HYPRUTILS_TRANSFORM_NORMAL); + g_pRenderer->renderTexture(texbox, *asset, 1.0, ROUND, HYPRUTILS_TRANSFORM_NORMAL); g_pRenderer->popFb(); } @@ -211,23 +206,21 @@ bool CImage::draw(const SRenderData& data) { } void CImage::renderUpdate() { - auto newAsset = g_pAsyncResourceGatherer->getAssetByID(pendingResourceID); + auto newAsset = g_asyncResourceManager->getAssetById(pendingResourceID); if (newAsset) { - if (newAsset->texture.m_iType == TEXTURE_INVALID) { - g_pAsyncResourceGatherer->unloadAsset(newAsset); + if (newAsset->m_iType == TEXTURE_INVALID) { + g_asyncResourceManager->unload(newAsset); } else if (resourceID != pendingResourceID) { - g_pAsyncResourceGatherer->unloadAsset(asset); + g_asyncResourceManager->unload(asset); imageFB.destroyBuffer(); asset = newAsset; resourceID = pendingResourceID; firstRender = true; } - pendingResourceID = ""; - } else if (!pendingResourceID.empty()) { - Debug::log(WARN, "Asset {} not available after the asyncResourceGatherer's callback!", pendingResourceID); - pendingResourceID = ""; - } else if (!pendingResourceID.empty()) { + pendingResourceID = 0; + + } else if (pendingResourceID > 0) { 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); diff --git a/src/renderer/widgets/Image.hpp b/src/renderer/widgets/Image.hpp index 019a2706..f517fa0b 100644 --- a/src/renderer/widgets/Image.hpp +++ b/src/renderer/widgets/Image.hpp @@ -36,35 +36,35 @@ class CImage : public IWidget { void plantTimer(); private: - AWP m_self; + AWP m_self; - CFramebuffer imageFB; + CFramebuffer imageFB; - int size; - int rounding; - double border; - double angle; - CGradientValueData color; - Vector2D pos; - Vector2D configPos; + int size; + int rounding; + double border; + double angle; + CGradientValueData color; + Vector2D pos; + Vector2D configPos; - std::string halign, valign, path; + std::string halign, valign, path; - bool firstRender = true; + bool firstRender = true; - int reloadTime; - std::string reloadCommand; - std::string onclickCommand; + int reloadTime; + std::string reloadCommand; + std::string onclickCommand; - std::filesystem::file_time_type modificationTime; - ASP imageTimer; - CAsyncResourceGatherer::SPreloadRequest request; + std::filesystem::file_time_type modificationTime; + ASP imageTimer; + CAsyncResourceGatherer_::SPreloadRequest request; - Vector2D viewport; - std::string stringPort; + Vector2D viewport; + std::string stringPort; - std::string resourceID; - std::string pendingResourceID; // if reloading image - ASP asset = nullptr; - CShadowable shadow; + size_t resourceID; + size_t pendingResourceID; // if reloading image + ASP asset = nullptr; + CShadowable shadow; }; diff --git a/src/renderer/widgets/Label.cpp b/src/renderer/widgets/Label.cpp index 755ee77e..049e1873 100644 --- a/src/renderer/widgets/Label.cpp +++ b/src/renderer/widgets/Label.cpp @@ -1,5 +1,6 @@ #include "Label.hpp" #include "../Renderer.hpp" +#include "../AsyncResourceManager.hpp" #include "../../helpers/Log.hpp" #include "../../core/hyprlock.hpp" #include "../../helpers/Color.hpp" @@ -42,19 +43,15 @@ void CLabel::onTimerUpdate() { if (label.formatted == oldFormatted && !label.alwaysUpdate) return; - if (!pendingResourceID.empty()) { + if (pendingResourceID > 0) { Debug::log(WARN, "Trying to update label, but resource {} is still pending! Skipping update.", pendingResourceID); return; } // request new - request.id = getUniqueResourceId(); - pendingResourceID = request.id; - request.asset = label.formatted; - - request.callback = [REF = m_self]() { onAssetCallback(REF); }; - - g_pAsyncResourceGatherer->requestAsyncAssetPreload(request); + request.text = label.formatted; + pendingResourceID = (label.cmd) ? g_asyncResourceManager->requestTextCmd(request, [REF = m_self]() { onAssetCallback(REF); }) : + g_asyncResourceManager->requestText(request, [REF = m_self]() { onAssetCallback(REF); }); } void CLabel::plantTimer() { @@ -89,17 +86,13 @@ void CLabel::configure(const std::unordered_map& props, c label = formatString(labelPreFormat); - request.id = getUniqueResourceId(); - resourceID = request.id; - request.asset = label.formatted; - request.type = CAsyncResourceGatherer::eTargetType::TARGET_TEXT; - request.props["font_family"] = fontFamily; - request.props["color"] = labelColor; - request.props["font_size"] = fontSize; - request.props["cmd"] = label.cmd; + request.text = label.formatted; + request.font = fontFamily; + request.fontSize = fontSize; + request.color = labelColor.asRGB(); if (!textAlign.empty()) - request.props["text_align"] = textAlign; + request.align = parseTextAlignment(textAlign); } catch (const std::bad_any_cast& e) { RASSERT(false, "Failed to construct CLabel: {}", e.what()); // @@ -109,7 +102,7 @@ void CLabel::configure(const std::unordered_map& props, c pos = configPos; // Label size not known yet - g_pAsyncResourceGatherer->requestAsyncAssetPreload(request); + resourceID = (label.cmd) ? g_asyncResourceManager->requestTextCmd(request, []() {}) : g_asyncResourceManager->requestText(request, []() {}); plantTimer(); } @@ -124,16 +117,16 @@ void CLabel::reset() { return; if (asset) - g_pAsyncResourceGatherer->unloadAsset(asset); + g_asyncResourceManager->unload(asset); - asset = nullptr; - pendingResourceID.clear(); - resourceID.clear(); + asset = nullptr; + pendingResourceID = 0; + resourceID = 0; } bool CLabel::draw(const SRenderData& data) { if (!asset) { - asset = g_pAsyncResourceGatherer->getAssetByID(resourceID); + asset = g_asyncResourceManager->getAssetById(resourceID); if (!asset) return true; @@ -147,23 +140,23 @@ bool CLabel::draw(const SRenderData& data) { shadow.draw(data); // calc pos - pos = posFromHVAlign(viewport, asset->texture.m_vSize, configPos, halign, valign, angle); + pos = posFromHVAlign(viewport, asset->m_vSize, configPos, halign, valign, angle); - CBox box = {pos.x, pos.y, asset->texture.m_vSize.x, asset->texture.m_vSize.y}; + CBox box = {pos.x, pos.y, asset->m_vSize.x, asset->m_vSize.y}; box.rot = angle; - g_pRenderer->renderTexture(box, asset->texture, data.opacity); + g_pRenderer->renderTexture(box, *asset, data.opacity); return false; } void CLabel::renderUpdate() { - auto newAsset = g_pAsyncResourceGatherer->getAssetByID(pendingResourceID); + auto newAsset = g_asyncResourceManager->getAssetById(pendingResourceID); if (newAsset) { // new asset is ready :D - g_pAsyncResourceGatherer->unloadAsset(asset); + g_asyncResourceManager->unload(asset); asset = newAsset; resourceID = pendingResourceID; - pendingResourceID = ""; + pendingResourceID = 0; updateShadow = true; } else { Debug::log(WARN, "Asset {} not available after the asyncResourceGatherer's callback!", pendingResourceID); @@ -180,8 +173,8 @@ CBox CLabel::getBoundingBoxWl() const { return CBox{}; return { - Vector2D{pos.x, viewport.y - pos.y - asset->texture.m_vSize.y}, - asset->texture.m_vSize, + Vector2D{pos.x, viewport.y - pos.y - asset->m_vSize.y}, + asset->m_vSize, }; } diff --git a/src/renderer/widgets/Label.hpp b/src/renderer/widgets/Label.hpp index c41aea79..a7f6b89f 100644 --- a/src/renderer/widgets/Label.hpp +++ b/src/renderer/widgets/Label.hpp @@ -3,9 +3,10 @@ #include "../../defines.hpp" #include "IWidget.hpp" #include "Shadowable.hpp" -#include "../../helpers/Math.hpp" #include "../../core/Timer.hpp" #include "../AsyncResourceGatherer.hpp" +#include +#include #include #include #include @@ -33,29 +34,29 @@ class CLabel : public IWidget { void plantTimer(); private: - AWP m_self; + AWP m_self; - std::string getUniqueResourceId(); + std::string getUniqueResourceId(); - std::string labelPreFormat; - IWidget::SFormatResult label; + std::string labelPreFormat; + IWidget::SFormatResult label; - Vector2D viewport; - Vector2D pos; - Vector2D configPos; - double angle; - std::string resourceID; - std::string pendingResourceID; // if dynamic label - std::string halign, valign; - std::string onclickCommand; - ASP asset = nullptr; + Vector2D viewport; + Vector2D pos; + Vector2D configPos; + double angle; + size_t resourceID = 0; + size_t pendingResourceID = 0; // if dynamic label + std::string halign, valign; + std::string onclickCommand; + ASP asset = nullptr; - std::string outputStringPort; + std::string outputStringPort; - CAsyncResourceGatherer::SPreloadRequest request; + Hyprgraphics::CTextResource::STextResourceData request; - ASP labelTimer = nullptr; + ASP labelTimer = nullptr; - CShadowable shadow; - bool updateShadow = true; + CShadowable shadow; + bool updateShadow = true; }; diff --git a/src/renderer/widgets/PasswordInputField.cpp b/src/renderer/widgets/PasswordInputField.cpp index 604278e6..9207902f 100644 --- a/src/renderer/widgets/PasswordInputField.cpp +++ b/src/renderer/widgets/PasswordInputField.cpp @@ -1,4 +1,5 @@ #include "PasswordInputField.hpp" +#include "../AsyncResourceManager.hpp" #include "../Renderer.hpp" #include "../../core/hyprlock.hpp" #include "../../auth/Auth.hpp" @@ -8,6 +9,7 @@ #include "../../core/AnimationManager.hpp" #include "../../helpers/Color.hpp" #include +#include #include #include #include @@ -86,16 +88,12 @@ void CPasswordInputField::configure(const std::unordered_mapgoal(), configPos, halign, valign); if (!dots.textFormat.empty()) { - dots.textResourceID = std::format("input:{}-{}", (uintptr_t)this, dots.textFormat); - CAsyncResourceGatherer::SPreloadRequest request; - request.id = dots.textResourceID; - request.asset = dots.textFormat; - request.type = CAsyncResourceGatherer::eTargetType::TARGET_TEXT; - request.props["font_family"] = fontFamily; - request.props["color"] = colorConfig.font; - request.props["font_size"] = (int)(std::nearbyint(configSize.y * dots.size * 0.5f) * 2.f); - - g_pAsyncResourceGatherer->requestAsyncAssetPreload(request); + Hyprgraphics::CTextResource::STextResourceData request; + request.text = dots.textFormat; + request.font = fontFamily; + request.color = colorConfig.font.asRGB(); + request.fontSize = (int)(std::nearbyint(configSize.y * dots.size * 0.5f) * 2.f); + dots.textResourceID = g_asyncResourceManager->requestText(request, []() {}); } // request the inital placeholder asset @@ -103,7 +101,7 @@ void CPasswordInputField::configure(const std::unordered_mapcancel(); fade.fadeOutTimer.reset(); } @@ -112,10 +110,10 @@ void CPasswordInputField::reset() { return; if (placeholder.asset) - g_pAsyncResourceGatherer->unloadAsset(placeholder.asset); + g_asyncResourceManager->unload(placeholder.asset); - placeholder.asset = nullptr; - placeholder.resourceID.clear(); + placeholder.asset = nullptr; + placeholder.resourceID = 0; placeholder.currentText.clear(); } @@ -151,7 +149,7 @@ void CPasswordInputField::updateFade() { if (fade.allowFadeOut || fadeTimeoutMs == 0) { *fade.a = 0.0; fade.allowFadeOut = false; - } else if (!fade.fadeOutTimer.get()) + } else if (!fade.fadeOutTimer) fade.fadeOutTimer = g_pHyprlock->addTimer(std::chrono::milliseconds(fadeTimeoutMs), [REF = m_self](auto, auto) { fadeOutCallback(REF); }, nullptr); } else if (INPUTUSED && fade.a->goal() != 1.0) @@ -242,12 +240,12 @@ bool CPasswordInputField::draw(const SRenderData& data) { if (!dots.textFormat.empty()) { if (!dots.textAsset) - dots.textAsset = g_pAsyncResourceGatherer->getAssetByID(dots.textResourceID); + dots.textAsset = g_asyncResourceManager->getAssetById(dots.textResourceID); if (!dots.textAsset) forceReload = true; else { - passSize = dots.textAsset->texture.m_vSize; + passSize = dots.textAsset->m_vSize; passSpacing = std::floor(passSize.x * dots.spacing); } } @@ -293,7 +291,7 @@ bool CPasswordInputField::draw(const SRenderData& data) { break; } - g_pRenderer->renderTexture(box, dots.textAsset->texture, fontCol.a, dots.rounding); + g_pRenderer->renderTexture(box, *dots.textAsset, fontCol.a, dots.rounding); } else g_pRenderer->renderRect(box, fontCol, dots.rounding); @@ -301,22 +299,22 @@ bool CPasswordInputField::draw(const SRenderData& data) { } } - if (passwordLength == 0 && !checkWaiting && !placeholder.resourceID.empty()) { - ASP currAsset = nullptr; + if (passwordLength == 0 && !checkWaiting && placeholder.resourceID > 0) { + ASP currAsset = nullptr; if (!placeholder.asset) - placeholder.asset = g_pAsyncResourceGatherer->getAssetByID(placeholder.resourceID); + placeholder.asset = g_asyncResourceManager->getAssetById(placeholder.resourceID); currAsset = placeholder.asset; if (currAsset) { - const Vector2D ASSETPOS = inputFieldBox.pos() + inputFieldBox.size() / 2.0 - currAsset->texture.m_vSize / 2.0; - const CBox ASSETBOX{ASSETPOS, currAsset->texture.m_vSize}; + const Vector2D ASSETPOS = inputFieldBox.pos() + inputFieldBox.size() / 2.0 - currAsset->m_vSize / 2.0; + const CBox ASSETBOX{ASSETPOS, currAsset->m_vSize}; // Cut the texture to the width of the input field glEnable(GL_SCISSOR_TEST); glScissor(inputFieldBox.x, inputFieldBox.y, inputFieldBox.w, inputFieldBox.h); - g_pRenderer->renderTexture(ASSETBOX, currAsset->texture, data.opacity * fade.a->value(), 0); + g_pRenderer->renderTexture(ASSETBOX, *currAsset, data.opacity * fade.a->value(), 0); glScissor(0, 0, viewport.x, viewport.y); glDisable(GL_SCISSOR_TEST); } else @@ -330,9 +328,9 @@ void CPasswordInputField::updatePlaceholder() { if (passwordLength != 0) { if (placeholder.asset && /* keep prompt asset cause it is likely to be used again */ displayFail) { std::erase(placeholder.registeredResourceIDs, placeholder.resourceID); - g_pAsyncResourceGatherer->unloadAsset(placeholder.asset); + g_asyncResourceManager->unload(placeholder.asset); placeholder.asset = nullptr; - placeholder.resourceID = ""; + placeholder.resourceID = 0; redrawShadow = true; } return; @@ -351,42 +349,40 @@ void CPasswordInputField::updatePlaceholder() { if (!ALLOWCOLORSWAP && newText == placeholder.currentText) return; - const auto NEWRESOURCEID = std::format("placeholder:{}{}{}{}{}{}", newText, (uintptr_t)this, colorState.font.r, colorState.font.g, colorState.font.b, colorState.font.a); + // TODO: detect same text via resource manager + //const auto NEWRESOURCEID = std::format("placeholder:{}{}{}{}{}{}", newText, (uintptr_t)this, colorState.font.r, colorState.font.g, colorState.font.b, colorState.font.a); - if (placeholder.resourceID == NEWRESOURCEID) - return; + //if (placeholder.resourceID == NEWRESOURCEID) + // return; Debug::log(TRACE, "Updating placeholder text: {}", newText); placeholder.currentText = newText; placeholder.asset = nullptr; - placeholder.resourceID = NEWRESOURCEID; - if (std::ranges::find(placeholder.registeredResourceIDs, placeholder.resourceID) != placeholder.registeredResourceIDs.end()) - return; + //if (std::ranges::find(placeholder.registeredResourceIDs, placeholder.resourceID) != placeholder.registeredResourceIDs.end()) + // return; Debug::log(TRACE, "Requesting new placeholder asset: {}", placeholder.resourceID); placeholder.registeredResourceIDs.push_back(placeholder.resourceID); // query - CAsyncResourceGatherer::SPreloadRequest request; - request.id = placeholder.resourceID; - request.asset = placeholder.currentText; - request.type = CAsyncResourceGatherer::eTargetType::TARGET_TEXT; - request.props["font_family"] = fontFamily; - request.props["color"] = colorState.font; - request.props["font_size"] = (int)size->value().y / 4; - request.callback = [REF = m_self] { + Hyprgraphics::CTextResource::STextResourceData request; + request.text = placeholder.currentText; + request.font = fontFamily; + request.color = colorState.font.asRGB(); + request.fontSize = (int)size->value().y / 4; + + placeholder.resourceID = g_asyncResourceManager->requestText(request, [REF = m_self] { if (const auto SELF = REF.lock(); SELF) g_pHyprlock->renderOutput(SELF->outputStringPort); - }; - g_pAsyncResourceGatherer->requestAsyncAssetPreload(request); + }); } void CPasswordInputField::updateWidth() { double targetSizeX = configSize.x; if (passwordLength == 0 && placeholder.asset) - targetSizeX = placeholder.asset->texture.m_vSize.x + size->goal().y; + targetSizeX = placeholder.asset->m_vSize.x + size->goal().y; targetSizeX = std::max(targetSizeX, configSize.x); diff --git a/src/renderer/widgets/PasswordInputField.hpp b/src/renderer/widgets/PasswordInputField.hpp index d75a67ec..cb80b4a4 100644 --- a/src/renderer/widgets/PasswordInputField.hpp +++ b/src/renderer/widgets/PasswordInputField.hpp @@ -60,14 +60,14 @@ class CPasswordInputField : public IWidget { int outThick, rounding; struct { - PHLANIMVAR currentAmount; - bool center = false; - float size = 0; - float spacing = 0; - int rounding = 0; - std::string textFormat = ""; - std::string textResourceID; - ASP textAsset = nullptr; + PHLANIMVAR currentAmount; + bool center = false; + float size = 0; + float spacing = 0; + int rounding = 0; + size_t textResourceID = 0; + std::string textFormat = ""; + ASP textAsset = nullptr; } dots; struct { @@ -78,13 +78,13 @@ class CPasswordInputField : public IWidget { } fade; struct { - std::string resourceID = ""; - ASP asset = nullptr; + size_t resourceID = 0; + ASP asset = nullptr; - std::string currentText = ""; - size_t failedAttempts = 0; + std::string currentText = ""; + size_t failedAttempts = 0; - std::vector registeredResourceIDs; + std::vector registeredResourceIDs; } placeholder; struct { From a397af805b028a06824f305b96a79e131d656352 Mon Sep 17 00:00:00 2001 From: Maximilian Seidler Date: Wed, 24 Sep 2025 09:42:01 +0200 Subject: [PATCH 02/19] core: move screencopy frame generation to the new resource manager --- src/core/Output.cpp | 4 + src/core/Output.hpp | 1 + src/core/hyprlock.cpp | 61 +-- src/core/hyprlock.hpp | 4 +- src/renderer/AsyncResourceGatherer.cpp | 384 ------------------ src/renderer/AsyncResourceGatherer.hpp | 93 ----- src/renderer/AsyncResourceManager.cpp | 168 ++++++-- src/renderer/AsyncResourceManager.hpp | 34 +- src/renderer/Renderer.cpp | 14 +- src/renderer/Renderer.hpp | 1 - src/renderer/Screencopy.cpp | 53 +-- src/renderer/Screencopy.hpp | 25 +- .../{ => resources}/TextCmdResource.cpp | 4 +- .../{ => resources}/TextCmdResource.hpp | 0 src/renderer/widgets/Background.cpp | 71 ++-- src/renderer/widgets/Background.hpp | 75 ++-- src/renderer/widgets/IWidget.hpp | 1 + src/renderer/widgets/Image.cpp | 6 +- src/renderer/widgets/Image.hpp | 48 ++- src/renderer/widgets/Label.cpp | 4 +- src/renderer/widgets/Label.hpp | 1 - src/renderer/widgets/PasswordInputField.cpp | 4 +- 22 files changed, 321 insertions(+), 735 deletions(-) delete mode 100644 src/renderer/AsyncResourceGatherer.cpp delete mode 100644 src/renderer/AsyncResourceGatherer.hpp rename src/renderer/{ => resources}/TextCmdResource.cpp (90%) rename src/renderer/{ => resources}/TextCmdResource.hpp (100%) diff --git a/src/core/Output.cpp b/src/core/Output.cpp index 07d27c7a..e6cbfe26 100644 --- a/src/core/Output.cpp +++ b/src/core/Output.cpp @@ -67,3 +67,7 @@ void COutput::createSessionLockSurface() { Vector2D COutput::getViewport() const { return (m_sessionLockSurface) ? m_sessionLockSurface->size : size; } + +size_t COutput::getScreencopyResourceID() const { + return (size_t)this; +} diff --git a/src/core/Output.hpp b/src/core/Output.hpp index e9edd03b..75685546 100644 --- a/src/core/Output.hpp +++ b/src/core/Output.hpp @@ -30,4 +30,5 @@ class COutput { void createSessionLockSurface(); Vector2D getViewport() const; + size_t getScreencopyResourceID() const; }; diff --git a/src/core/hyprlock.cpp b/src/core/hyprlock.cpp index c3c20331..4a731b09 100644 --- a/src/core/hyprlock.cpp +++ b/src/core/hyprlock.cpp @@ -2,11 +2,11 @@ #include "../helpers/Log.hpp" #include "../config/ConfigManager.hpp" #include "../renderer/Renderer.hpp" -#include "../renderer/AsyncResourceGatherer.hpp" #include "../renderer/AsyncResourceManager.hpp" #include "../auth/Auth.hpp" #include "../auth/Fingerprint.hpp" -#include "Egl.hpp" +#include "./Egl.hpp" +#include "./Seat.hpp" #include #include #include @@ -331,61 +331,21 @@ void CHyprlock::run() { // gather info about monitors wl_display_roundtrip(m_sWaylandState.display); - g_pRenderer = makeUnique(); - g_pAsyncResourceGatherer = makeUnique(); - g_asyncResourceManager = makeUnique(); - g_pAuth = makeUnique(); + g_pRenderer = makeUnique(); + g_asyncResourceManager = makeUnique(); + g_pAuth = makeUnique(); g_pAuth->start(); Debug::log(LOG, "Running on {}", m_sCurrentDesktop); - if (!g_pHyprlock->m_bImmediateRender) { + g_asyncResourceManager->enqueueStaticAssets(); + g_asyncResourceManager->enqueueScreencopyFrames(); + + if (!g_pHyprlock->m_bImmediateRender) // 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_pAsyncResourceGatherer->gatheredEventfd.isValid()) { - pollfds[1] = { - .fd = g_pAsyncResourceGatherer->gatheredEventfd.get(), - .events = POLLIN, - }; - - fdcount++; - } - - while (!g_pAsyncResourceGatherer->gathered) { - wl_display_flush(m_sWaylandState.display); - 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_dispatch_pending(m_sWaylandState.display); - } else { - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - wl_display_dispatch(m_sWaylandState.display); - } - - if (std::chrono::duration_cast(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::system_clock::now() - STARTGATHERTP).count()); - } + g_asyncResourceManager->gatherInitialResources(m_sWaylandState.display); // Failed to lock the session if (!acquireSessionLock()) { @@ -533,7 +493,6 @@ void CHyprlock::run() { m_vOutputs.clear(); g_pSeatManager.reset(); - g_pAsyncResourceGatherer.reset(); g_pRenderer.reset(); g_pEGL.reset(); diff --git a/src/core/hyprlock.hpp b/src/core/hyprlock.hpp index 040d8a00..cae332d8 100644 --- a/src/core/hyprlock.hpp +++ b/src/core/hyprlock.hpp @@ -8,10 +8,8 @@ #include "linux-dmabuf-v1.hpp" #include "viewporter.hpp" #include "Output.hpp" -#include "Seat.hpp" -#include "CursorShape.hpp" #include "Timer.hpp" -#include +#include "../renderer/Screencopy.hpp" #include #include #include diff --git a/src/renderer/AsyncResourceGatherer.cpp b/src/renderer/AsyncResourceGatherer.cpp deleted file mode 100644 index df80d44b..00000000 --- a/src/renderer/AsyncResourceGatherer.cpp +++ /dev/null @@ -1,384 +0,0 @@ -#include "AsyncResourceGatherer.hpp" -#include "../config/ConfigManager.hpp" -#include "../core/Egl.hpp" -#include "../core/hyprlock.hpp" -#include "../helpers/Color.hpp" -#include "../helpers/Log.hpp" -#include "../helpers/MiscFunctions.hpp" -#include -#include -#include -#include -#include -#include -#include -using namespace Hyprgraphics; -using namespace Hyprutils::OS; - -CAsyncResourceGatherer_::CAsyncResourceGatherer_() { - if (g_pHyprlock->getScreencopy()) - enqueueScreencopyFrames(); - - initialGatherThread = std::thread([this]() { this->gather(); }); - asyncLoopThread = std::thread([this]() { this->asyncAssetSpinLock(); }); - - gatheredEventfd = CFileDescriptor{eventfd(0, EFD_CLOEXEC)}; - if (!gatheredEventfd.isValid()) - Debug::log(ERR, "Failed to create eventfd: {}", strerror(errno)); -} - -CAsyncResourceGatherer_::~CAsyncResourceGatherer_() { - notify(); - await(); -} - -void CAsyncResourceGatherer_::enqueueScreencopyFrames() { - if (g_pHyprlock->m_vOutputs.empty()) - return; - - static const auto ANIMATIONSENABLED = g_pConfigManager->getValue("animations:enabled"); - - const auto FADEINCFG = g_pConfigManager->m_AnimationTree.getConfig("fadeIn"); - const auto FADEOUTCFG = g_pConfigManager->m_AnimationTree.getConfig("fadeOut"); - - const bool FADENEEDSSC = *ANIMATIONSENABLED && - ((FADEINCFG->pValues && FADEINCFG->pValues->internalEnabled) || // fadeIn or fadeOut enabled - (FADEOUTCFG->pValues && FADEOUTCFG->pValues->internalEnabled)); - - const auto BGSCREENSHOT = std::ranges::any_of(g_pConfigManager->getWidgetConfigs(), [](const auto& w) { // - return w.type == "background" && std::string{std::any_cast(w.values.at("path"))} == "screenshot"; - }); - - if (!BGSCREENSHOT && !FADENEEDSSC) { - Debug::log(LOG, "Skipping screencopy"); - return; - } - - for (const auto& MON : g_pHyprlock->m_vOutputs) { - scframes.emplace_back(makeUnique(MON)); - } -} - -ASP 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) { - if (a.first == id) - return a.second; - } - - if (apply()) { - for (auto& a : assets) { - if (a.first == id) - return a.second; - } - }; - - return nullptr; -} - -static SP getCairoSurfaceFromImageFile(const std::filesystem::path& path) { - auto image = CImage(path); - if (!image.success()) { - Debug::log(ERR, "Image {} could not be loaded: {}", path.string(), image.getError()); - return nullptr; - } - - return image.cairoSurface(); -} - -void CAsyncResourceGatherer_::gather() { - const auto CWIDGETS = g_pConfigManager->getWidgetConfigs(); - - g_pEGL->makeCurrent(nullptr); - - // gather resources to preload - // clang-format off - int preloads = std::count_if(CWIDGETS.begin(), CWIDGETS.end(), [](const auto& w) { - return w.type == "background" || w.type == "image"; - }); - // clang-format on - - progress = 0; - for (auto& c : CWIDGETS) { - if (c.type == "background" || c.type == "image") { -#if defined(_LIBCPP_VERSION) && _LIBCPP_VERSION < 180100 - progress = progress + 1.0 / (preloads + 1.0); -#else - progress += 1.0 / (preloads + 1.0); -#endif - - std::string path = std::any_cast(c.values.at("path")); - - if (path.empty() || path == "screenshot") - continue; - - std::string id = (c.type == "background" ? std::string{"background:"} : std::string{"image:"}) + path; - - // render the image directly, since we are in a seperate thread - CAsyncResourceGatherer_::SPreloadRequest rq; - rq.type = CAsyncResourceGatherer_::TARGET_IMAGE; - rq.asset = path; - rq.id = id; - - renderImage(rq); - } - } - - // TODO: Wake this thread when all scframes are done instead of busy waiting. - while (!g_pHyprlock->m_bTerminate && std::ranges::any_of(scframes, [](const auto& d) { return !d->m_asset->ready; })) { - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - } - - // We are done with screencopy. - Debug::log(TRACE, "Gathered all screencopy frames - removing dmabuf listeners"); - g_pHyprlock->addTimer(std::chrono::milliseconds(0), [](auto, auto) { g_pHyprlock->removeDmabufListener(); }, nullptr); - - gathered = true; - // wake hyprlock from poll - if (gatheredEventfd.isValid()) - eventfd_write(gatheredEventfd.get(), 1); -} - -bool CAsyncResourceGatherer_::apply() { - preloadTargetsMutex.lock(); - - if (preloadTargets.empty()) { - preloadTargetsMutex.unlock(); - return false; - } - - auto currentPreloadTargets = preloadTargets; - preloadTargets.clear(); - preloadTargetsMutex.unlock(); - - for (auto& t : currentPreloadTargets) { - if (t.type == TARGET_IMAGE) { - if (!assets.contains(t.id)) { - assets[t.id] = makeAtomicShared(); - } - const auto ASSET = assets[t.id]; - - const cairo_status_t SURFACESTATUS = (cairo_status_t)t.cairosurface->status(); - const auto CAIROFORMAT = cairo_image_surface_get_format(t.cairosurface->cairo()); - const GLint glIFormat = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_RGB32F : GL_RGBA; - const GLint glFormat = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_RGB : GL_RGBA; - const GLint glType = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_FLOAT : GL_UNSIGNED_BYTE; - - if (SURFACESTATUS != CAIRO_STATUS_SUCCESS) { - Debug::log(ERR, "Resource {} invalid ({})", t.id, cairo_status_to_string(SURFACESTATUS)); - ASSET->texture.m_iType = TEXTURE_INVALID; - } - - ASSET->texture.m_vSize = t.size; - ASSET->texture.allocate(); - - glBindTexture(GL_TEXTURE_2D, ASSET->texture.m_iTexID); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - if (CAIROFORMAT != CAIRO_FORMAT_RGB96F) { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_BLUE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED); - } - glTexImage2D(GL_TEXTURE_2D, 0, glIFormat, ASSET->texture.m_vSize.x, ASSET->texture.m_vSize.y, 0, glFormat, glType, t.data); - - cairo_destroy((cairo_t*)t.cairo); - t.cairosurface.reset(); - } else - Debug::log(ERR, "Unsupported type in ::apply(): {}", (int)t.type); - } - - return true; -} - -void CAsyncResourceGatherer_::renderImage(const SPreloadRequest& rq) { - SPreloadTarget target; - target.type = TARGET_IMAGE; - target.id = rq.id; - - std::filesystem::path ABSOLUTEPATH(absolutePath(rq.asset, "")); - const auto CAIROISURFACE = getCairoSurfaceFromImageFile(ABSOLUTEPATH); - - if (!CAIROISURFACE) { - Debug::log(ERR, "renderImage: No cairo surface!"); - return; - } - - const auto CAIRO = cairo_create(CAIROISURFACE->cairo()); - cairo_scale(CAIRO, 1, 1); - - target.cairo = CAIRO; - target.cairosurface = CAIROISURFACE; - target.data = CAIROISURFACE->data(); - target.size = CAIROISURFACE->size(); - - std::lock_guard lg{preloadTargetsMutex}; - preloadTargets.push_back(target); -} - -void CAsyncResourceGatherer_::renderText(const SPreloadRequest& rq) { - SPreloadTarget target; - target.type = TARGET_IMAGE; /* text is just an image lol */ - target.id = rq.id; - - const int FONTSIZE = rq.props.contains("font_size") ? std::any_cast(rq.props.at("font_size")) : 16; - const CHyprColor FONTCOLOR = rq.props.contains("color") ? std::any_cast(rq.props.at("color")) : CHyprColor(1.0, 1.0, 1.0, 1.0); - const std::string FONTFAMILY = rq.props.contains("font_family") ? std::any_cast(rq.props.at("font_family")) : "Sans"; - const bool ISCMD = rq.props.contains("cmd") ? std::any_cast(rq.props.at("cmd")) : false; - - static const auto TRIM = g_pConfigManager->getValue("general:text_trim"); - std::string text = ISCMD ? spawnSync(rq.asset) : rq.asset; - - if (*TRIM) { - text.erase(0, text.find_first_not_of(" \n\r\t")); - text.erase(text.find_last_not_of(" \n\r\t") + 1); - } - - auto CAIROSURFACE = makeShared(cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1920, 1080 /* dummy value */)); - auto CAIRO = cairo_create(CAIROSURFACE->cairo()); - - // draw title using Pango - PangoLayout* layout = pango_cairo_create_layout(CAIRO); - - PangoFontDescription* fontDesc = pango_font_description_from_string(FONTFAMILY.c_str()); - pango_font_description_set_size(fontDesc, FONTSIZE * PANGO_SCALE); - pango_layout_set_font_description(layout, fontDesc); - pango_font_description_free(fontDesc); - - if (rq.props.contains("text_align")) { - const std::string TEXTALIGN = std::any_cast(rq.props.at("text_align")); - PangoAlignment align = PANGO_ALIGN_LEFT; - if (TEXTALIGN == "center") - align = PANGO_ALIGN_CENTER; - else if (TEXTALIGN == "right") - align = PANGO_ALIGN_RIGHT; - - pango_layout_set_alignment(layout, align); - } - - PangoAttrList* attrList = nullptr; - GError* gError = nullptr; - char* buf = nullptr; - if (pango_parse_markup(text.c_str(), -1, 0, &attrList, &buf, nullptr, &gError)) - pango_layout_set_text(layout, buf, -1); - else { - Debug::log(ERR, "Pango markup parsing for {} failed: {}", text, gError->message); - g_error_free(gError); - pango_layout_set_text(layout, text.c_str(), -1); - } - - if (!attrList) - attrList = pango_attr_list_new(); - - if (buf) - free(buf); - - pango_attr_list_insert(attrList, pango_attr_scale_new(1)); - pango_layout_set_attributes(layout, attrList); - pango_attr_list_unref(attrList); - - int layoutWidth, layoutHeight; - pango_layout_get_size(layout, &layoutWidth, &layoutHeight); - - // TODO: avoid this? - cairo_destroy(CAIRO); - CAIROSURFACE = makeShared(cairo_image_surface_create(CAIRO_FORMAT_ARGB32, layoutWidth / PANGO_SCALE, layoutHeight / PANGO_SCALE)); - CAIRO = cairo_create(CAIROSURFACE->cairo()); - - // clear the pixmap - cairo_save(CAIRO); - cairo_set_operator(CAIRO, CAIRO_OPERATOR_CLEAR); - cairo_paint(CAIRO); - cairo_restore(CAIRO); - - // render the thing - cairo_set_source_rgba(CAIRO, FONTCOLOR.r, FONTCOLOR.g, FONTCOLOR.b, FONTCOLOR.a); - - cairo_move_to(CAIRO, 0, 0); - pango_cairo_show_layout(CAIRO, layout); - - g_object_unref(layout); - - cairo_surface_flush(CAIROSURFACE->cairo()); - - target.cairo = CAIRO; - target.cairosurface = CAIROSURFACE; - target.data = CAIROSURFACE->data(); - target.size = {layoutWidth / (double)PANGO_SCALE, layoutHeight / (double)PANGO_SCALE}; - - std::lock_guard lg{preloadTargetsMutex}; - preloadTargets.push_back(target); -} - -void CAsyncResourceGatherer_::asyncAssetSpinLock() { - while (!g_pHyprlock->m_bTerminate) { - - std::unique_lock lk(asyncLoopState.requestsMutex); - if (!asyncLoopState.pending) // avoid a lock if a thread managed to request something already since we .unlock()ed - asyncLoopState.requestsCV.wait_for(lk, std::chrono::seconds(5), [this] { return asyncLoopState.pending; }); // wait for events - - asyncLoopState.pending = false; - - if (asyncLoopState.requests.empty()) { - lk.unlock(); - continue; - } - - auto requests = asyncLoopState.requests; - asyncLoopState.requests.clear(); - - lk.unlock(); - - // process requests - for (auto& r : requests) { - Debug::log(TRACE, "Processing requested resourceID {}", r.id); - - if (r.type == TARGET_TEXT) { - renderText(r); - } else if (r.type == TARGET_IMAGE) { - renderImage(r); - } else { - Debug::log(ERR, "Unsupported async preload type {}??", (int)r.type); - continue; - } - - // plant timer for callback - if (r.callback) - g_pHyprlock->addTimer(std::chrono::milliseconds(0), [cb = r.callback](auto, auto) { cb(); }, nullptr); - } - } -} - -void CAsyncResourceGatherer_::requestAsyncAssetPreload(const SPreloadRequest& request) { - Debug::log(TRACE, "Requesting label resource {}", request.id); - - std::lock_guard lg(asyncLoopState.requestsMutex); - asyncLoopState.requests.push_back(request); - asyncLoopState.pending = true; - asyncLoopState.requestsCV.notify_all(); -} - -void CAsyncResourceGatherer_::unloadAsset(ASP asset) { - std::erase_if(assets, [asset](const auto& a) { return a.second == asset; }); -} - -void CAsyncResourceGatherer_::notify() { - std::lock_guard lg(asyncLoopState.requestsMutex); - asyncLoopState.requests.clear(); - asyncLoopState.pending = true; - asyncLoopState.requestsCV.notify_all(); -} - -void CAsyncResourceGatherer_::await() { - if (initialGatherThread.joinable()) - initialGatherThread.join(); - if (asyncLoopThread.joinable()) - asyncLoopThread.join(); -} diff --git a/src/renderer/AsyncResourceGatherer.hpp b/src/renderer/AsyncResourceGatherer.hpp deleted file mode 100644 index 5f9339b3..00000000 --- a/src/renderer/AsyncResourceGatherer.hpp +++ /dev/null @@ -1,93 +0,0 @@ -#pragma once - -#include "Screencopy.hpp" -#include "../defines.hpp" -#include -#include -#include -#include -#include -#include -#include "Shared.hpp" -#include -#include - -class CAsyncResourceGatherer_ { - public: - CAsyncResourceGatherer_(); - ~CAsyncResourceGatherer_(); - std::atomic gathered = false; - Hyprutils::OS::CFileDescriptor gatheredEventfd; - - std::atomic progress = 0; - - /* only call from ogl thread */ - ASP getAssetByID(const std::string& id); - - bool apply(); - - enum eTargetType { - TARGET_IMAGE = 0, - TARGET_TEXT - }; - - struct SPreloadRequest { - eTargetType type; - std::string asset; - std::string id; - - std::unordered_map props; - - // optional. Callbacks will be dispatched from the main thread, - // so wayland/gl calls are OK. - // will fire once the resource is fully loaded and ready. - std::function callback = nullptr; - }; - - void requestAsyncAssetPreload(const SPreloadRequest& request); - void unloadAsset(ASP asset); - - private: - void notify(); - void await(); - - std::thread asyncLoopThread; - std::thread initialGatherThread; - - void asyncAssetSpinLock(); - void renderText(const SPreloadRequest& rq); - void renderImage(const SPreloadRequest& rq); - - struct { - std::condition_variable requestsCV; - std::mutex requestsMutex; - - std::vector requests; - bool pending = false; - - bool busy = false; - } asyncLoopState; - - struct SPreloadTarget { - eTargetType type = TARGET_IMAGE; - std::string id = ""; - - void* data = nullptr; - void* cairo = nullptr; - SP cairosurface; - - Vector2D size; - }; - - std::vector> scframes; - - std::vector preloadTargets; - std::mutex preloadTargetsMutex; - - std::unordered_map> assets; - - void gather(); - void enqueueScreencopyFrames(); -}; - -inline UP g_pAsyncResourceGatherer; diff --git a/src/renderer/AsyncResourceManager.cpp b/src/renderer/AsyncResourceManager.cpp index 7ac1a765..e3b40b84 100644 --- a/src/renderer/AsyncResourceManager.cpp +++ b/src/renderer/AsyncResourceManager.cpp @@ -1,11 +1,15 @@ #include "AsyncResourceManager.hpp" -#include "./TextCmdResource.hpp" +#include "./resources/TextCmdResource.hpp" #include "../helpers/Log.hpp" #include "../helpers/MiscFunctions.hpp" #include "../core/hyprlock.hpp" +#include "../config/ConfigManager.hpp" +#include #include +#include +#include using namespace Hyprgraphics; @@ -27,12 +31,8 @@ size_t CAsyncResourceManager::requestText(const CTextResource::STextResourceData Debug::log(TRACE, "Text resource text:\"{}\" (resourceID: {}) already requested, incrementing refcount!", request.text, RESOURCEID); m_textures[RESOURCEID].refs++; return RESOURCEID; - } else { - m_textures[RESOURCEID] = { - .texture = nullptr, - .refs = 1, - }; - } + } else + m_textures.emplace(RESOURCEID, SPreloadedTexture{.texture = nullptr, .refs = 1}); auto resource = makeAtomicShared(CTextResource::STextResourceData{request}); CAtomicSharedPointer resourceGeneric{resource}; @@ -68,12 +68,8 @@ size_t CAsyncResourceManager::requestTextCmd(const CTextResource::STextResourceD Debug::log(TRACE, "Text cmd resource text:\"{}\" (resourceID: {}) already requested, incrementing refcount!", request.text, RESOURCEID); m_textures[RESOURCEID].refs++; return RESOURCEID; - } else { - m_textures[RESOURCEID] = { - .texture = nullptr, - .refs = 1, - }; - } + } else + m_textures.emplace(RESOURCEID, SPreloadedTexture{.texture = nullptr, .refs = 1}); auto resource = makeAtomicShared(CTextResource::STextResourceData{request}); CAtomicSharedPointer resourceGeneric{resource}; @@ -108,12 +104,8 @@ size_t CAsyncResourceManager::requestImage(const std::string& path, std::functio Debug::log(TRACE, "Image resource image:\"{}\" (resourceID: {}) already requested, incrementing refcount!", path, RESOURCEID); m_textures[RESOURCEID].refs++; return RESOURCEID; - } else { - m_textures[RESOURCEID] = { - .texture = nullptr, - .refs = 1, - }; - } + } else + m_textures.emplace(RESOURCEID, SPreloadedTexture{.texture = nullptr, .refs = 1}); auto resource = makeAtomicShared(absolutePath(path, "")); CAtomicSharedPointer resourceGeneric{resource}; @@ -131,6 +123,7 @@ size_t CAsyncResourceManager::requestImage(const std::string& path, std::functio g_pHyprlock->addTimer( std::chrono::milliseconds(0), [RESOURCEID, callback](auto, auto) { + Debug::log(LOG, "CALLBACK!!!"); g_asyncResourceManager->resourceToTexture(RESOURCEID); callback(); }, @@ -142,6 +135,125 @@ size_t CAsyncResourceManager::requestImage(const std::string& path, std::functio return RESOURCEID; } +ASP CAsyncResourceManager::getAssetByID(size_t id) { + if (!m_textures.contains(id)) + return nullptr; + + return m_textures[id].texture; +} + +void CAsyncResourceManager::enqueueStaticAssets() { + const auto CWIDGETS = g_pConfigManager->getWidgetConfigs(); + + for (auto& c : CWIDGETS) { + if (c.type == "background" || c.type == "image") { + std::string path = std::any_cast(c.values.at("path")); + + if (path.empty() || path == "screenshot") + continue; + + std::string ABSOLUTEPATH(absolutePath(path, "")); + requestImage(ABSOLUTEPATH, [this]() { + if (!g_pHyprlock->m_bImmediateRender && m_resources.empty()) { + if (m_gatheredEventfd.isValid()) + eventfd_write(m_gatheredEventfd.get(), 1); + } + }); + } + } +} + +void CAsyncResourceManager::enqueueScreencopyFrames() { + if (g_pHyprlock->m_vOutputs.empty()) + return; + + static const auto ANIMATIONSENABLED = g_pConfigManager->getValue("animations:enabled"); + + const auto FADEINCFG = g_pConfigManager->m_AnimationTree.getConfig("fadeIn"); + const auto FADEOUTCFG = g_pConfigManager->m_AnimationTree.getConfig("fadeOut"); + + const bool FADENEEDSSC = *ANIMATIONSENABLED && + ((FADEINCFG->pValues && FADEINCFG->pValues->internalEnabled) || // fadeIn or fadeOut enabled + (FADEOUTCFG->pValues && FADEOUTCFG->pValues->internalEnabled)); + + const auto BGSCREENSHOT = std::ranges::any_of(g_pConfigManager->getWidgetConfigs(), [](const auto& w) { // + return w.type == "background" && std::string{std::any_cast(w.values.at("path"))} == "screenshot"; + }); + + if (!BGSCREENSHOT && !FADENEEDSSC) { + Debug::log(LOG, "Skipping screencopy"); + return; + } + + for (const auto& MON : g_pHyprlock->m_vOutputs) { + m_scFrames.emplace_back(makeUnique()); + auto* frame = m_scFrames.back().get(); + frame->capture(MON); + m_textures.emplace(frame->m_resourceID, + SPreloadedTexture{ + .texture = nullptr, + .refs = 1, + }); + } +} + +void CAsyncResourceManager::onScreencopyDone() { + // We are done with screencopy. + Debug::log(TRACE, "Gathered all screencopy frames - removing dmabuf listeners"); + g_pHyprlock->removeDmabufListener(); +} + +void CAsyncResourceManager::gatherInitialResources(wl_display* display) { + // 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(display), + .events = POLLIN, + }; + + if (m_gatheredEventfd.isValid()) { + pollfds[1] = { + .fd = m_gatheredEventfd.get(), + .events = POLLIN, + }; + + fdcount++; + } + + bool gathered = false; + while (!gathered) { + wl_display_flush(display); + if (wl_display_prepare_read(display) == 0) { + if (poll(pollfds, fdcount, /* 100ms timeout */ 100) < 0) { + RASSERT(errno == EINTR, "[core] Polling fds failed with {}", errno); + wl_display_cancel_read(display); + continue; + } + wl_display_read_events(display); + wl_display_dispatch_pending(display); + } else { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + wl_display_dispatch(display); + } + + if (std::chrono::duration_cast(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, "m_resources.size: {}, m_scFrames.size(): {}", m_resources.size(), m_scFrames.size()); + gathered = m_resources.empty() && m_scFrames.empty(); + } + + Debug::log(LOG, "Resources gathered after {} milliseconds", std::chrono::duration_cast(std::chrono::system_clock::now() - STARTGATHERTP).count()); +} + void CAsyncResourceManager::unload(ASP texture) { auto preload = std::ranges::find_if(m_textures, [texture](const auto& a) { return a.second.texture == texture; }); if (preload == m_textures.end()) @@ -162,13 +274,6 @@ void CAsyncResourceManager::unloadById(size_t id) { m_textures.erase(id); } -ASP CAsyncResourceManager::getAssetById(size_t id) { - if (!m_textures.contains(id)) - return nullptr; - - return m_textures[id].texture; -} - void CAsyncResourceManager::resourceToTexture(size_t id) { if (!m_resources.contains(id)) return; @@ -210,3 +315,14 @@ void CAsyncResourceManager::resourceToTexture(size_t id) { m_textures[id].texture = texture; } + +void CAsyncResourceManager::screencopyToTexture(const CScreencopyFrame& scFrame) { + RASSERT(scFrame.m_ready && m_textures.contains(scFrame.m_resourceID), "Logic error in screencopy gathering."); + m_textures[scFrame.m_resourceID].texture = scFrame.m_asset; + + std::erase_if(m_scFrames, [&scFrame](const auto& f) { return f.get() == &scFrame; }); + + Debug::log(LOG, "Done sc frame {}", scFrame.m_resourceID); + if (m_scFrames.empty()) + onScreencopyDone(); +} diff --git a/src/renderer/AsyncResourceManager.hpp b/src/renderer/AsyncResourceManager.hpp index a1542a4e..40baddb9 100644 --- a/src/renderer/AsyncResourceManager.hpp +++ b/src/renderer/AsyncResourceManager.hpp @@ -2,14 +2,27 @@ #include "../defines.hpp" #include "./Texture.hpp" +#include "./Screencopy.hpp" #include #include #include #include +#include class CAsyncResourceManager { public: + // Notes on resource lifetimes: + // Resources id's are the result of hashing the requested resource parameters. + // When a new request is made, adding a new entry to the m_textures map is done immediatly + // within a critical section. Subsequent passes through this section with the same resource id + // will increment the texture's references. The manager will release the resource when refs reaches 0, + // while the resource itelf may outlife it's reference in the manager. + // Why not use ASP/AWP for this? + // The problem is that we want to to increment the reference as soon as requesting the resource id. + // Not only when actually retrieving the asset with `getAssetById`. + // Also, this way a resource is static as long as it is not unloaded by all instances that requested it. + // TODO:: Make a wrapper object that contains the resource id and unload with RAII. struct SPreloadedTexture { ASP texture; size_t refs = 0; @@ -24,29 +37,36 @@ class CAsyncResourceManager { size_t requestTextCmd(const CTextResource::STextResourceData& request, std::function callback); size_t requestImage(const std::string& path, std::function callback); - ASP getAssetById(size_t id); + ASP getAssetByID(size_t id); void unload(ASP resource); void unloadById(size_t id); + void enqueueStaticAssets(); + void enqueueScreencopyFrames(); + void screencopyToTexture(const CScreencopyFrame& scFrame); + void gatherInitialResources(wl_display* display); + private: - bool apply(); void resourceToTexture(size_t id); + void onScreencopyDone(); //std::mutex m_wakeupMutex; //std::condition_variable m_wakeup; + Hyprutils::OS::CFileDescriptor m_gatheredEventfd; - bool m_exit = false; + bool m_exit = false; - int m_loadedAssets = 0; + int m_loadedAssets = 0; + // not shared between threads + std::unordered_map m_textures; + std::vector> m_scFrames; // shared between threads std::mutex m_resourcesMutex; std::unordered_map> m_resources; - // not shared between threads - std::unordered_map m_textures; - Hyprgraphics::CAsyncResourceGatherer m_gatherer; + Hyprgraphics::CAsyncResourceGatherer m_gatherer; }; inline UP g_asyncResourceManager; diff --git a/src/renderer/Renderer.cpp b/src/renderer/Renderer.cpp index 36810023..03529502 100644 --- a/src/renderer/Renderer.cpp +++ b/src/renderer/Renderer.cpp @@ -215,18 +215,12 @@ CRenderer::SRenderFeedback CRenderer::renderLock(const CSessionLockSurface& surf glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); SRenderFeedback feedback; - const bool WAITFORASSETS = !g_pHyprlock->m_bImmediateRender && !g_pAsyncResourceGatherer->gathered; - - if (!WAITFORASSETS) { - // render widgets - const auto WIDGETS = getOrCreateWidgetsFor(surf); - for (auto& w : WIDGETS) { - feedback.needsFrame = w->draw({opacity->value()}) || feedback.needsFrame; - } + // render widgets + const auto WIDGETS = getOrCreateWidgetsFor(surf); + for (auto& w : WIDGETS) { + feedback.needsFrame = w->draw({opacity->value()}) || feedback.needsFrame; } - feedback.needsFrame = feedback.needsFrame || !g_pAsyncResourceGatherer->gathered; - glDisable(GL_BLEND); return feedback; diff --git a/src/renderer/Renderer.hpp b/src/renderer/Renderer.hpp index 1f681791..df1ba592 100644 --- a/src/renderer/Renderer.hpp +++ b/src/renderer/Renderer.hpp @@ -7,7 +7,6 @@ #include "../core/LockSurface.hpp" #include "../helpers/AnimatedVariable.hpp" #include "../helpers/Color.hpp" -#include "AsyncResourceGatherer.hpp" #include "../config/ConfigDataValues.hpp" #include "widgets/IWidget.hpp" #include "Framebuffer.hpp" diff --git a/src/renderer/Screencopy.cpp b/src/renderer/Screencopy.cpp index e958bbdf..3f63513d 100644 --- a/src/renderer/Screencopy.cpp +++ b/src/renderer/Screencopy.cpp @@ -4,6 +4,7 @@ #include "../core/hyprlock.hpp" #include "../core/Egl.hpp" #include "../config/ConfigManager.hpp" +#include "src/renderer/AsyncResourceManager.hpp" #include "wlr-screencopy-unstable-v1.hpp" #include #include @@ -23,28 +24,20 @@ static PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES = nullpt static PFNEGLQUERYDMABUFMODIFIERSEXTPROC eglQueryDmaBufModifiersEXT = nullptr; // -std::string CScreencopyFrame::getResourceId(SP pOutput) { - return RESOURCEIDPREFIX + std::format(":{}-{}x{}", pOutput->stringPort, pOutput->size.x, pOutput->size.y); -} - -CScreencopyFrame::CScreencopyFrame(SP pOutput) : m_outputRef(pOutput) { - m_asset = makeAtomicShared(); - captureOutput(); +void CScreencopyFrame::capture(SP pOutput) { + RASSERT(pOutput, "Screencopy, but no valid output"); static const auto SCMODE = g_pConfigManager->getValue("general:screencopy_mode"); + + m_asset = makeAtomicShared(); + m_resourceID = (size_t)pOutput.get(); + + m_sc = makeShared(g_pHyprlock->getScreencopy()->sendCaptureOutput(false, pOutput->m_wlOutput->resource())); + if (*SCMODE == 1) m_frame = makeUnique(m_sc); else m_frame = makeUnique(m_sc); -} - -void CScreencopyFrame::captureOutput() { - const auto POUTPUT = m_outputRef.lock(); - RASSERT(POUTPUT, "Screencopy, but no valid output"); - - m_resourceID = getResourceId(POUTPUT); - - m_sc = makeShared(g_pHyprlock->getScreencopy()->sendCaptureOutput(false, POUTPUT->m_wlOutput->resource())); m_sc->setBufferDone([this](CCZwlrScreencopyFrameV1* r) { Debug::log(TRACE, "[sc] wlrOnBufferDone for {}", (void*)this); @@ -74,6 +67,8 @@ void CScreencopyFrame::captureOutput() { } m_sc.reset(); + m_ready = true; + g_asyncResourceManager->screencopyToTexture(*this); }); } @@ -202,7 +197,7 @@ bool CSCDMAFrame::onBufferDone() { return true; } -bool CSCDMAFrame::onBufferReady(ASP asset) { +bool CSCDMAFrame::onBufferReady(ASP texture) { static constexpr struct { EGLAttrib fd; EGLAttrib offset; @@ -256,9 +251,9 @@ bool CSCDMAFrame::onBufferReady(ASP asset) { return false; } - asset->texture.allocate(); - asset->texture.m_vSize = {m_w, m_h}; - glBindTexture(GL_TEXTURE_2D, asset->texture.m_iTexID); + texture->allocate(); + texture->m_vSize = {m_w, m_h}; + glBindTexture(GL_TEXTURE_2D, texture->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_NEAREST); @@ -266,9 +261,7 @@ bool CSCDMAFrame::onBufferReady(ASP asset) { glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, m_image); glBindTexture(GL_TEXTURE_2D, 0); - Debug::log(LOG, "Got dma frame with size {}", asset->texture.m_vSize); - - asset->ready = true; + Debug::log(LOG, "Got dma frame with size {}", texture->m_vSize); return true; } @@ -460,14 +453,14 @@ void CSCSHMFrame::convertBuffer() { } } -bool CSCSHMFrame::onBufferReady(ASP asset) { +bool CSCSHMFrame::onBufferReady(ASP texture) { convertBuffer(); - asset->texture.allocate(); - asset->texture.m_vSize.x = m_w; - asset->texture.m_vSize.y = m_h; + texture->allocate(); + texture->m_vSize.x = m_w; + texture->m_vSize.y = m_h; - glBindTexture(GL_TEXTURE_2D, asset->texture.m_iTexID); + glBindTexture(GL_TEXTURE_2D, texture->m_iTexID); void* buffer = m_convBuffer ? m_convBuffer : m_shmData; @@ -478,9 +471,7 @@ bool CSCSHMFrame::onBufferReady(ASP asset) { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_w, m_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer); glBindTexture(GL_TEXTURE_2D, 0); - Debug::log(LOG, "[sc] [shm] Got screenshot with size {}", asset->texture.m_vSize); - - asset->ready = true; + Debug::log(LOG, "[sc] [shm] Got screenshot with size {}", texture->m_vSize); return true; } diff --git a/src/renderer/Screencopy.hpp b/src/renderer/Screencopy.hpp index 77671394..28a83f54 100644 --- a/src/renderer/Screencopy.hpp +++ b/src/renderer/Screencopy.hpp @@ -2,10 +2,9 @@ #include "../defines.hpp" #include "../core/Output.hpp" +#include "../renderer/Texture.hpp" #include #include -#include -#include "Shared.hpp" #include "linux-dmabuf-v1.hpp" #include "wlr-screencopy-unstable-v1.hpp" @@ -14,29 +13,27 @@ class ISCFrame { ISCFrame() = default; virtual ~ISCFrame() = default; - virtual bool onBufferDone() = 0; - virtual bool onBufferReady(ASP asset) = 0; + virtual bool onBufferDone() = 0; + virtual bool onBufferReady(ASP asset) = 0; SP m_wlBuffer = nullptr; }; class CScreencopyFrame { public: - static std::string getResourceId(SP pOutput); - static constexpr const std::string RESOURCEIDPREFIX = "screencopy"; - - CScreencopyFrame(SP pOutput); + CScreencopyFrame() = default; ~CScreencopyFrame() = default; - void captureOutput(); + void capture(SP pOutput); SP m_sc = nullptr; - std::string m_resourceID; - ASP m_asset; + size_t m_resourceID; + ASP m_asset; + + bool m_ready = false; private: - WP m_outputRef; UP m_frame = nullptr; bool m_dmaFailed = false; @@ -48,7 +45,7 @@ class CSCDMAFrame : public ISCFrame { CSCDMAFrame(SP sc); virtual ~CSCDMAFrame(); - virtual bool onBufferReady(ASP asset); + virtual bool onBufferReady(ASP asset); virtual bool onBufferDone(); private: @@ -78,7 +75,7 @@ class CSCSHMFrame : public ISCFrame { virtual bool onBufferDone() { return m_ok; } - virtual bool onBufferReady(ASP asset); + virtual bool onBufferReady(ASP texture); void convertBuffer(); private: diff --git a/src/renderer/TextCmdResource.cpp b/src/renderer/resources/TextCmdResource.cpp similarity index 90% rename from src/renderer/TextCmdResource.cpp rename to src/renderer/resources/TextCmdResource.cpp index 75fa1028..50480bc6 100644 --- a/src/renderer/TextCmdResource.cpp +++ b/src/renderer/resources/TextCmdResource.cpp @@ -1,8 +1,8 @@ #include "TextCmdResource.hpp" -#include "../config/ConfigManager.hpp" -#include "../helpers/MiscFunctions.hpp" +#include "../../config/ConfigManager.hpp" +#include "../../helpers/MiscFunctions.hpp" #include using namespace Hyprgraphics; diff --git a/src/renderer/TextCmdResource.hpp b/src/renderer/resources/TextCmdResource.hpp similarity index 100% rename from src/renderer/TextCmdResource.hpp rename to src/renderer/resources/TextCmdResource.hpp diff --git a/src/renderer/widgets/Background.cpp b/src/renderer/widgets/Background.cpp index d8be6d29..61abdf16 100644 --- a/src/renderer/widgets/Background.cpp +++ b/src/renderer/widgets/Background.cpp @@ -1,7 +1,7 @@ #include "Background.hpp" #include "../Renderer.hpp" +#include "../AsyncResourceManager.hpp" #include "../Framebuffer.hpp" -#include "../Shared.hpp" #include "../../core/hyprlock.hpp" #include "../../helpers/Log.hpp" #include "../../helpers/MiscFunctions.hpp" @@ -53,15 +53,15 @@ void CBackground::configure(const std::unordered_map& pro viewport = pOutput->getViewport(); outputPort = pOutput->stringPort; transform = wlTransformToHyprutils(invertTransform(pOutput->transform)); - scResourceID = CScreencopyFrame::getResourceId(pOutput); + scResourceID = pOutput->getScreencopyResourceID(); g_pAnimationManager->createAnimation(0.f, crossFadeProgress, g_pConfigManager->m_AnimationTree.getConfig("fadeIn")); // 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. - if (g_pAsyncResourceGatherer->gathered && !g_pAsyncResourceGatherer->getAssetByID(scResourceID)) { + if (!g_asyncResourceManager->getAssetByID(scResourceID)) { Debug::log(LOG, "Missing screenshot for output {}", outputPort); - scResourceID = ""; + scResourceID = 0; } if (isScreenshot) { @@ -69,10 +69,10 @@ void CBackground::configure(const std::unordered_map& pro if (!g_pHyprlock->getScreencopy()) { Debug::log(ERR, "No screencopy support! path=screenshot won't work. Falling back to background color."); - resourceID = ""; + resourceID = 0; } } else if (!path.empty()) - resourceID = "background:" + path; + resourceID = 0; if (!isScreenshot && reloadTime > -1) { try { @@ -94,17 +94,16 @@ void CBackground::reset() { } void CBackground::updatePrimaryAsset() { - if (asset || resourceID.empty()) + if (asset || resourceID == 0) return; - asset = g_pAsyncResourceGatherer->getAssetByID(resourceID); + asset = g_asyncResourceManager->getAssetByID(resourceID); if (!asset) return; - const bool NEEDFB = - (isScreenshot || blurPasses > 0 || asset->texture.m_vSize != viewport || transform != HYPRUTILS_TRANSFORM_NORMAL) && (!blurredFB->isAllocated() || firstRender); + const bool NEEDFB = (isScreenshot || blurPasses > 0 || asset->m_vSize != viewport || transform != HYPRUTILS_TRANSFORM_NORMAL) && (!blurredFB->isAllocated() || firstRender); if (NEEDFB) - renderToFB(asset->texture, *blurredFB, blurPasses, isScreenshot); + renderToFB(*asset, *blurredFB, blurPasses, isScreenshot); } void CBackground::updatePendingAsset() { @@ -112,21 +111,21 @@ void CBackground::updatePendingAsset() { if (!pendingAsset || blurPasses == 0 || pendingBlurredFB->isAllocated()) return; - renderToFB(pendingAsset->texture, *pendingBlurredFB, blurPasses); + renderToFB(*pendingAsset, *pendingBlurredFB, blurPasses); } void CBackground::updateScAsset() { - if (scAsset || scResourceID.empty()) + if (scAsset || scResourceID == 0) return; // path=screenshot -> scAsset = asset - scAsset = (asset && isScreenshot) ? asset : g_pAsyncResourceGatherer->getAssetByID(scResourceID); + scAsset = (asset && isScreenshot) ? asset : g_asyncResourceManager->getAssetByID(scResourceID); if (!scAsset) return; const bool NEEDSCTRANSFORM = transform != HYPRUTILS_TRANSFORM_NORMAL; if (NEEDSCTRANSFORM) - renderToFB(scAsset->texture, *transformedScFB, 0, true); + renderToFB(*scAsset, *transformedScFB, 0, true); } const CTexture& CBackground::getPrimaryAssetTex() const { @@ -134,15 +133,15 @@ const CTexture& CBackground::getPrimaryAssetTex() const { if (isScreenshot && blurPasses == 0 && transformedScFB->isAllocated()) return transformedScFB->m_cTex; - return (blurredFB->isAllocated()) ? blurredFB->m_cTex : asset->texture; + return (blurredFB->isAllocated()) ? blurredFB->m_cTex : *asset; } const CTexture& CBackground::getPendingAssetTex() const { - return (pendingBlurredFB->isAllocated()) ? pendingBlurredFB->m_cTex : pendingAsset->texture; + return (pendingBlurredFB->isAllocated()) ? pendingBlurredFB->m_cTex : *pendingAsset; } const CTexture& CBackground::getScAssetTex() const { - return (transformedScFB->isAllocated()) ? transformedScFB->m_cTex : scAsset->texture; + return (transformedScFB->isAllocated()) ? transformedScFB->m_cTex : *scAsset; } void CBackground::renderRect(CHyprColor color) { @@ -219,14 +218,14 @@ bool CBackground::draw(const SRenderData& data) { updatePendingAsset(); updateScAsset(); - if (asset && asset->texture.m_iType == TEXTURE_INVALID) { - g_pAsyncResourceGatherer->unloadAsset(asset); - resourceID = ""; + if (asset && asset->m_iType == TEXTURE_INVALID) { + g_asyncResourceManager->unload(asset); + resourceID = 0; renderRect(color); return false; } - if (!asset || resourceID.empty()) { + if (!asset || resourceID == 0) { // fade in/out with a solid color if (data.opacity < 1.0 && scAsset) { const auto& SCTEX = getScAssetTex(); @@ -239,7 +238,7 @@ bool CBackground::draw(const SRenderData& data) { } renderRect(color); - return !asset && !resourceID.empty(); // resource not ready + return !asset && resourceID > 0; // resource not ready } const auto& TEX = getPrimaryAssetTex(); @@ -294,28 +293,20 @@ void CBackground::onReloadTimerUpdate() { return; } - if (!pendingResourceID.empty()) + if (pendingResourceID > 0) 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 = [REF = m_self]() { onAssetCallback(REF); }; - - g_pAsyncResourceGatherer->requestAsyncAssetPreload(request); + pendingResourceID = g_asyncResourceManager->requestImage(path, [REF = m_self]() { onAssetCallback(REF); }); } void CBackground::startCrossFade() { - auto newAsset = g_pAsyncResourceGatherer->getAssetByID(pendingResourceID); + auto newAsset = g_asyncResourceManager->getAssetByID(pendingResourceID); if (newAsset) { - if (newAsset->texture.m_iType == TEXTURE_INVALID) { - g_pAsyncResourceGatherer->unloadAsset(newAsset); + if (newAsset->m_iType == TEXTURE_INVALID) { + g_asyncResourceManager->unload(newAsset); Debug::log(ERR, "New asset had an invalid texture!"); - pendingResourceID = ""; + pendingResourceID = 0; } else if (resourceID != pendingResourceID) { pendingAsset = newAsset; crossFadeProgress->setValueAndWarp(0); @@ -325,11 +316,11 @@ void CBackground::startCrossFade() { [REF = m_self](auto) { if (const auto PSELF = REF.lock()) { if (PSELF->asset) - g_pAsyncResourceGatherer->unloadAsset(PSELF->asset); + g_asyncResourceManager->unload(PSELF->asset); PSELF->asset = PSELF->pendingAsset; PSELF->pendingAsset = nullptr; PSELF->resourceID = PSELF->pendingResourceID; - PSELF->pendingResourceID = ""; + PSELF->pendingResourceID = 0; PSELF->blurredFB->destroyBuffer(); PSELF->blurredFB = std::move(PSELF->pendingBlurredFB); @@ -339,7 +330,7 @@ void CBackground::startCrossFade() { g_pHyprlock->renderOutput(outputPort); } - } else if (!pendingResourceID.empty()) { + } else if (pendingResourceID > 0) { 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); } diff --git a/src/renderer/widgets/Background.hpp b/src/renderer/widgets/Background.hpp index 51477d95..27f0b0d8 100644 --- a/src/renderer/widgets/Background.hpp +++ b/src/renderer/widgets/Background.hpp @@ -1,19 +1,15 @@ #pragma once -#include "../../defines.hpp" #include "IWidget.hpp" +#include "../../defines.hpp" #include "../../helpers/AnimatedVariable.hpp" #include "../../helpers/Color.hpp" -#include "../../helpers/Math.hpp" #include "../../core/Timer.hpp" #include "../Framebuffer.hpp" -#include "../AsyncResourceGatherer.hpp" -#include #include #include #include #include -#include #include struct SPreloadedAsset; @@ -50,39 +46,38 @@ class CBackground : public IWidget { AWP m_self; // if needed - UP blurredFB; - UP pendingBlurredFB; - UP transformedScFB; - - 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 outputPort; - Hyprutils::Math::eTransform transform; - - std::string resourceID; - std::string scResourceID; - std::string pendingResourceID; - - PHLANIMVAR crossFadeProgress; - - CHyprColor color; - ASP asset = nullptr; - ASP scAsset = nullptr; - ASP pendingAsset = nullptr; - bool isScreenshot = false; - bool firstRender = true; - - int reloadTime = -1; - std::string reloadCommand; - CAsyncResourceGatherer_::SPreloadRequest request; - ASP reloadTimer; - std::filesystem::file_time_type modificationTime; + UP blurredFB; + UP pendingBlurredFB; + UP transformedScFB; + + 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 outputPort; + Hyprutils::Math::eTransform transform; + + size_t resourceID; + size_t scResourceID; + size_t pendingResourceID; + + PHLANIMVAR crossFadeProgress; + + CHyprColor color; + ASP asset = nullptr; + ASP scAsset = nullptr; + ASP pendingAsset = nullptr; + bool isScreenshot = false; + bool firstRender = true; + + int reloadTime = -1; + std::string reloadCommand; + ASP reloadTimer; + std::filesystem::file_time_type modificationTime; }; diff --git a/src/renderer/widgets/IWidget.hpp b/src/renderer/widgets/IWidget.hpp index 50a1d134..5456602a 100644 --- a/src/renderer/widgets/IWidget.hpp +++ b/src/renderer/widgets/IWidget.hpp @@ -2,6 +2,7 @@ #include "../../defines.hpp" #include "../../helpers/Math.hpp" +#include "../../core/Seat.hpp" #include #include #include diff --git a/src/renderer/widgets/Image.cpp b/src/renderer/widgets/Image.cpp index 1d8ebe19..2c928b19 100644 --- a/src/renderer/widgets/Image.cpp +++ b/src/renderer/widgets/Image.cpp @@ -1,10 +1,10 @@ #include "Image.hpp" #include "../Renderer.hpp" +#include "../AsyncResourceManager.hpp" #include "../../core/hyprlock.hpp" #include "../../helpers/Log.hpp" #include "../../helpers/MiscFunctions.hpp" #include "../../config/ConfigDataValues.hpp" -#include "src/renderer/AsyncResourceManager.hpp" #include #include #include @@ -136,7 +136,7 @@ bool CImage::draw(const SRenderData& data) { return false; if (!asset) - asset = g_asyncResourceManager->getAssetById(resourceID); + asset = g_asyncResourceManager->getAssetByID(resourceID); if (!asset) return true; @@ -206,7 +206,7 @@ bool CImage::draw(const SRenderData& data) { } void CImage::renderUpdate() { - auto newAsset = g_asyncResourceManager->getAssetById(pendingResourceID); + auto newAsset = g_asyncResourceManager->getAssetByID(pendingResourceID); if (newAsset) { if (newAsset->m_iType == TEXTURE_INVALID) { g_asyncResourceManager->unload(newAsset); diff --git a/src/renderer/widgets/Image.hpp b/src/renderer/widgets/Image.hpp index f517fa0b..bc2a26e7 100644 --- a/src/renderer/widgets/Image.hpp +++ b/src/renderer/widgets/Image.hpp @@ -1,12 +1,11 @@ #pragma once -#include "../../defines.hpp" #include "IWidget.hpp" +#include "../../defines.hpp" #include "../../helpers/Color.hpp" #include "../../helpers/Math.hpp" #include "../../config/ConfigDataValues.hpp" #include "../../core/Timer.hpp" -#include "../AsyncResourceGatherer.hpp" #include "Shadowable.hpp" #include #include @@ -36,35 +35,34 @@ class CImage : public IWidget { void plantTimer(); private: - AWP m_self; + AWP m_self; - CFramebuffer imageFB; + CFramebuffer imageFB; - int size; - int rounding; - double border; - double angle; - CGradientValueData color; - Vector2D pos; - Vector2D configPos; + int size; + int rounding; + double border; + double angle; + CGradientValueData color; + Vector2D pos; + Vector2D configPos; - std::string halign, valign, path; + std::string halign, valign, path; - bool firstRender = true; + bool firstRender = true; - int reloadTime; - std::string reloadCommand; - std::string onclickCommand; + int reloadTime; + std::string reloadCommand; + std::string onclickCommand; - std::filesystem::file_time_type modificationTime; - ASP imageTimer; - CAsyncResourceGatherer_::SPreloadRequest request; + std::filesystem::file_time_type modificationTime; + ASP imageTimer; - Vector2D viewport; - std::string stringPort; + Vector2D viewport; + std::string stringPort; - size_t resourceID; - size_t pendingResourceID; // if reloading image - ASP asset = nullptr; - CShadowable shadow; + size_t resourceID; + size_t pendingResourceID; // if reloading image + ASP asset = nullptr; + CShadowable shadow; }; diff --git a/src/renderer/widgets/Label.cpp b/src/renderer/widgets/Label.cpp index 049e1873..376d684f 100644 --- a/src/renderer/widgets/Label.cpp +++ b/src/renderer/widgets/Label.cpp @@ -126,7 +126,7 @@ void CLabel::reset() { bool CLabel::draw(const SRenderData& data) { if (!asset) { - asset = g_asyncResourceManager->getAssetById(resourceID); + asset = g_asyncResourceManager->getAssetByID(resourceID); if (!asset) return true; @@ -150,7 +150,7 @@ bool CLabel::draw(const SRenderData& data) { } void CLabel::renderUpdate() { - auto newAsset = g_asyncResourceManager->getAssetById(pendingResourceID); + auto newAsset = g_asyncResourceManager->getAssetByID(pendingResourceID); if (newAsset) { // new asset is ready :D g_asyncResourceManager->unload(asset); diff --git a/src/renderer/widgets/Label.hpp b/src/renderer/widgets/Label.hpp index a7f6b89f..b920a168 100644 --- a/src/renderer/widgets/Label.hpp +++ b/src/renderer/widgets/Label.hpp @@ -4,7 +4,6 @@ #include "IWidget.hpp" #include "Shadowable.hpp" #include "../../core/Timer.hpp" -#include "../AsyncResourceGatherer.hpp" #include #include #include diff --git a/src/renderer/widgets/PasswordInputField.cpp b/src/renderer/widgets/PasswordInputField.cpp index 9207902f..33a9851c 100644 --- a/src/renderer/widgets/PasswordInputField.cpp +++ b/src/renderer/widgets/PasswordInputField.cpp @@ -240,7 +240,7 @@ bool CPasswordInputField::draw(const SRenderData& data) { if (!dots.textFormat.empty()) { if (!dots.textAsset) - dots.textAsset = g_asyncResourceManager->getAssetById(dots.textResourceID); + dots.textAsset = g_asyncResourceManager->getAssetByID(dots.textResourceID); if (!dots.textAsset) forceReload = true; @@ -303,7 +303,7 @@ bool CPasswordInputField::draw(const SRenderData& data) { ASP currAsset = nullptr; if (!placeholder.asset) - placeholder.asset = g_asyncResourceManager->getAssetById(placeholder.resourceID); + placeholder.asset = g_asyncResourceManager->getAssetByID(placeholder.resourceID); currAsset = placeholder.asset; From ce9452cbd4b2cf461a456302c87fb0964a73abfe Mon Sep 17 00:00:00 2001 From: Maximilian Seidler Date: Wed, 24 Sep 2025 09:58:56 +0200 Subject: [PATCH 03/19] fixup static resources, renames --- src/core/hyprlock.cpp | 48 ++++++++++-------- src/core/hyprlock.hpp | 1 + src/renderer/AsyncResourceManager.cpp | 73 +++++++++++++-------------- src/renderer/AsyncResourceManager.hpp | 9 ++-- 4 files changed, 67 insertions(+), 64 deletions(-) diff --git a/src/core/hyprlock.cpp b/src/core/hyprlock.cpp index 4a731b09..3df2b4e4 100644 --- a/src/core/hyprlock.cpp +++ b/src/core/hyprlock.cpp @@ -460,28 +460,7 @@ void CHyprlock::run() { } } - // do timers - m_sLoopState.timersMutex.lock(); - auto timerscpy = m_vTimers; - m_sLoopState.timersMutex.unlock(); - - std::vector> passed; - - for (auto& t : timerscpy) { - if (t->passed() && !t->cancelled()) { - t->call(t); - passed.push_back(t); - } - - if (t->cancelled()) - passed.push_back(t); - } - - m_sLoopState.timersMutex.lock(); - std::erase_if(m_vTimers, [passed](const auto& timer) { return std::find(passed.begin(), passed.end(), timer) != passed.end(); }); - m_sLoopState.timersMutex.unlock(); - - passed.clear(); + processTimers(); } const auto DPY = m_sWaylandState.display; @@ -869,6 +848,31 @@ ASP CHyprlock::addTimer(const std::chrono::system_clock::duration& timeo return T; } +void CHyprlock::processTimers() { + // do timers + m_sLoopState.timersMutex.lock(); + auto timerscpy = m_vTimers; + m_sLoopState.timersMutex.unlock(); + + std::vector> passed; + + for (auto& t : timerscpy) { + if (t->passed() && !t->cancelled()) { + t->call(t); + passed.push_back(t); + } + + if (t->cancelled()) + passed.push_back(t); + } + + m_sLoopState.timersMutex.lock(); + std::erase_if(m_vTimers, [passed](const auto& timer) { return std::find(passed.begin(), passed.end(), timer) != passed.end(); }); + m_sLoopState.timersMutex.unlock(); + + passed.clear(); +} + std::vector> CHyprlock::getTimers() { return m_vTimers; } diff --git a/src/core/hyprlock.hpp b/src/core/hyprlock.hpp index cae332d8..38fbe424 100644 --- a/src/core/hyprlock.hpp +++ b/src/core/hyprlock.hpp @@ -36,6 +36,7 @@ class CHyprlock { bool isUnlocked(); ASP addTimer(const std::chrono::system_clock::duration& timeout, std::function self, void* data)> cb_, void* data, bool force = false); + void processTimers(); void enqueueForceUpdateTimers(); diff --git a/src/renderer/AsyncResourceManager.cpp b/src/renderer/AsyncResourceManager.cpp index e3b40b84..67d3df69 100644 --- a/src/renderer/AsyncResourceManager.cpp +++ b/src/renderer/AsyncResourceManager.cpp @@ -12,6 +12,7 @@ #include using namespace Hyprgraphics; +using namespace Hyprutils::OS; template <> struct std::hash { @@ -27,12 +28,12 @@ struct std::hash { size_t CAsyncResourceManager::requestText(const CTextResource::STextResourceData& request, std::function callback) { const auto RESOURCEID = std::hash{}(request); - if (m_textures.contains(RESOURCEID)) { + if (m_assets.contains(RESOURCEID)) { Debug::log(TRACE, "Text resource text:\"{}\" (resourceID: {}) already requested, incrementing refcount!", request.text, RESOURCEID); - m_textures[RESOURCEID].refs++; + m_assets[RESOURCEID].refs++; return RESOURCEID; } else - m_textures.emplace(RESOURCEID, SPreloadedTexture{.texture = nullptr, .refs = 1}); + m_assets.emplace(RESOURCEID, SPreloadedTexture{.texture = nullptr, .refs = 1}); auto resource = makeAtomicShared(CTextResource::STextResourceData{request}); CAtomicSharedPointer resourceGeneric{resource}; @@ -51,7 +52,7 @@ size_t CAsyncResourceManager::requestText(const CTextResource::STextResourceData g_pHyprlock->addTimer( std::chrono::milliseconds(0), [RESOURCEID, callback](auto, auto) { - g_asyncResourceManager->resourceToTexture(RESOURCEID); + g_asyncResourceManager->resourceToAsset(RESOURCEID); callback(); }, nullptr); @@ -64,12 +65,12 @@ size_t CAsyncResourceManager::requestText(const CTextResource::STextResourceData size_t CAsyncResourceManager::requestTextCmd(const CTextResource::STextResourceData& request, std::function callback) { const auto RESOURCEID = std::hash{}(request); - if (m_textures.contains(RESOURCEID)) { + if (m_assets.contains(RESOURCEID)) { Debug::log(TRACE, "Text cmd resource text:\"{}\" (resourceID: {}) already requested, incrementing refcount!", request.text, RESOURCEID); - m_textures[RESOURCEID].refs++; + m_assets[RESOURCEID].refs++; return RESOURCEID; } else - m_textures.emplace(RESOURCEID, SPreloadedTexture{.texture = nullptr, .refs = 1}); + m_assets.emplace(RESOURCEID, SPreloadedTexture{.texture = nullptr, .refs = 1}); auto resource = makeAtomicShared(CTextResource::STextResourceData{request}); CAtomicSharedPointer resourceGeneric{resource}; @@ -87,7 +88,7 @@ size_t CAsyncResourceManager::requestTextCmd(const CTextResource::STextResourceD g_pHyprlock->addTimer( std::chrono::milliseconds(0), [RESOURCEID, callback](auto, auto) { - g_asyncResourceManager->resourceToTexture(RESOURCEID); + g_asyncResourceManager->resourceToAsset(RESOURCEID); callback(); }, nullptr); @@ -99,13 +100,13 @@ size_t CAsyncResourceManager::requestTextCmd(const CTextResource::STextResourceD } size_t CAsyncResourceManager::requestImage(const std::string& path, std::function callback) { - const auto RESOURCEID = std::hash{}(path); - if (m_textures.contains(RESOURCEID)) { + const auto RESOURCEID = std::hash{}(absolutePath(path, "")); + if (m_assets.contains(RESOURCEID)) { Debug::log(TRACE, "Image resource image:\"{}\" (resourceID: {}) already requested, incrementing refcount!", path, RESOURCEID); - m_textures[RESOURCEID].refs++; + m_assets[RESOURCEID].refs++; return RESOURCEID; } else - m_textures.emplace(RESOURCEID, SPreloadedTexture{.texture = nullptr, .refs = 1}); + m_assets.emplace(RESOURCEID, SPreloadedTexture{.texture = nullptr, .refs = 1}); auto resource = makeAtomicShared(absolutePath(path, "")); CAtomicSharedPointer resourceGeneric{resource}; @@ -124,7 +125,7 @@ size_t CAsyncResourceManager::requestImage(const std::string& path, std::functio std::chrono::milliseconds(0), [RESOURCEID, callback](auto, auto) { Debug::log(LOG, "CALLBACK!!!"); - g_asyncResourceManager->resourceToTexture(RESOURCEID); + g_asyncResourceManager->resourceToAsset(RESOURCEID); callback(); }, nullptr); @@ -136,10 +137,10 @@ size_t CAsyncResourceManager::requestImage(const std::string& path, std::functio } ASP CAsyncResourceManager::getAssetByID(size_t id) { - if (!m_textures.contains(id)) + if (!m_assets.contains(id)) return nullptr; - return m_textures[id].texture; + return m_assets[id].texture; } void CAsyncResourceManager::enqueueStaticAssets() { @@ -152,8 +153,7 @@ void CAsyncResourceManager::enqueueStaticAssets() { if (path.empty() || path == "screenshot") continue; - std::string ABSOLUTEPATH(absolutePath(path, "")); - requestImage(ABSOLUTEPATH, [this]() { + requestImage(path, [this]() { if (!g_pHyprlock->m_bImmediateRender && m_resources.empty()) { if (m_gatheredEventfd.isValid()) eventfd_write(m_gatheredEventfd.get(), 1); @@ -189,11 +189,7 @@ void CAsyncResourceManager::enqueueScreencopyFrames() { m_scFrames.emplace_back(makeUnique()); auto* frame = m_scFrames.back().get(); frame->capture(MON); - m_textures.emplace(frame->m_resourceID, - SPreloadedTexture{ - .texture = nullptr, - .refs = 1, - }); + m_assets.emplace(frame->m_resourceID, SPreloadedTexture{.texture = nullptr, .refs = 1}); } } @@ -210,8 +206,10 @@ void CAsyncResourceManager::gatherInitialResources(wl_display* display) { const auto MAXDELAYMS = 2000; // 2 Seconds const auto STARTGATHERTP = std::chrono::system_clock::now(); - int fdcount = 1; - pollfd pollfds[2]; + m_gatheredEventfd = CFileDescriptor{eventfd(0, EFD_CLOEXEC)}; + + int fdcount = 1; + pollfd pollfds[2]; pollfds[0] = { .fd = wl_display_get_fd(display), .events = POLLIN, @@ -242,12 +240,13 @@ void CAsyncResourceManager::gatherInitialResources(wl_display* display) { wl_display_dispatch(display); } + g_pHyprlock->processTimers(); + if (std::chrono::duration_cast(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, "m_resources.size: {}, m_scFrames.size(): {}", m_resources.size(), m_scFrames.size()); gathered = m_resources.empty() && m_scFrames.empty(); } @@ -255,26 +254,26 @@ void CAsyncResourceManager::gatherInitialResources(wl_display* display) { } void CAsyncResourceManager::unload(ASP texture) { - auto preload = std::ranges::find_if(m_textures, [texture](const auto& a) { return a.second.texture == texture; }); - if (preload == m_textures.end()) + auto preload = std::ranges::find_if(m_assets, [texture](const auto& a) { return a.second.texture == texture; }); + if (preload == m_assets.end()) return; preload->second.refs--; if (preload->second.refs == 0) - m_textures.erase(preload->first); + m_assets.erase(preload->first); } void CAsyncResourceManager::unloadById(size_t id) { - if (!m_textures.contains(id)) + if (!m_assets.contains(id)) return; - m_textures.erase(id); - m_textures[id].refs--; - if (m_textures[id].refs == 0) - m_textures.erase(id); + m_assets.erase(id); + m_assets[id].refs--; + if (m_assets[id].refs == 0) + m_assets.erase(id); } -void CAsyncResourceManager::resourceToTexture(size_t id) { +void CAsyncResourceManager::resourceToAsset(size_t id) { if (!m_resources.contains(id)) return; @@ -313,12 +312,12 @@ void CAsyncResourceManager::resourceToTexture(size_t id) { } glTexImage2D(GL_TEXTURE_2D, 0, glIFormat, texture->m_vSize.x, texture->m_vSize.y, 0, glFormat, glType, RESOURCE->m_asset.cairoSurface->data()); - m_textures[id].texture = texture; + m_assets[id].texture = texture; } void CAsyncResourceManager::screencopyToTexture(const CScreencopyFrame& scFrame) { - RASSERT(scFrame.m_ready && m_textures.contains(scFrame.m_resourceID), "Logic error in screencopy gathering."); - m_textures[scFrame.m_resourceID].texture = scFrame.m_asset; + RASSERT(scFrame.m_ready && m_assets.contains(scFrame.m_resourceID), "Logic error in screencopy gathering."); + m_assets[scFrame.m_resourceID].texture = scFrame.m_asset; std::erase_if(m_scFrames, [&scFrame](const auto& f) { return f.get() == &scFrame; }); diff --git a/src/renderer/AsyncResourceManager.hpp b/src/renderer/AsyncResourceManager.hpp index 40baddb9..029b081f 100644 --- a/src/renderer/AsyncResourceManager.hpp +++ b/src/renderer/AsyncResourceManager.hpp @@ -14,7 +14,7 @@ class CAsyncResourceManager { public: // Notes on resource lifetimes: // Resources id's are the result of hashing the requested resource parameters. - // When a new request is made, adding a new entry to the m_textures map is done immediatly + // When a new request is made, adding a new entry to the m_assets map is done immediatly // within a critical section. Subsequent passes through this section with the same resource id // will increment the texture's references. The manager will release the resource when refs reaches 0, // while the resource itelf may outlife it's reference in the manager. @@ -48,11 +48,10 @@ class CAsyncResourceManager { void gatherInitialResources(wl_display* display); private: - void resourceToTexture(size_t id); + void resourceToAsset(size_t id); void onScreencopyDone(); - //std::mutex m_wakeupMutex; - //std::condition_variable m_wakeup; + // for polling when using gatherInitialResources Hyprutils::OS::CFileDescriptor m_gatheredEventfd; bool m_exit = false; @@ -60,7 +59,7 @@ class CAsyncResourceManager { int m_loadedAssets = 0; // not shared between threads - std::unordered_map m_textures; + std::unordered_map m_assets; std::vector> m_scFrames; // shared between threads std::mutex m_resourcesMutex; From 0ffa251c1600e4d36228c293546e55411288e650 Mon Sep 17 00:00:00 2001 From: Maximilian Seidler Date: Wed, 24 Sep 2025 10:22:01 +0200 Subject: [PATCH 04/19] core: introduce a dedicated onAssetUpdate callback --- src/core/Output.cpp | 4 - src/core/Output.hpp | 1 - src/core/hyprlock.hpp | 1 - src/defines.hpp | 3 + src/helpers/Log.hpp | 2 +- src/renderer/AsyncResourceManager.cpp | 274 +++++++++++--------- src/renderer/AsyncResourceManager.hpp | 41 ++- src/renderer/Screencopy.cpp | 5 +- src/renderer/widgets/Background.cpp | 87 +++---- src/renderer/widgets/Background.hpp | 8 +- src/renderer/widgets/IWidget.hpp | 3 + src/renderer/widgets/Image.cpp | 52 ++-- src/renderer/widgets/Image.hpp | 17 +- src/renderer/widgets/Label.cpp | 44 ++-- src/renderer/widgets/Label.hpp | 15 +- src/renderer/widgets/PasswordInputField.cpp | 28 +- src/renderer/widgets/PasswordInputField.hpp | 12 +- src/renderer/widgets/Shape.cpp | 5 + src/renderer/widgets/Shape.hpp | 2 + 19 files changed, 320 insertions(+), 284 deletions(-) diff --git a/src/core/Output.cpp b/src/core/Output.cpp index e6cbfe26..07d27c7a 100644 --- a/src/core/Output.cpp +++ b/src/core/Output.cpp @@ -67,7 +67,3 @@ void COutput::createSessionLockSurface() { Vector2D COutput::getViewport() const { return (m_sessionLockSurface) ? m_sessionLockSurface->size : size; } - -size_t COutput::getScreencopyResourceID() const { - return (size_t)this; -} diff --git a/src/core/Output.hpp b/src/core/Output.hpp index 75685546..e9edd03b 100644 --- a/src/core/Output.hpp +++ b/src/core/Output.hpp @@ -30,5 +30,4 @@ class COutput { void createSessionLockSurface(); Vector2D getViewport() const; - size_t getScreencopyResourceID() const; }; diff --git a/src/core/hyprlock.hpp b/src/core/hyprlock.hpp index 38fbe424..309c8bf5 100644 --- a/src/core/hyprlock.hpp +++ b/src/core/hyprlock.hpp @@ -9,7 +9,6 @@ #include "viewporter.hpp" #include "Output.hpp" #include "Timer.hpp" -#include "../renderer/Screencopy.hpp" #include #include #include diff --git a/src/defines.hpp b/src/defines.hpp index dd0f1bba..51a711cc 100644 --- a/src/defines.hpp +++ b/src/defines.hpp @@ -7,6 +7,9 @@ using namespace Hyprutils::Memory; using namespace Hyprgraphics; + +using ResourceID = size_t; + #define SP CSharedPointer #define WP CWeakPointer #define UP CUniquePointer diff --git a/src/helpers/Log.hpp b/src/helpers/Log.hpp index 00770dad..7604e90f 100644 --- a/src/helpers/Log.hpp +++ b/src/helpers/Log.hpp @@ -51,4 +51,4 @@ namespace Debug { std::println("[{}] {}", logLevelString(level), std::vformat(fmt, std::make_format_args(args...))); } } -}; \ No newline at end of file +}; diff --git a/src/renderer/AsyncResourceManager.cpp b/src/renderer/AsyncResourceManager.cpp index 67d3df69..a25d6888 100644 --- a/src/renderer/AsyncResourceManager.cpp +++ b/src/renderer/AsyncResourceManager.cpp @@ -7,6 +7,7 @@ #include "../config/ConfigManager.hpp" #include +#include #include #include #include @@ -14,125 +15,74 @@ using namespace Hyprgraphics; using namespace Hyprutils::OS; -template <> -struct std::hash { - std::size_t operator()(CTextResource::STextResourceData const& s) const noexcept { - const auto H1 = std::hash{}(s.text); - const auto H2 = std::hash{}(s.color.asRgb().r); - const auto H3 = std::hash{}(s.color.asRgb().g); - const auto H4 = std::hash{}(s.color.asRgb().b); +static inline ResourceID scopeResourceID(uint8_t scope, size_t in) { + return (in & ~0xff) | scope; +} - return H1 ^ H2 ^ H3 ^ H4; - } -}; +ResourceID CAsyncResourceManager::resourceIDForTextRequest(const CTextResource::STextResourceData& s) { + // TODO: Currently ignores the font string and resulting distribution is probably not perfect. + const auto H1 = std::hash{}(s.text); + const auto H2 = std::hash{}(s.color.asRgb().r); + const auto H3 = std::hash{}(s.color.asRgb().g) + s.fontSize; + const auto H4 = std::hash{}(s.color.asRgb().b) + s.align; -size_t CAsyncResourceManager::requestText(const CTextResource::STextResourceData& request, std::function callback) { - const auto RESOURCEID = std::hash{}(request); - if (m_assets.contains(RESOURCEID)) { - Debug::log(TRACE, "Text resource text:\"{}\" (resourceID: {}) already requested, incrementing refcount!", request.text, RESOURCEID); - m_assets[RESOURCEID].refs++; - return RESOURCEID; - } else - m_assets.emplace(RESOURCEID, SPreloadedTexture{.texture = nullptr, .refs = 1}); + return scopeResourceID(1, H1 ^ (H2 << 1) ^ (H3 << 2) ^ (H4 << 3)); +} - auto resource = makeAtomicShared(CTextResource::STextResourceData{request}); - CAtomicSharedPointer resourceGeneric{resource}; +ResourceID CAsyncResourceManager::resourceIDForTextCmdRequest(const CTextResource::STextResourceData& s) { + return scopeResourceID(2, resourceIDForTextRequest(s) ^ std::hash{}(std::chrono::system_clock::now().time_since_epoch().count())); +} - m_gatherer.enqueue(resourceGeneric); +ResourceID CAsyncResourceManager::resourceIDForImageRequest(const std::string& path, size_t revision) { + return scopeResourceID(3, std::hash{}(path) ^ std::hash{}(revision)); +} - m_resourcesMutex.lock(); - if (m_resources.contains(RESOURCEID)) { - m_resourcesMutex.unlock(); +ResourceID CAsyncResourceManager::resourceIDForScreencopy(const std::string& port) { + return scopeResourceID(4, std::hash{}(port)); +} + +size_t CAsyncResourceManager::requestText(const CTextResource::STextResourceData& params, const AWP& widget) { + const auto RESOURCEID = resourceIDForTextRequest(params); + if (request(RESOURCEID, widget)) { + Debug::log(TRACE, "Text resource \"{}\" (resourceID: {}) already requested!", params.text, RESOURCEID); return RESOURCEID; } - m_resources[RESOURCEID] = std::move(resourceGeneric); - m_resourcesMutex.unlock(); - resource->m_events.finished.listenStatic([RESOURCEID, callback]() { - g_pHyprlock->addTimer( - std::chrono::milliseconds(0), - [RESOURCEID, callback](auto, auto) { - g_asyncResourceManager->resourceToAsset(RESOURCEID); - callback(); - }, - nullptr); - }); - - Debug::log(TRACE, "Enqueued text:\"{}\" (resourceID: {}) successfully.", request.text, RESOURCEID); + auto resource = makeAtomicShared(CTextResource::STextResourceData{params}); + CAtomicSharedPointer resourceGeneric{resource}; + Debug::log(LOG, "Requesting text resource \"{}\" (resourceID: {})", params.text, RESOURCEID); + enqueue(RESOURCEID, resourceGeneric, widget); return RESOURCEID; } -size_t CAsyncResourceManager::requestTextCmd(const CTextResource::STextResourceData& request, std::function callback) { - const auto RESOURCEID = std::hash{}(request); - if (m_assets.contains(RESOURCEID)) { - Debug::log(TRACE, "Text cmd resource text:\"{}\" (resourceID: {}) already requested, incrementing refcount!", request.text, RESOURCEID); - m_assets[RESOURCEID].refs++; +size_t CAsyncResourceManager::requestTextCmd(const CTextResource::STextResourceData& params, const AWP& widget) { + const auto RESOURCEID = resourceIDForTextCmdRequest(params); + if (request(RESOURCEID, widget)) { + Debug::log(TRACE, "Text cmd resource \"{}\" (resourceID: {}) already requested!", params.text, RESOURCEID); return RESOURCEID; - } else - m_assets.emplace(RESOURCEID, SPreloadedTexture{.texture = nullptr, .refs = 1}); + } - auto resource = makeAtomicShared(CTextResource::STextResourceData{request}); + auto resource = makeAtomicShared(CTextResource::STextResourceData{params}); CAtomicSharedPointer resourceGeneric{resource}; - m_gatherer.enqueue(resourceGeneric); - - m_resourcesMutex.lock(); - if (m_resources.contains(RESOURCEID)) - Debug::log(ERR, "Resource already enqueued! This is a bug."); - - m_resources[RESOURCEID] = std::move(resourceGeneric); - m_resourcesMutex.unlock(); - - resource->m_events.finished.listenStatic([RESOURCEID, callback]() { - g_pHyprlock->addTimer( - std::chrono::milliseconds(0), - [RESOURCEID, callback](auto, auto) { - g_asyncResourceManager->resourceToAsset(RESOURCEID); - callback(); - }, - nullptr); - }); - - Debug::log(TRACE, "Enqueued text cmd:\"{}\" (resourceID: {}) successfully.", request.text, RESOURCEID); - + Debug::log(LOG, "Enqueued text cmd resource `{}` (resourceID: {})", params.text, RESOURCEID); + enqueue(RESOURCEID, resourceGeneric, widget); return RESOURCEID; } -size_t CAsyncResourceManager::requestImage(const std::string& path, std::function callback) { - const auto RESOURCEID = std::hash{}(absolutePath(path, "")); - if (m_assets.contains(RESOURCEID)) { - Debug::log(TRACE, "Image resource image:\"{}\" (resourceID: {}) already requested, incrementing refcount!", path, RESOURCEID); - m_assets[RESOURCEID].refs++; +size_t CAsyncResourceManager::requestImage(const std::string& path, size_t revision, const AWP& widget) { + const auto RESOURCEID = resourceIDForImageRequest(path, revision); + if (request(RESOURCEID, widget)) { + Debug::log(TRACE, "Image resource {} revision {} (resourceID: {}) already requested!", path, revision, RESOURCEID); return RESOURCEID; - } else - m_assets.emplace(RESOURCEID, SPreloadedTexture{.texture = nullptr, .refs = 1}); + } auto resource = makeAtomicShared(absolutePath(path, "")); CAtomicSharedPointer resourceGeneric{resource}; - m_gatherer.enqueue(resourceGeneric); - - m_resourcesMutex.lock(); - if (m_resources.contains(RESOURCEID)) - Debug::log(ERR, "Resource already enqueued! This is a bug."); - - m_resources[RESOURCEID] = std::move(resourceGeneric); - m_resourcesMutex.unlock(); - - resource->m_events.finished.listenStatic([RESOURCEID, callback]() { - g_pHyprlock->addTimer( - std::chrono::milliseconds(0), - [RESOURCEID, callback](auto, auto) { - Debug::log(LOG, "CALLBACK!!!"); - g_asyncResourceManager->resourceToAsset(RESOURCEID); - callback(); - }, - nullptr); - }); - - Debug::log(TRACE, "Enqueued image:\"{}\" (resourceID: {}) successfully.", path, RESOURCEID); - + Debug::log(LOG, "Image resource {} revision {} (resourceID: {})", path, revision, RESOURCEID); + enqueue(RESOURCEID, resourceGeneric, widget); return RESOURCEID; } @@ -153,12 +103,7 @@ void CAsyncResourceManager::enqueueStaticAssets() { if (path.empty() || path == "screenshot") continue; - requestImage(path, [this]() { - if (!g_pHyprlock->m_bImmediateRender && m_resources.empty()) { - if (m_gatheredEventfd.isValid()) - eventfd_write(m_gatheredEventfd.get(), 1); - } - }); + requestImage(path, 0, nullptr); } } } @@ -193,16 +138,19 @@ void CAsyncResourceManager::enqueueScreencopyFrames() { } } -void CAsyncResourceManager::onScreencopyDone() { - // We are done with screencopy. - Debug::log(TRACE, "Gathered all screencopy frames - removing dmabuf listeners"); - g_pHyprlock->removeDmabufListener(); +void CAsyncResourceManager::screencopyToTexture(const CScreencopyFrame& scFrame) { + RASSERT(scFrame.m_ready && m_assets.contains(scFrame.m_resourceID), "Logic error in screencopy gathering."); + m_assets[scFrame.m_resourceID].texture = scFrame.m_asset; + + Debug::log(TRACE, "Done sc frame {}", scFrame.m_resourceID); + + std::erase_if(m_scFrames, [&scFrame](const auto& f) { return f.get() == &scFrame; }); + + if (m_scFrames.empty()) + onScreencopyDone(); } void CAsyncResourceManager::gatherInitialResources(wl_display* display) { - // 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(); @@ -253,39 +201,101 @@ void CAsyncResourceManager::gatherInitialResources(wl_display* display) { Debug::log(LOG, "Resources gathered after {} milliseconds", std::chrono::duration_cast(std::chrono::system_clock::now() - STARTGATHERTP).count()); } +bool CAsyncResourceManager::checkIdPresent(ResourceID id) { + return m_assets.contains(id); +} + void CAsyncResourceManager::unload(ASP texture) { auto preload = std::ranges::find_if(m_assets, [texture](const auto& a) { return a.second.texture == texture; }); if (preload == m_assets.end()) return; preload->second.refs--; - if (preload->second.refs == 0) + + if (preload->second.refs == 0) { + Debug::log(TRACE, "Releasing resourceID: {}!", preload->first); m_assets.erase(preload->first); + } } -void CAsyncResourceManager::unloadById(size_t id) { +void CAsyncResourceManager::unloadById(ResourceID id) { if (!m_assets.contains(id)) return; - m_assets.erase(id); m_assets[id].refs--; - if (m_assets[id].refs == 0) + + if (m_assets[id].refs == 0) { + Debug::log(TRACE, "Releasing resourceID: {}!", id); m_assets.erase(id); + } } -void CAsyncResourceManager::resourceToAsset(size_t id) { - if (!m_resources.contains(id)) - return; +bool CAsyncResourceManager::request(ResourceID id, const AWP& widget) { + if (!m_assets.contains(id)) { + // New asset!! + m_assets.emplace(id, SPreloadedTexture{.texture = nullptr, .refs = 1}); + return false; + } + + m_assets[id].refs++; + + if (m_assets[id].texture) { + // Asset already present. Dispatch the asset callback function. + const auto& TEXTURE = m_assets[id].texture; + if (widget) + g_pHyprlock->addTimer( + std::chrono::milliseconds(0), + [widget, TEXTURE](auto, auto) { + if (const auto WIDGET = widget.lock(); WIDGET) + WIDGET->onAssetUpdate(TEXTURE); + }, + nullptr); + } else if (widget) { + // Asset currently in-flight. Add the widget reference to in order for the callback to get dispatched later. + m_resourcesMutex.lock(); + if (!m_resources.contains(id)) { + Debug::log(ERR, "In-flight resourceID: {} not found! This is a bug.", id); + m_resourcesMutex.unlock(); + return true; + } + m_resources[id].second.emplace_back(widget); + m_resourcesMutex.unlock(); + } + + return true; +} + +void CAsyncResourceManager::enqueue(ResourceID resourceID, const ASP& resource, const AWP& widget) { + m_gatherer.enqueue(resource); m_resourcesMutex.lock(); - const auto RESOURCE = m_resources[id]; + if (m_resources.contains(resourceID)) + Debug::log(ERR, "Resource already enqueued! This is a bug."); + + m_resources[resourceID] = {resource, {widget}}; + m_resourcesMutex.unlock(); + + resource->m_events.finished.listenStatic([resourceID]() { + g_pHyprlock->addTimer(std::chrono::milliseconds(0), [](auto, void* resourceID) { g_asyncResourceManager->onResourceFinished((size_t)resourceID); }, (void*)resourceID); + }); +} + +void CAsyncResourceManager::onResourceFinished(ResourceID id) { + m_resourcesMutex.lock(); + if (!m_resources.contains(id)) { + m_resourcesMutex.unlock(); + return; + } + + const auto RESOURCE = m_resources[id].first; + const auto WIDGETS = m_resources[id].second; m_resources.erase(id); m_resourcesMutex.unlock(); if (!RESOURCE || !RESOURCE->m_asset.cairoSurface) return; - Debug::log(TRACE, "Resource to texture id:{}", id); + //Debug::log(TRACE, "Resource to texture id:{}", id); const auto texture = makeAtomicShared(); @@ -296,7 +306,7 @@ void CAsyncResourceManager::resourceToAsset(size_t id) { const GLint glType = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_FLOAT : GL_UNSIGNED_BYTE; if (SURFACESTATUS != CAIRO_STATUS_SUCCESS) { - Debug::log(ERR, "RESOURCE {} invalid ({})", id, cairo_status_to_string(SURFACESTATUS)); + Debug::log(ERR, "resourceID: {} invalid ({})", id, cairo_status_to_string(SURFACESTATUS)); texture->m_iType = TEXTURE_INVALID; } @@ -313,15 +323,27 @@ void CAsyncResourceManager::resourceToAsset(size_t id) { glTexImage2D(GL_TEXTURE_2D, 0, glIFormat, texture->m_vSize.x, texture->m_vSize.y, 0, glFormat, glType, RESOURCE->m_asset.cairoSurface->data()); m_assets[id].texture = texture; -} -void CAsyncResourceManager::screencopyToTexture(const CScreencopyFrame& scFrame) { - RASSERT(scFrame.m_ready && m_assets.contains(scFrame.m_resourceID), "Logic error in screencopy gathering."); - m_assets[scFrame.m_resourceID].texture = scFrame.m_asset; + for (const auto& widget : WIDGETS) { + if (widget) + widget->onAssetUpdate(texture); + } - std::erase_if(m_scFrames, [&scFrame](const auto& f) { return f.get() == &scFrame; }); + if (!m_gathered && !g_pHyprlock->m_bImmediateRender) { + m_resourcesMutex.lock(); + if (m_resources.empty()) { + m_gathered = true; + if (m_gatheredEventfd.isValid()) + eventfd_write(m_gatheredEventfd.get(), 1); - Debug::log(LOG, "Done sc frame {}", scFrame.m_resourceID); - if (m_scFrames.empty()) - onScreencopyDone(); + m_gatheredEventfd.reset(); + } + m_resourcesMutex.unlock(); + } +} + +void CAsyncResourceManager::onScreencopyDone() { + // We are done with screencopy. + //Debug::log(TRACE, "Gathered all screencopy frames - removing dmabuf listeners"); + g_pHyprlock->removeDmabufListener(); } diff --git a/src/renderer/AsyncResourceManager.hpp b/src/renderer/AsyncResourceManager.hpp index 029b081f..e07059d4 100644 --- a/src/renderer/AsyncResourceManager.hpp +++ b/src/renderer/AsyncResourceManager.hpp @@ -3,6 +3,7 @@ #include "../defines.hpp" #include "./Texture.hpp" #include "./Screencopy.hpp" +#include "./widgets/IWidget.hpp" #include #include @@ -11,6 +12,7 @@ #include class CAsyncResourceManager { + public: // Notes on resource lifetimes: // Resources id's are the result of hashing the requested resource parameters. @@ -23,6 +25,14 @@ class CAsyncResourceManager { // Not only when actually retrieving the asset with `getAssetById`. // Also, this way a resource is static as long as it is not unloaded by all instances that requested it. // TODO:: Make a wrapper object that contains the resource id and unload with RAII. + + // Those are hash functions that return the id for a requested resource. + static ResourceID resourceIDForTextRequest(const CTextResource::STextResourceData& s); + static ResourceID resourceIDForTextCmdRequest(const CTextResource::STextResourceData& s); + // image paths may be file system links, thus this function supports a revision parameter that gets factored into the resource id. + static ResourceID resourceIDForImageRequest(const std::string& path, size_t revision); + static ResourceID resourceIDForScreencopy(const std::string& port); + struct SPreloadedTexture { ASP texture; size_t refs = 0; @@ -32,26 +42,35 @@ class CAsyncResourceManager { ~CAsyncResourceManager() = default; // requesting an asset returns a unique id used to retrieve it later - size_t requestText(const CTextResource::STextResourceData& request, std::function callback); + ResourceID requestText(const CTextResource::STextResourceData& params, const AWP& widget); // same as requestText but substitute the text with what launching sh -c request.text returns - size_t requestTextCmd(const CTextResource::STextResourceData& request, std::function callback); - size_t requestImage(const std::string& path, std::function callback); + ResourceID requestTextCmd(const CTextResource::STextResourceData& params, const AWP& widget); + ResourceID requestImage(const std::string& path, size_t revision, const AWP& widget); - ASP getAssetByID(size_t id); + ASP getAssetByID(ResourceID id); void unload(ASP resource); - void unloadById(size_t id); + void unloadById(ResourceID id); void enqueueStaticAssets(); void enqueueScreencopyFrames(); void screencopyToTexture(const CScreencopyFrame& scFrame); void gatherInitialResources(wl_display* display); + bool checkIdPresent(ResourceID id); + private: - void resourceToAsset(size_t id); + // returns whether or not the id was already requested + // makes sure the widgets onAssetCallback function gets called + bool request(ResourceID id, const AWP& widget); + // adds a new resource to m_resources and passes it to m_gatherer + void enqueue(ResourceID resourceID, const ASP& resource, const AWP& widget); + + void onResourceFinished(ResourceID id); void onScreencopyDone(); // for polling when using gatherInitialResources + bool m_gathered = false; Hyprutils::OS::CFileDescriptor m_gatheredEventfd; bool m_exit = false; @@ -59,13 +78,13 @@ class CAsyncResourceManager { int m_loadedAssets = 0; // not shared between threads - std::unordered_map m_assets; - std::vector> m_scFrames; + std::unordered_map m_assets; + std::vector> m_scFrames; // shared between threads - std::mutex m_resourcesMutex; - std::unordered_map> m_resources; + std::mutex m_resourcesMutex; + std::unordered_map, std::vector>>> m_resources; - Hyprgraphics::CAsyncResourceGatherer m_gatherer; + Hyprgraphics::CAsyncResourceGatherer m_gatherer; }; inline UP g_asyncResourceManager; diff --git a/src/renderer/Screencopy.cpp b/src/renderer/Screencopy.cpp index 3f63513d..e6dacf81 100644 --- a/src/renderer/Screencopy.cpp +++ b/src/renderer/Screencopy.cpp @@ -30,7 +30,7 @@ void CScreencopyFrame::capture(SP pOutput) { static const auto SCMODE = g_pConfigManager->getValue("general:screencopy_mode"); m_asset = makeAtomicShared(); - m_resourceID = (size_t)pOutput.get(); + m_resourceID = CAsyncResourceManager::resourceIDForScreencopy(pOutput->stringPort); m_sc = makeShared(g_pHyprlock->getScreencopy()->sendCaptureOutput(false, pOutput->m_wlOutput->resource())); @@ -113,6 +113,9 @@ CSCDMAFrame::~CSCDMAFrame() { if (g_pEGL) eglDestroyImage(g_pEGL->eglDisplay, m_image); + if (m_bo) + gbm_bo_destroy(m_bo); + // leaks bo and stuff but lives throughout so for now who cares } diff --git a/src/renderer/widgets/Background.cpp b/src/renderer/widgets/Background.cpp index 61abdf16..54248f1d 100644 --- a/src/renderer/widgets/Background.cpp +++ b/src/renderer/widgets/Background.cpp @@ -53,13 +53,13 @@ void CBackground::configure(const std::unordered_map& pro viewport = pOutput->getViewport(); outputPort = pOutput->stringPort; transform = wlTransformToHyprutils(invertTransform(pOutput->transform)); - scResourceID = pOutput->getScreencopyResourceID(); + scResourceID = CAsyncResourceManager::resourceIDForScreencopy(pOutput->stringPort); g_pAnimationManager->createAnimation(0.f, crossFadeProgress, g_pConfigManager->m_AnimationTree.getConfig("fadeIn")); // 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. - if (!g_asyncResourceManager->getAssetByID(scResourceID)) { + if (!g_asyncResourceManager->checkIdPresent(scResourceID)) { Debug::log(LOG, "Missing screenshot for output {}", outputPort); scResourceID = 0; } @@ -72,7 +72,7 @@ void CBackground::configure(const std::unordered_map& pro resourceID = 0; } } else if (!path.empty()) - resourceID = 0; + resourceID = g_asyncResourceManager->requestImage(path, m_imageRevision, nullptr); if (!isScreenshot && reloadTime > -1) { try { @@ -156,11 +156,6 @@ static void onReloadTimer(AWP ref) { } } -static void onAssetCallback(AWP ref) { - if (auto PBG = ref.lock(); PBG) - PBG->startCrossFade(); -} - static CBox getScaledBoxForTextureSize(const Vector2D& size, const Vector2D& viewport) { CBox texbox = {{}, size}; @@ -255,6 +250,39 @@ bool CBackground::draw(const SRenderData& data) { return crossFadeProgress->isBeingAnimated() || data.opacity < 1.0; } +void CBackground::onAssetUpdate(ASP newAsset) { + pendingResourceID = 0; + + if (!newAsset) + Debug::log(ERR, "Background asset update failed, resourceID: {} not available on update!", pendingResourceID); + else if (newAsset->m_iType == TEXTURE_INVALID) { + g_asyncResourceManager->unload(newAsset); + Debug::log(ERR, "New background asset has an invalid texture!"); + } else { + pendingAsset = newAsset; + crossFadeProgress->setValueAndWarp(0); + *crossFadeProgress = 1.0; + + crossFadeProgress->setCallbackOnEnd( + [REF = m_self](auto) { + if (const auto PSELF = REF.lock()) { + if (PSELF->asset) + g_asyncResourceManager->unload(PSELF->asset); + PSELF->asset = PSELF->pendingAsset; + PSELF->pendingAsset = nullptr; + PSELF->resourceID = PSELF->pendingResourceID; + PSELF->pendingResourceID = 0; + + PSELF->blurredFB->destroyBuffer(); + PSELF->blurredFB = std::move(PSELF->pendingBlurredFB); + } + }, + true); + + g_pHyprlock->renderOutput(outputPort); + } +} + void CBackground::plantReloadTimer() { if (reloadTime == 0) @@ -287,6 +315,10 @@ void CBackground::onReloadTimerUpdate() { return; modificationTime = MTIME; + if (OLDPATH == path) + m_imageRevision++; + else + m_imageRevision = 0; } catch (std::exception& e) { path = OLDPATH; Debug::log(ERR, "{}", e.what()); @@ -297,41 +329,6 @@ void CBackground::onReloadTimerUpdate() { return; // Issue the next request - pendingResourceID = g_asyncResourceManager->requestImage(path, [REF = m_self]() { onAssetCallback(REF); }); -} - -void CBackground::startCrossFade() { - auto newAsset = g_asyncResourceManager->getAssetByID(pendingResourceID); - if (newAsset) { - if (newAsset->m_iType == TEXTURE_INVALID) { - g_asyncResourceManager->unload(newAsset); - Debug::log(ERR, "New asset had an invalid texture!"); - pendingResourceID = 0; - } else if (resourceID != pendingResourceID) { - pendingAsset = newAsset; - crossFadeProgress->setValueAndWarp(0); - *crossFadeProgress = 1.0; - - crossFadeProgress->setCallbackOnEnd( - [REF = m_self](auto) { - if (const auto PSELF = REF.lock()) { - if (PSELF->asset) - g_asyncResourceManager->unload(PSELF->asset); - PSELF->asset = PSELF->pendingAsset; - PSELF->pendingAsset = nullptr; - PSELF->resourceID = PSELF->pendingResourceID; - PSELF->pendingResourceID = 0; - - PSELF->blurredFB->destroyBuffer(); - PSELF->blurredFB = std::move(PSELF->pendingBlurredFB); - } - }, - true); - - g_pHyprlock->renderOutput(outputPort); - } - } else if (pendingResourceID > 0) { - 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); - } + AWP widget(m_self); + pendingResourceID = g_asyncResourceManager->requestImage(path, m_imageRevision, widget); } diff --git a/src/renderer/widgets/Background.hpp b/src/renderer/widgets/Background.hpp index 27f0b0d8..36608a71 100644 --- a/src/renderer/widgets/Background.hpp +++ b/src/renderer/widgets/Background.hpp @@ -24,6 +24,7 @@ class CBackground : public IWidget { virtual void configure(const std::unordered_map& props, const SP& pOutput); virtual bool draw(const SRenderData& data); + virtual void onAssetUpdate(ASP newAsset); void reset(); // Unload assets, remove timers, etc. @@ -63,9 +64,9 @@ class CBackground : public IWidget { std::string outputPort; Hyprutils::Math::eTransform transform; - size_t resourceID; - size_t scResourceID; - size_t pendingResourceID; + ResourceID resourceID = 0; + ResourceID scResourceID = 0; + ResourceID pendingResourceID = 0; PHLANIMVAR crossFadeProgress; @@ -80,4 +81,5 @@ class CBackground : public IWidget { std::string reloadCommand; ASP reloadTimer; std::filesystem::file_time_type modificationTime; + size_t m_imageRevision = 0; }; diff --git a/src/renderer/widgets/IWidget.hpp b/src/renderer/widgets/IWidget.hpp index 5456602a..1db4847a 100644 --- a/src/renderer/widgets/IWidget.hpp +++ b/src/renderer/widgets/IWidget.hpp @@ -3,6 +3,8 @@ #include "../../defines.hpp" #include "../../helpers/Math.hpp" #include "../../core/Seat.hpp" +#include "../Texture.hpp" + #include #include #include @@ -20,6 +22,7 @@ class IWidget { virtual void configure(const std::unordered_map& prop, const SP& pOutput) = 0; virtual bool draw(const SRenderData& data) = 0; + virtual void onAssetUpdate(ASP newAsset) = 0; static Vector2D posFromHVAlign(const Vector2D& viewport, const Vector2D& size, const Vector2D& offset, const std::string& halign, const std::string& valign, const double& ang = 0); diff --git a/src/renderer/widgets/Image.cpp b/src/renderer/widgets/Image.cpp index 2c928b19..aceef7a9 100644 --- a/src/renderer/widgets/Image.cpp +++ b/src/renderer/widgets/Image.cpp @@ -24,11 +24,6 @@ static void onTimer(AWP ref) { } } -static void onAssetCallback(AWP ref) { - if (auto PIMAGE = ref.lock(); PIMAGE) - PIMAGE->renderUpdate(); -} - void CImage::onTimerUpdate() { const std::string OLDPATH = path; @@ -51,6 +46,10 @@ void CImage::onTimerUpdate() { return; modificationTime = MTIME; + if (OLDPATH == path) + m_imageRevision++; + else + m_imageRevision = 0; } catch (std::exception& e) { path = OLDPATH; Debug::log(ERR, "{}", e.what()); @@ -60,7 +59,8 @@ void CImage::onTimerUpdate() { if (pendingResourceID > 0) return; - pendingResourceID = g_asyncResourceManager->requestImage(path, [REF = m_self]() { onAssetCallback(REF); }); + AWP widget(m_self); + pendingResourceID = g_asyncResourceManager->requestImage(path, m_imageRevision, widget); } void CImage::plantTimer() { @@ -99,7 +99,7 @@ void CImage::configure(const std::unordered_map& props, c RASSERT(false, "Missing propperty for CImage: {}", e.what()); // } - resourceID = g_asyncResourceManager->requestImage(path, [REF = m_self]() { onAssetCallback(REF); }); + resourceID = g_asyncResourceManager->requestImage(path, m_imageRevision, nullptr); angle = angle * M_PI / 180.0; if (reloadTime > -1) { @@ -205,28 +205,24 @@ bool CImage::draw(const SRenderData& data) { return data.opacity < 1.0; } -void CImage::renderUpdate() { - auto newAsset = g_asyncResourceManager->getAssetByID(pendingResourceID); - if (newAsset) { - if (newAsset->m_iType == TEXTURE_INVALID) { - g_asyncResourceManager->unload(newAsset); - } else if (resourceID != pendingResourceID) { - g_asyncResourceManager->unload(asset); - imageFB.destroyBuffer(); - - asset = newAsset; - resourceID = pendingResourceID; - firstRender = true; - } - pendingResourceID = 0; - - } else if (pendingResourceID > 0) { - 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); - } +void CImage::onAssetUpdate(ASP newAsset) { + pendingResourceID = 0; - g_pHyprlock->renderOutput(stringPort); + if (!newAsset) + Debug::log(ERR, "asset update failed, resourceID: {} not available on update!", pendingResourceID); + else if (newAsset->m_iType == TEXTURE_INVALID) { + g_asyncResourceManager->unload(newAsset); + Debug::log(ERR, "New image asset has an invalid texture!"); + } else { + g_asyncResourceManager->unload(asset); + imageFB.destroyBuffer(); + + asset = newAsset; + resourceID = pendingResourceID; + firstRender = true; + + g_pHyprlock->renderOutput(stringPort); + } } CBox CImage::getBoundingBoxWl() const { diff --git a/src/renderer/widgets/Image.hpp b/src/renderer/widgets/Image.hpp index bc2a26e7..65447e25 100644 --- a/src/renderer/widgets/Image.hpp +++ b/src/renderer/widgets/Image.hpp @@ -24,6 +24,8 @@ class CImage : public IWidget { virtual void configure(const std::unordered_map& props, const SP& pOutput); virtual bool draw(const SRenderData& data); + virtual void onAssetUpdate(ASP newAsset); + virtual CBox getBoundingBoxWl() const; virtual void onClick(uint32_t button, bool down, const Vector2D& pos); virtual void onHover(const Vector2D& pos); @@ -39,10 +41,10 @@ class CImage : public IWidget { CFramebuffer imageFB; - int size; - int rounding; - double border; - double angle; + int size = 0; + int rounding = 0; + double border = 0; + double angle = 0; CGradientValueData color; Vector2D pos; Vector2D configPos; @@ -56,13 +58,16 @@ class CImage : public IWidget { std::string onclickCommand; std::filesystem::file_time_type modificationTime; + size_t m_imageRevision = 0; + ASP imageTimer; Vector2D viewport; std::string stringPort; - size_t resourceID; - size_t pendingResourceID; // if reloading image + ResourceID resourceID = 0; + ResourceID pendingResourceID = 0; + ASP asset = nullptr; CShadowable shadow; }; diff --git a/src/renderer/widgets/Label.cpp b/src/renderer/widgets/Label.cpp index 376d684f..1c3e5009 100644 --- a/src/renderer/widgets/Label.cpp +++ b/src/renderer/widgets/Label.cpp @@ -26,15 +26,6 @@ static void onTimer(AWP ref) { } } -static void onAssetCallback(AWP ref) { - if (auto PLABEL = ref.lock(); PLABEL) - PLABEL->renderUpdate(); -} - -std::string CLabel::getUniqueResourceId() { - return std::string{"label:"} + std::to_string((uintptr_t)this) + ",time:" + std::to_string(std::chrono::system_clock::now().time_since_epoch().count()); -} - void CLabel::onTimerUpdate() { std::string oldFormatted = label.formatted; @@ -49,9 +40,10 @@ void CLabel::onTimerUpdate() { } // request new - request.text = label.formatted; - pendingResourceID = (label.cmd) ? g_asyncResourceManager->requestTextCmd(request, [REF = m_self]() { onAssetCallback(REF); }) : - g_asyncResourceManager->requestText(request, [REF = m_self]() { onAssetCallback(REF); }); + request.text = label.formatted; + + AWP widget(m_self); + pendingResourceID = (label.cmd) ? g_asyncResourceManager->requestTextCmd(request, widget.lock()) : g_asyncResourceManager->requestText(request, widget.lock()); } void CLabel::plantTimer() { @@ -102,7 +94,7 @@ void CLabel::configure(const std::unordered_map& props, c pos = configPos; // Label size not known yet - resourceID = (label.cmd) ? g_asyncResourceManager->requestTextCmd(request, []() {}) : g_asyncResourceManager->requestText(request, []() {}); + resourceID = (label.cmd) ? g_asyncResourceManager->requestTextCmd(request, nullptr) : g_asyncResourceManager->requestText(request, nullptr); plantTimer(); } @@ -149,23 +141,23 @@ bool CLabel::draw(const SRenderData& data) { return false; } -void CLabel::renderUpdate() { - auto newAsset = g_asyncResourceManager->getAssetByID(pendingResourceID); - if (newAsset) { +void CLabel::onAssetUpdate(ASP newAsset) { + pendingResourceID = 0; + + if (!newAsset) + Debug::log(ERR, "asset update failed, resourceID: {} not available on update!", pendingResourceID); + else if (newAsset->m_iType == TEXTURE_INVALID) { + g_asyncResourceManager->unload(newAsset); + Debug::log(ERR, "New image asset has an invalid texture!"); + } else { // new asset is ready :D g_asyncResourceManager->unload(asset); - asset = newAsset; - resourceID = pendingResourceID; - pendingResourceID = 0; - updateShadow = true; - } else { - Debug::log(WARN, "Asset {} not available after the asyncResourceGatherer's callback!", pendingResourceID); + asset = newAsset; + resourceID = pendingResourceID; + updateShadow = true; - g_pHyprlock->addTimer(std::chrono::milliseconds(100), [REF = m_self](auto, auto) { onAssetCallback(REF); }, nullptr); - return; + g_pHyprlock->renderOutput(outputStringPort); } - - g_pHyprlock->renderOutput(outputStringPort); } CBox CLabel::getBoundingBoxWl() const { diff --git a/src/renderer/widgets/Label.hpp b/src/renderer/widgets/Label.hpp index b920a168..d980fc1b 100644 --- a/src/renderer/widgets/Label.hpp +++ b/src/renderer/widgets/Label.hpp @@ -22,6 +22,8 @@ class CLabel : public IWidget { virtual void configure(const std::unordered_map& prop, const SP& pOutput); virtual bool draw(const SRenderData& data); + virtual void onAssetUpdate(ASP newAsset); + virtual CBox getBoundingBoxWl() const; virtual void onClick(uint32_t button, bool down, const Vector2D& pos); virtual void onHover(const Vector2D& pos); @@ -35,19 +37,20 @@ class CLabel : public IWidget { private: AWP m_self; - std::string getUniqueResourceId(); - std::string labelPreFormat; IWidget::SFormatResult label; + std::string halign, valign; + std::string onclickCommand; + Vector2D viewport; Vector2D pos; Vector2D configPos; double angle; - size_t resourceID = 0; - size_t pendingResourceID = 0; // if dynamic label - std::string halign, valign; - std::string onclickCommand; + + ResourceID resourceID = 0; + ResourceID pendingResourceID = 0; + ASP asset = nullptr; std::string outputStringPort; diff --git a/src/renderer/widgets/PasswordInputField.cpp b/src/renderer/widgets/PasswordInputField.cpp index 33a9851c..39668fd3 100644 --- a/src/renderer/widgets/PasswordInputField.cpp +++ b/src/renderer/widgets/PasswordInputField.cpp @@ -8,6 +8,7 @@ #include "../../helpers/Log.hpp" #include "../../core/AnimationManager.hpp" #include "../../helpers/Color.hpp" +#include "src/renderer/widgets/IWidget.hpp" #include #include #include @@ -93,7 +94,7 @@ void CPasswordInputField::configure(const std::unordered_maprequestText(request, []() {}); + dots.textResourceID = g_asyncResourceManager->requestText(request, nullptr); } // request the inital placeholder asset @@ -327,7 +328,6 @@ bool CPasswordInputField::draw(const SRenderData& data) { void CPasswordInputField::updatePlaceholder() { if (passwordLength != 0) { if (placeholder.asset && /* keep prompt asset cause it is likely to be used again */ displayFail) { - std::erase(placeholder.registeredResourceIDs, placeholder.resourceID); g_asyncResourceManager->unload(placeholder.asset); placeholder.asset = nullptr; placeholder.resourceID = 0; @@ -349,22 +349,10 @@ void CPasswordInputField::updatePlaceholder() { if (!ALLOWCOLORSWAP && newText == placeholder.currentText) return; - // TODO: detect same text via resource manager - //const auto NEWRESOURCEID = std::format("placeholder:{}{}{}{}{}{}", newText, (uintptr_t)this, colorState.font.r, colorState.font.g, colorState.font.b, colorState.font.a); - - //if (placeholder.resourceID == NEWRESOURCEID) - // return; - - Debug::log(TRACE, "Updating placeholder text: {}", newText); + Debug::log(LOG, "Updating placeholder text: {}", newText); placeholder.currentText = newText; placeholder.asset = nullptr; - //if (std::ranges::find(placeholder.registeredResourceIDs, placeholder.resourceID) != placeholder.registeredResourceIDs.end()) - // return; - - Debug::log(TRACE, "Requesting new placeholder asset: {}", placeholder.resourceID); - placeholder.registeredResourceIDs.push_back(placeholder.resourceID); - // query Hyprgraphics::CTextResource::STextResourceData request; request.text = placeholder.currentText; @@ -372,10 +360,12 @@ void CPasswordInputField::updatePlaceholder() { request.color = colorState.font.asRGB(); request.fontSize = (int)size->value().y / 4; - placeholder.resourceID = g_asyncResourceManager->requestText(request, [REF = m_self] { - if (const auto SELF = REF.lock(); SELF) - g_pHyprlock->renderOutput(SELF->outputStringPort); - }); + AWP widget(m_self); + placeholder.resourceID = g_asyncResourceManager->requestText(request, widget); +} + +void CPasswordInputField::onAssetUpdate(ASP newAsset) { + g_pHyprlock->renderOutput(outputStringPort); } void CPasswordInputField::updateWidth() { diff --git a/src/renderer/widgets/PasswordInputField.hpp b/src/renderer/widgets/PasswordInputField.hpp index cb80b4a4..d5c00759 100644 --- a/src/renderer/widgets/PasswordInputField.hpp +++ b/src/renderer/widgets/PasswordInputField.hpp @@ -24,6 +24,8 @@ class CPasswordInputField : public IWidget { virtual void configure(const std::unordered_map& prop, const SP& pOutput); virtual bool draw(const SRenderData& data); + virtual void onAssetUpdate(ASP newAsset); + virtual void onHover(const Vector2D& pos); virtual CBox getBoundingBoxWl() const; @@ -78,13 +80,11 @@ class CPasswordInputField : public IWidget { } fade; struct { - size_t resourceID = 0; - ASP asset = nullptr; - - std::string currentText = ""; - size_t failedAttempts = 0; + size_t resourceID = 0; + ASP asset = nullptr; - std::vector registeredResourceIDs; + std::string currentText = ""; + size_t failedAttempts = 0; } placeholder; struct { diff --git a/src/renderer/widgets/Shape.cpp b/src/renderer/widgets/Shape.cpp index c1c6dc1e..6777720d 100644 --- a/src/renderer/widgets/Shape.cpp +++ b/src/renderer/widgets/Shape.cpp @@ -104,6 +104,11 @@ bool CShape::draw(const SRenderData& data) { return data.opacity < 1.0; } + +void CShape::onAssetUpdate(ASP newAsset) { + ; +} + CBox CShape::getBoundingBoxWl() const { return { Vector2D{pos.x, viewport.y - pos.y - size.y}, diff --git a/src/renderer/widgets/Shape.hpp b/src/renderer/widgets/Shape.hpp index 33a4e023..aaa8a592 100644 --- a/src/renderer/widgets/Shape.hpp +++ b/src/renderer/widgets/Shape.hpp @@ -18,6 +18,8 @@ class CShape : public IWidget { virtual void configure(const std::unordered_map& prop, const SP& pOutput); virtual bool draw(const SRenderData& data); + virtual void onAssetUpdate(ASP newAsset); + virtual CBox getBoundingBoxWl() const; virtual void onClick(uint32_t button, bool down, const Vector2D& pos); virtual void onHover(const Vector2D& pos); From a400f70830399d9cb16872ebae083e6bce2cd8b5 Mon Sep 17 00:00:00 2001 From: Maximilian Seidler Date: Thu, 25 Sep 2025 18:57:49 +0200 Subject: [PATCH 05/19] cmake: bump hyprgraphics for asyncResourceGatherer --- CMakeLists.txt | 2 +- src/renderer/AsyncResourceManager.cpp | 6 +++--- src/renderer/Screencopy.cpp | 5 +---- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a7506ddd..b3d6e4b2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -88,7 +88,7 @@ pkg_check_modules( gbm hyprutils>=0.8.0 sdbus-c++>=2.0.0 - hyprgraphics) + hyprgraphics>=0.1.6) find_library(PAM_FOUND pam) if(PAM_FOUND) message(STATUS "Found pam") diff --git a/src/renderer/AsyncResourceManager.cpp b/src/renderer/AsyncResourceManager.cpp index a25d6888..36988115 100644 --- a/src/renderer/AsyncResourceManager.cpp +++ b/src/renderer/AsyncResourceManager.cpp @@ -41,7 +41,7 @@ ResourceID CAsyncResourceManager::resourceIDForScreencopy(const std::string& por return scopeResourceID(4, std::hash{}(port)); } -size_t CAsyncResourceManager::requestText(const CTextResource::STextResourceData& params, const AWP& widget) { +ResourceID CAsyncResourceManager::requestText(const CTextResource::STextResourceData& params, const AWP& widget) { const auto RESOURCEID = resourceIDForTextRequest(params); if (request(RESOURCEID, widget)) { Debug::log(TRACE, "Text resource \"{}\" (resourceID: {}) already requested!", params.text, RESOURCEID); @@ -56,7 +56,7 @@ size_t CAsyncResourceManager::requestText(const CTextResource::STextResourceData return RESOURCEID; } -size_t CAsyncResourceManager::requestTextCmd(const CTextResource::STextResourceData& params, const AWP& widget) { +ResourceID CAsyncResourceManager::requestTextCmd(const CTextResource::STextResourceData& params, const AWP& widget) { const auto RESOURCEID = resourceIDForTextCmdRequest(params); if (request(RESOURCEID, widget)) { Debug::log(TRACE, "Text cmd resource \"{}\" (resourceID: {}) already requested!", params.text, RESOURCEID); @@ -71,7 +71,7 @@ size_t CAsyncResourceManager::requestTextCmd(const CTextResource::STextResourceD return RESOURCEID; } -size_t CAsyncResourceManager::requestImage(const std::string& path, size_t revision, const AWP& widget) { +ResourceID CAsyncResourceManager::requestImage(const std::string& path, size_t revision, const AWP& widget) { const auto RESOURCEID = resourceIDForImageRequest(path, revision); if (request(RESOURCEID, widget)) { Debug::log(TRACE, "Image resource {} revision {} (resourceID: {}) already requested!", path, revision, RESOURCEID); diff --git a/src/renderer/Screencopy.cpp b/src/renderer/Screencopy.cpp index e6dacf81..fa363e5d 100644 --- a/src/renderer/Screencopy.cpp +++ b/src/renderer/Screencopy.cpp @@ -1,10 +1,10 @@ #include "Screencopy.hpp" +#include "./AsyncResourceManager.hpp" #include "../helpers/Log.hpp" #include "../helpers/MiscFunctions.hpp" #include "../core/hyprlock.hpp" #include "../core/Egl.hpp" #include "../config/ConfigManager.hpp" -#include "src/renderer/AsyncResourceManager.hpp" #include "wlr-screencopy-unstable-v1.hpp" #include #include @@ -113,9 +113,6 @@ CSCDMAFrame::~CSCDMAFrame() { if (g_pEGL) eglDestroyImage(g_pEGL->eglDisplay, m_image); - if (m_bo) - gbm_bo_destroy(m_bo); - // leaks bo and stuff but lives throughout so for now who cares } From 4aa090d065648af711d49f0d2dbf707fde2246b0 Mon Sep 17 00:00:00 2001 From: Maximilian Seidler Date: Thu, 25 Sep 2025 19:32:41 +0200 Subject: [PATCH 06/19] bump nix --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 836f3a20..449743c9 100644 --- a/flake.lock +++ b/flake.lock @@ -13,11 +13,11 @@ ] }, "locked": { - "lastModified": 1758112956, - "narHash": "sha256-noG/SglFnGs+pdND/0XXu5vzjpgE5SwOrCM/6pa9H+Y=", + "lastModified": 1758572180, + "narHash": "sha256-Is8Rcp99Ynl3JFcU3k2lsmyf8WGacWKZtnVb0mVIZ6M=", "owner": "hyprwm", "repo": "hyprgraphics", - "rev": "b86c4d9ed353073e764fef286423c5cc6fb9318b", + "rev": "32e6b8386f7dc70a4cc01607a826a281f3c52364", "type": "github" }, "original": { From 07ada1456467889c80e5b480e10095d1053b7e08 Mon Sep 17 00:00:00 2001 From: Maximilian Seidler Date: Thu, 25 Sep 2025 19:39:10 +0200 Subject: [PATCH 07/19] remove bad include --- src/renderer/widgets/PasswordInputField.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/renderer/widgets/PasswordInputField.cpp b/src/renderer/widgets/PasswordInputField.cpp index 39668fd3..f5a8283a 100644 --- a/src/renderer/widgets/PasswordInputField.cpp +++ b/src/renderer/widgets/PasswordInputField.cpp @@ -8,7 +8,6 @@ #include "../../helpers/Log.hpp" #include "../../core/AnimationManager.hpp" #include "../../helpers/Color.hpp" -#include "src/renderer/widgets/IWidget.hpp" #include #include #include From b8982219d39e86ca48aecc737cdb3ef90aed2412 Mon Sep 17 00:00:00 2001 From: Maximilian Seidler Date: Fri, 26 Sep 2025 11:22:35 +0200 Subject: [PATCH 08/19] check for unloaded before finished and some cleanup --- src/renderer/AsyncResourceManager.cpp | 17 ++++++++--------- src/renderer/AsyncResourceManager.hpp | 11 +++++++---- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/renderer/AsyncResourceManager.cpp b/src/renderer/AsyncResourceManager.cpp index 36988115..987c52c4 100644 --- a/src/renderer/AsyncResourceManager.cpp +++ b/src/renderer/AsyncResourceManager.cpp @@ -146,8 +146,10 @@ void CAsyncResourceManager::screencopyToTexture(const CScreencopyFrame& scFrame) std::erase_if(m_scFrames, [&scFrame](const auto& f) { return f.get() == &scFrame; }); - if (m_scFrames.empty()) - onScreencopyDone(); + if (m_scFrames.empty()) { + Debug::log(TRACE, "Gathered all screencopy frames - removing dmabuf listeners"); + g_pHyprlock->removeDmabufListener(); + } } void CAsyncResourceManager::gatherInitialResources(wl_display* display) { @@ -292,10 +294,13 @@ void CAsyncResourceManager::onResourceFinished(ResourceID id) { m_resources.erase(id); m_resourcesMutex.unlock(); + if (!m_assets.contains(id) || m_assets[id].refs == 0) // Not referenced? Drop it + return; + if (!RESOURCE || !RESOURCE->m_asset.cairoSurface) return; - //Debug::log(TRACE, "Resource to texture id:{}", id); + Debug::log(TRACE, "Resource to texture id:{}", id); const auto texture = makeAtomicShared(); @@ -341,9 +346,3 @@ void CAsyncResourceManager::onResourceFinished(ResourceID id) { m_resourcesMutex.unlock(); } } - -void CAsyncResourceManager::onScreencopyDone() { - // We are done with screencopy. - //Debug::log(TRACE, "Gathered all screencopy frames - removing dmabuf listeners"); - g_pHyprlock->removeDmabufListener(); -} diff --git a/src/renderer/AsyncResourceManager.hpp b/src/renderer/AsyncResourceManager.hpp index e07059d4..ceda39b8 100644 --- a/src/renderer/AsyncResourceManager.hpp +++ b/src/renderer/AsyncResourceManager.hpp @@ -23,8 +23,9 @@ class CAsyncResourceManager { // Why not use ASP/AWP for this? // The problem is that we want to to increment the reference as soon as requesting the resource id. // Not only when actually retrieving the asset with `getAssetById`. - // Also, this way a resource is static as long as it is not unloaded by all instances that requested it. - // TODO:: Make a wrapper object that contains the resource id and unload with RAII. + // Managing the ref count here also allows for having an asset outlife it's original reference. + // + // Improvement idea: Make a wrapper object that contains the resource id and unload with RAII. // Those are hash functions that return the id for a requested resource. static ResourceID resourceIDForTextRequest(const CTextResource::STextResourceData& s); @@ -65,9 +66,11 @@ class CAsyncResourceManager { bool request(ResourceID id, const AWP& widget); // adds a new resource to m_resources and passes it to m_gatherer void enqueue(ResourceID resourceID, const ASP& resource, const AWP& widget); - + // callback for finished resoruces. + // copies the resources cairo surface to a GL_TEXTURE_2D and sets it in the asset map. + // removes the entry in m_resources. + // call onAssetUpdate for all stored widget references. void onResourceFinished(ResourceID id); - void onScreencopyDone(); // for polling when using gatherInitialResources bool m_gathered = false; From 4a6b588ca2855945868f630dca757521021323a8 Mon Sep 17 00:00:00 2001 From: Maximilian Seidler Date: Fri, 26 Sep 2025 11:33:05 +0200 Subject: [PATCH 09/19] typo --- src/renderer/AsyncResourceManager.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/AsyncResourceManager.hpp b/src/renderer/AsyncResourceManager.hpp index ceda39b8..f52c443d 100644 --- a/src/renderer/AsyncResourceManager.hpp +++ b/src/renderer/AsyncResourceManager.hpp @@ -66,7 +66,7 @@ class CAsyncResourceManager { bool request(ResourceID id, const AWP& widget); // adds a new resource to m_resources and passes it to m_gatherer void enqueue(ResourceID resourceID, const ASP& resource, const AWP& widget); - // callback for finished resoruces. + // callback for finished resources. // copies the resources cairo surface to a GL_TEXTURE_2D and sets it in the asset map. // removes the entry in m_resources. // call onAssetUpdate for all stored widget references. From 28f961a11166435e18efa34a786f884995cc127e Mon Sep 17 00:00:00 2001 From: Maximilian Seidler Date: Fri, 26 Sep 2025 16:53:57 +0200 Subject: [PATCH 10/19] improve comments --- src/renderer/AsyncResourceManager.hpp | 31 ++++++++++++--------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/src/renderer/AsyncResourceManager.hpp b/src/renderer/AsyncResourceManager.hpp index f52c443d..bb502a79 100644 --- a/src/renderer/AsyncResourceManager.hpp +++ b/src/renderer/AsyncResourceManager.hpp @@ -16,21 +16,19 @@ class CAsyncResourceManager { public: // Notes on resource lifetimes: // Resources id's are the result of hashing the requested resource parameters. - // When a new request is made, adding a new entry to the m_assets map is done immediatly - // within a critical section. Subsequent passes through this section with the same resource id - // will increment the texture's references. The manager will release the resource when refs reaches 0, - // while the resource itelf may outlife it's reference in the manager. + // When a new request is made, adding a new entry to the m_assets map is done immediatly. + // Subsequent calls through this section with the same resource id will increment the texture's references. + // The manager will release the resource when refs reaches 0, while the resource itelf may outlife it's reference in the manager. // Why not use ASP/AWP for this? // The problem is that we want to to increment the reference as soon as requesting the resource id. // Not only when actually retrieving the asset with `getAssetById`. - // Managing the ref count here also allows for having an asset outlife it's original reference. // - // Improvement idea: Make a wrapper object that contains the resource id and unload with RAII. + // Improvement idea: Make a wrapper object that is returned when requesting and contains the resource id. Then we can unload with RAII. // Those are hash functions that return the id for a requested resource. static ResourceID resourceIDForTextRequest(const CTextResource::STextResourceData& s); static ResourceID resourceIDForTextCmdRequest(const CTextResource::STextResourceData& s); - // image paths may be file system links, thus this function supports a revision parameter that gets factored into the resource id. + // Image paths may be file system links, thus this function supports a revision parameter that gets factored into the resource id. static ResourceID resourceIDForImageRequest(const std::string& path, size_t revision); static ResourceID resourceIDForScreencopy(const std::string& port); @@ -42,9 +40,8 @@ class CAsyncResourceManager { CAsyncResourceManager() = default; ~CAsyncResourceManager() = default; - // requesting an asset returns a unique id used to retrieve it later ResourceID requestText(const CTextResource::STextResourceData& params, const AWP& widget); - // same as requestText but substitute the text with what launching sh -c request.text returns + // Same as requestText but substitute the text with what launching sh -c request.text returns. ResourceID requestTextCmd(const CTextResource::STextResourceData& params, const AWP& widget); ResourceID requestImage(const std::string& path, size_t revision, const AWP& widget); @@ -61,18 +58,18 @@ class CAsyncResourceManager { bool checkIdPresent(ResourceID id); private: - // returns whether or not the id was already requested - // makes sure the widgets onAssetCallback function gets called + // Returns whether or not the id was already requested. + // Makes sure the widgets onAssetCallback function gets called. bool request(ResourceID id, const AWP& widget); - // adds a new resource to m_resources and passes it to m_gatherer + // Adds a new resource to m_resources and passes it to m_gatherer. void enqueue(ResourceID resourceID, const ASP& resource, const AWP& widget); - // callback for finished resources. - // copies the resources cairo surface to a GL_TEXTURE_2D and sets it in the asset map. - // removes the entry in m_resources. - // call onAssetUpdate for all stored widget references. + // Callback for finished resources. + // Copies the resources cairo surface to a GL_TEXTURE_2D and sets it in the asset map. + // Removes the entry in m_resources. + // Call onAssetUpdate for all stored widget references. void onResourceFinished(ResourceID id); - // for polling when using gatherInitialResources + // For polling when using gatherInitialResources. bool m_gathered = false; Hyprutils::OS::CFileDescriptor m_gatheredEventfd; From 4734f8bcd49ae81499c62ea6da95f796137bbd0b Mon Sep 17 00:00:00 2001 From: Maximilian Seidler Date: Mon, 29 Sep 2025 18:03:56 +0200 Subject: [PATCH 11/19] also allow for dynamic label resource deduplication use a simple counter instead of a timestamp to allow the same widget on a different monitor to reuse a text cmd resource. I didn't do this before, because I was worried about two labels that use the same command with different reload times. I mitigated that by just incrementing the revision by the time interval. This should be sufficent to avoid clashes. --- src/renderer/AsyncResourceManager.cpp | 30 +++++++++++++++------------ src/renderer/AsyncResourceManager.hpp | 5 +++-- src/renderer/widgets/Label.cpp | 12 +++++++++-- src/renderer/widgets/Label.hpp | 2 ++ 4 files changed, 32 insertions(+), 17 deletions(-) diff --git a/src/renderer/AsyncResourceManager.cpp b/src/renderer/AsyncResourceManager.cpp index 987c52c4..88a66a52 100644 --- a/src/renderer/AsyncResourceManager.cpp +++ b/src/renderer/AsyncResourceManager.cpp @@ -29,12 +29,12 @@ ResourceID CAsyncResourceManager::resourceIDForTextRequest(const CTextResource:: return scopeResourceID(1, H1 ^ (H2 << 1) ^ (H3 << 2) ^ (H4 << 3)); } -ResourceID CAsyncResourceManager::resourceIDForTextCmdRequest(const CTextResource::STextResourceData& s) { - return scopeResourceID(2, resourceIDForTextRequest(s) ^ std::hash{}(std::chrono::system_clock::now().time_since_epoch().count())); +ResourceID CAsyncResourceManager::resourceIDForTextCmdRequest(const CTextResource::STextResourceData& s, size_t revision) { + return scopeResourceID(2, resourceIDForTextRequest(s) ^ (revision << 32)); } ResourceID CAsyncResourceManager::resourceIDForImageRequest(const std::string& path, size_t revision) { - return scopeResourceID(3, std::hash{}(path) ^ std::hash{}(revision)); + return scopeResourceID(3, std::hash{}(path) ^ (revision << 32)); } ResourceID CAsyncResourceManager::resourceIDForScreencopy(const std::string& port) { @@ -44,29 +44,29 @@ ResourceID CAsyncResourceManager::resourceIDForScreencopy(const std::string& por ResourceID CAsyncResourceManager::requestText(const CTextResource::STextResourceData& params, const AWP& widget) { const auto RESOURCEID = resourceIDForTextRequest(params); if (request(RESOURCEID, widget)) { - Debug::log(TRACE, "Text resource \"{}\" (resourceID: {}) already requested!", params.text, RESOURCEID); + Debug::log(TRACE, "Reusing text resource \"{}\" (resourceID: {}, widget: 0x{})", params.text, RESOURCEID, (uintptr_t)widget.get()); return RESOURCEID; } auto resource = makeAtomicShared(CTextResource::STextResourceData{params}); CAtomicSharedPointer resourceGeneric{resource}; - Debug::log(LOG, "Requesting text resource \"{}\" (resourceID: {})", params.text, RESOURCEID); + Debug::log(TRACE, "Requesting text resource \"{}\" (resourceID: {}, widget: 0x{})", params.text, RESOURCEID, (uintptr_t)widget.get()); enqueue(RESOURCEID, resourceGeneric, widget); return RESOURCEID; } -ResourceID CAsyncResourceManager::requestTextCmd(const CTextResource::STextResourceData& params, const AWP& widget) { - const auto RESOURCEID = resourceIDForTextCmdRequest(params); +ResourceID CAsyncResourceManager::requestTextCmd(const CTextResource::STextResourceData& params, size_t revision, const AWP& widget) { + const auto RESOURCEID = resourceIDForTextCmdRequest(params, revision); if (request(RESOURCEID, widget)) { - Debug::log(TRACE, "Text cmd resource \"{}\" (resourceID: {}) already requested!", params.text, RESOURCEID); + Debug::log(TRACE, "Reusing text cmd resource \"{}\" revision {} (resourceID: {}, widget: 0x{})", params.text, revision, RESOURCEID, (uintptr_t)widget.get()); return RESOURCEID; } auto resource = makeAtomicShared(CTextResource::STextResourceData{params}); CAtomicSharedPointer resourceGeneric{resource}; - Debug::log(LOG, "Enqueued text cmd resource `{}` (resourceID: {})", params.text, RESOURCEID); + Debug::log(TRACE, "Requesting text cmd resource \"{}\" revision {} (resourceID: {}, widget: 0x{})", params.text, revision, RESOURCEID, (uintptr_t)widget.get()); enqueue(RESOURCEID, resourceGeneric, widget); return RESOURCEID; } @@ -74,14 +74,14 @@ ResourceID CAsyncResourceManager::requestTextCmd(const CTextResource::STextResou ResourceID CAsyncResourceManager::requestImage(const std::string& path, size_t revision, const AWP& widget) { const auto RESOURCEID = resourceIDForImageRequest(path, revision); if (request(RESOURCEID, widget)) { - Debug::log(TRACE, "Image resource {} revision {} (resourceID: {}) already requested!", path, revision, RESOURCEID); + Debug::log(TRACE, "Reusing image resource {} revision {} (resourceID: {}, widget: 0x{})", path, revision, RESOURCEID, (uintptr_t)widget.get()); return RESOURCEID; } auto resource = makeAtomicShared(absolutePath(path, "")); CAtomicSharedPointer resourceGeneric{resource}; - Debug::log(LOG, "Image resource {} revision {} (resourceID: {})", path, revision, RESOURCEID); + Debug::log(TRACE, "Requesting image resource {} revision {} (resourceID: {}, widget: 0x{})", path, revision, RESOURCEID, (uintptr_t)widget.get()); enqueue(RESOURCEID, resourceGeneric, widget); return RESOURCEID; } @@ -139,7 +139,11 @@ void CAsyncResourceManager::enqueueScreencopyFrames() { } void CAsyncResourceManager::screencopyToTexture(const CScreencopyFrame& scFrame) { - RASSERT(scFrame.m_ready && m_assets.contains(scFrame.m_resourceID), "Logic error in screencopy gathering."); + if (!scFrame.m_ready || !m_assets.contains(scFrame.m_resourceID)) { + Debug::log(ERR, "Bogus call to CAsyncResourceManager::screencopyToTexture. This is a bug!"); + return; + } + m_assets[scFrame.m_resourceID].texture = scFrame.m_asset; Debug::log(TRACE, "Done sc frame {}", scFrame.m_resourceID); @@ -147,7 +151,7 @@ void CAsyncResourceManager::screencopyToTexture(const CScreencopyFrame& scFrame) std::erase_if(m_scFrames, [&scFrame](const auto& f) { return f.get() == &scFrame; }); if (m_scFrames.empty()) { - Debug::log(TRACE, "Gathered all screencopy frames - removing dmabuf listeners"); + Debug::log(LOG, "Gathered all screencopy frames - removing dmabuf listeners"); g_pHyprlock->removeDmabufListener(); } } diff --git a/src/renderer/AsyncResourceManager.hpp b/src/renderer/AsyncResourceManager.hpp index bb502a79..78c892e4 100644 --- a/src/renderer/AsyncResourceManager.hpp +++ b/src/renderer/AsyncResourceManager.hpp @@ -27,7 +27,8 @@ class CAsyncResourceManager { // Those are hash functions that return the id for a requested resource. static ResourceID resourceIDForTextRequest(const CTextResource::STextResourceData& s); - static ResourceID resourceIDForTextCmdRequest(const CTextResource::STextResourceData& s); + // Consumer needs to increment the revision parameter to get a new command evaluation. + static ResourceID resourceIDForTextCmdRequest(const CTextResource::STextResourceData& s, size_t revision); // Image paths may be file system links, thus this function supports a revision parameter that gets factored into the resource id. static ResourceID resourceIDForImageRequest(const std::string& path, size_t revision); static ResourceID resourceIDForScreencopy(const std::string& port); @@ -42,7 +43,7 @@ class CAsyncResourceManager { ResourceID requestText(const CTextResource::STextResourceData& params, const AWP& widget); // Same as requestText but substitute the text with what launching sh -c request.text returns. - ResourceID requestTextCmd(const CTextResource::STextResourceData& params, const AWP& widget); + ResourceID requestTextCmd(const CTextResource::STextResourceData& params, size_t revision, const AWP& widget); ResourceID requestImage(const std::string& path, size_t revision, const AWP& widget); ASP getAssetByID(ResourceID id); diff --git a/src/renderer/widgets/Label.cpp b/src/renderer/widgets/Label.cpp index 1c3e5009..a48bfcb2 100644 --- a/src/renderer/widgets/Label.cpp +++ b/src/renderer/widgets/Label.cpp @@ -43,7 +43,12 @@ void CLabel::onTimerUpdate() { request.text = label.formatted; AWP widget(m_self); - pendingResourceID = (label.cmd) ? g_asyncResourceManager->requestTextCmd(request, widget.lock()) : g_asyncResourceManager->requestText(request, widget.lock()); + if (label.cmd) { + // Don't increment by one to avoid clashes with multiple widget using the same label command. + m_dynamicRevision += label.updateEveryMs; + pendingResourceID = g_asyncResourceManager->requestTextCmd(request, m_dynamicRevision, widget.lock()); + } else + pendingResourceID = g_asyncResourceManager->requestText(request, widget.lock()); } void CLabel::plantTimer() { @@ -94,7 +99,10 @@ void CLabel::configure(const std::unordered_map& props, c pos = configPos; // Label size not known yet - resourceID = (label.cmd) ? g_asyncResourceManager->requestTextCmd(request, nullptr) : g_asyncResourceManager->requestText(request, nullptr); + if (label.cmd) { + resourceID = g_asyncResourceManager->requestTextCmd(request, m_dynamicRevision, nullptr); + } else + resourceID = g_asyncResourceManager->requestText(request, nullptr); plantTimer(); } diff --git a/src/renderer/widgets/Label.hpp b/src/renderer/widgets/Label.hpp index d980fc1b..ff4dfcfd 100644 --- a/src/renderer/widgets/Label.hpp +++ b/src/renderer/widgets/Label.hpp @@ -51,6 +51,8 @@ class CLabel : public IWidget { ResourceID resourceID = 0; ResourceID pendingResourceID = 0; + size_t m_dynamicRevision = 0; + ASP asset = nullptr; std::string outputStringPort; From b4b6eb7718b8163d5ae964419d3eabe09fa5eba3 Mon Sep 17 00:00:00 2001 From: Maximilian Seidler Date: Mon, 29 Sep 2025 18:55:23 +0200 Subject: [PATCH 12/19] don't render within onAssetUpdate to avoid duplicate renders another much improvement for multi monitor setups. allows updating within the same frame for most labels. --- src/renderer/AsyncResourceManager.cpp | 28 +++++++++---------- src/renderer/widgets/Background.cpp | 4 +-- src/renderer/widgets/Background.hpp | 2 +- src/renderer/widgets/IWidget.hpp | 7 +++-- src/renderer/widgets/Image.cpp | 22 ++++++++------- src/renderer/widgets/Image.hpp | 4 +-- src/renderer/widgets/Label.cpp | 31 +++++++++++---------- src/renderer/widgets/Label.hpp | 4 +-- src/renderer/widgets/PasswordInputField.cpp | 4 +-- src/renderer/widgets/PasswordInputField.hpp | 2 +- src/renderer/widgets/Shape.cpp | 2 +- src/renderer/widgets/Shape.hpp | 2 +- 12 files changed, 57 insertions(+), 55 deletions(-) diff --git a/src/renderer/AsyncResourceManager.cpp b/src/renderer/AsyncResourceManager.cpp index 88a66a52..a015e811 100644 --- a/src/renderer/AsyncResourceManager.cpp +++ b/src/renderer/AsyncResourceManager.cpp @@ -44,14 +44,14 @@ ResourceID CAsyncResourceManager::resourceIDForScreencopy(const std::string& por ResourceID CAsyncResourceManager::requestText(const CTextResource::STextResourceData& params, const AWP& widget) { const auto RESOURCEID = resourceIDForTextRequest(params); if (request(RESOURCEID, widget)) { - Debug::log(TRACE, "Reusing text resource \"{}\" (resourceID: {}, widget: 0x{})", params.text, RESOURCEID, (uintptr_t)widget.get()); + Debug::log(TRACE, "Reusing text resource \"{}\" (resourceID: {})", params.text, RESOURCEID, (uintptr_t)widget.get()); return RESOURCEID; } auto resource = makeAtomicShared(CTextResource::STextResourceData{params}); CAtomicSharedPointer resourceGeneric{resource}; - Debug::log(TRACE, "Requesting text resource \"{}\" (resourceID: {}, widget: 0x{})", params.text, RESOURCEID, (uintptr_t)widget.get()); + Debug::log(TRACE, "Requesting text resource \"{}\" (resourceID: {})", params.text, RESOURCEID, (uintptr_t)widget.get()); enqueue(RESOURCEID, resourceGeneric, widget); return RESOURCEID; } @@ -59,14 +59,14 @@ ResourceID CAsyncResourceManager::requestText(const CTextResource::STextResource ResourceID CAsyncResourceManager::requestTextCmd(const CTextResource::STextResourceData& params, size_t revision, const AWP& widget) { const auto RESOURCEID = resourceIDForTextCmdRequest(params, revision); if (request(RESOURCEID, widget)) { - Debug::log(TRACE, "Reusing text cmd resource \"{}\" revision {} (resourceID: {}, widget: 0x{})", params.text, revision, RESOURCEID, (uintptr_t)widget.get()); + Debug::log(TRACE, "Reusing text cmd resource \"{}\" revision {} (resourceID: {})", params.text, revision, RESOURCEID, (uintptr_t)widget.get()); return RESOURCEID; } auto resource = makeAtomicShared(CTextResource::STextResourceData{params}); CAtomicSharedPointer resourceGeneric{resource}; - Debug::log(TRACE, "Requesting text cmd resource \"{}\" revision {} (resourceID: {}, widget: 0x{})", params.text, revision, RESOURCEID, (uintptr_t)widget.get()); + Debug::log(TRACE, "Requesting text cmd resource \"{}\" revision {} (resourceID: {})", params.text, revision, RESOURCEID, (uintptr_t)widget.get()); enqueue(RESOURCEID, resourceGeneric, widget); return RESOURCEID; } @@ -74,14 +74,14 @@ ResourceID CAsyncResourceManager::requestTextCmd(const CTextResource::STextResou ResourceID CAsyncResourceManager::requestImage(const std::string& path, size_t revision, const AWP& widget) { const auto RESOURCEID = resourceIDForImageRequest(path, revision); if (request(RESOURCEID, widget)) { - Debug::log(TRACE, "Reusing image resource {} revision {} (resourceID: {}, widget: 0x{})", path, revision, RESOURCEID, (uintptr_t)widget.get()); + Debug::log(TRACE, "Reusing image resource {} revision {} (resourceID: {})", path, revision, RESOURCEID, (uintptr_t)widget.get()); return RESOURCEID; } auto resource = makeAtomicShared(absolutePath(path, "")); CAtomicSharedPointer resourceGeneric{resource}; - Debug::log(TRACE, "Requesting image resource {} revision {} (resourceID: {}, widget: 0x{})", path, revision, RESOURCEID, (uintptr_t)widget.get()); + Debug::log(TRACE, "Requesting image resource {} revision {} (resourceID: {})", path, revision, RESOURCEID, (uintptr_t)widget.get()); enqueue(RESOURCEID, resourceGeneric, widget); return RESOURCEID; } @@ -248,14 +248,12 @@ bool CAsyncResourceManager::request(ResourceID id, const AWP& widget) { if (m_assets[id].texture) { // Asset already present. Dispatch the asset callback function. const auto& TEXTURE = m_assets[id].texture; + Debug::log(LOG, "onAssetUpdate {}", id); if (widget) - g_pHyprlock->addTimer( - std::chrono::milliseconds(0), - [widget, TEXTURE](auto, auto) { - if (const auto WIDGET = widget.lock(); WIDGET) - WIDGET->onAssetUpdate(TEXTURE); - }, - nullptr); + widget->onAssetUpdate(id, TEXTURE); + + // TODO: add a centalized mechanism to render in one place in the event loop to avoid duplicate render calls + g_pHyprlock->addTimer(std::chrono::milliseconds(0), [](auto, auto) { g_pHyprlock->renderAllOutputs(); }, nullptr); } else if (widget) { // Asset currently in-flight. Add the widget reference to in order for the callback to get dispatched later. m_resourcesMutex.lock(); @@ -335,9 +333,11 @@ void CAsyncResourceManager::onResourceFinished(ResourceID id) { for (const auto& widget : WIDGETS) { if (widget) - widget->onAssetUpdate(texture); + widget->onAssetUpdate(id, texture); } + g_pHyprlock->renderAllOutputs(); + if (!m_gathered && !g_pHyprlock->m_bImmediateRender) { m_resourcesMutex.lock(); if (m_resources.empty()) { diff --git a/src/renderer/widgets/Background.cpp b/src/renderer/widgets/Background.cpp index 54248f1d..c9383406 100644 --- a/src/renderer/widgets/Background.cpp +++ b/src/renderer/widgets/Background.cpp @@ -250,7 +250,7 @@ bool CBackground::draw(const SRenderData& data) { return crossFadeProgress->isBeingAnimated() || data.opacity < 1.0; } -void CBackground::onAssetUpdate(ASP newAsset) { +void CBackground::onAssetUpdate(ResourceID id, ASP newAsset) { pendingResourceID = 0; if (!newAsset) @@ -278,8 +278,6 @@ void CBackground::onAssetUpdate(ASP newAsset) { } }, true); - - g_pHyprlock->renderOutput(outputPort); } } diff --git a/src/renderer/widgets/Background.hpp b/src/renderer/widgets/Background.hpp index 36608a71..52784a73 100644 --- a/src/renderer/widgets/Background.hpp +++ b/src/renderer/widgets/Background.hpp @@ -24,7 +24,7 @@ class CBackground : public IWidget { virtual void configure(const std::unordered_map& props, const SP& pOutput); virtual bool draw(const SRenderData& data); - virtual void onAssetUpdate(ASP newAsset); + virtual void onAssetUpdate(ResourceID id, ASP newAsset); void reset(); // Unload assets, remove timers, etc. diff --git a/src/renderer/widgets/IWidget.hpp b/src/renderer/widgets/IWidget.hpp index 1db4847a..15df3646 100644 --- a/src/renderer/widgets/IWidget.hpp +++ b/src/renderer/widgets/IWidget.hpp @@ -20,9 +20,10 @@ class IWidget { virtual ~IWidget() = default; - virtual void configure(const std::unordered_map& prop, const SP& pOutput) = 0; - virtual bool draw(const SRenderData& data) = 0; - virtual void onAssetUpdate(ASP newAsset) = 0; + virtual void configure(const std::unordered_map& prop, const SP& pOutput) = 0; + virtual bool draw(const SRenderData& data) = 0; + // Never render within onAssetUpdate! + virtual void onAssetUpdate(ResourceID id, ASP newAsset) = 0; static Vector2D posFromHVAlign(const Vector2D& viewport, const Vector2D& size, const Vector2D& offset, const std::string& halign, const std::string& valign, const double& ang = 0); diff --git a/src/renderer/widgets/Image.cpp b/src/renderer/widgets/Image.cpp index aceef7a9..1846d70f 100644 --- a/src/renderer/widgets/Image.cpp +++ b/src/renderer/widgets/Image.cpp @@ -25,6 +25,11 @@ static void onTimer(AWP ref) { } void CImage::onTimerUpdate() { + if (m_pendingResource) { + Debug::log(WARN, "Trying to update image, but a resource is still pending! Skipping update."); + return; + } + const std::string OLDPATH = path; if (!reloadCommand.empty()) { @@ -56,11 +61,10 @@ void CImage::onTimerUpdate() { return; } - if (pendingResourceID > 0) - return; + m_pendingResource = true; AWP widget(m_self); - pendingResourceID = g_asyncResourceManager->requestImage(path, m_imageRevision, widget); + g_asyncResourceManager->requestImage(path, m_imageRevision, widget); } void CImage::plantTimer() { @@ -126,7 +130,7 @@ void CImage::reset() { g_asyncResourceManager->unload(asset); asset = nullptr; - pendingResourceID = 0; + m_pendingResource = false; resourceID = 0; } @@ -205,11 +209,11 @@ bool CImage::draw(const SRenderData& data) { return data.opacity < 1.0; } -void CImage::onAssetUpdate(ASP newAsset) { - pendingResourceID = 0; +void CImage::onAssetUpdate(ResourceID id, ASP newAsset) { + m_pendingResource = false; if (!newAsset) - Debug::log(ERR, "asset update failed, resourceID: {} not available on update!", pendingResourceID); + Debug::log(ERR, "asset update failed, resourceID: {} not available on update!", id); else if (newAsset->m_iType == TEXTURE_INVALID) { g_asyncResourceManager->unload(newAsset); Debug::log(ERR, "New image asset has an invalid texture!"); @@ -218,10 +222,8 @@ void CImage::onAssetUpdate(ASP newAsset) { imageFB.destroyBuffer(); asset = newAsset; - resourceID = pendingResourceID; + resourceID = id; firstRender = true; - - g_pHyprlock->renderOutput(stringPort); } } diff --git a/src/renderer/widgets/Image.hpp b/src/renderer/widgets/Image.hpp index 65447e25..03a3597a 100644 --- a/src/renderer/widgets/Image.hpp +++ b/src/renderer/widgets/Image.hpp @@ -24,7 +24,7 @@ class CImage : public IWidget { virtual void configure(const std::unordered_map& props, const SP& pOutput); virtual bool draw(const SRenderData& data); - virtual void onAssetUpdate(ASP newAsset); + virtual void onAssetUpdate(ResourceID id, ASP newAsset); virtual CBox getBoundingBoxWl() const; virtual void onClick(uint32_t button, bool down, const Vector2D& pos); @@ -66,7 +66,7 @@ class CImage : public IWidget { std::string stringPort; ResourceID resourceID = 0; - ResourceID pendingResourceID = 0; + bool m_pendingResource = false; ASP asset = nullptr; CShadowable shadow; diff --git a/src/renderer/widgets/Label.cpp b/src/renderer/widgets/Label.cpp index a48bfcb2..f391072b 100644 --- a/src/renderer/widgets/Label.cpp +++ b/src/renderer/widgets/Label.cpp @@ -6,6 +6,7 @@ #include "../../helpers/Color.hpp" #include "../../helpers/MiscFunctions.hpp" #include "../../config/ConfigDataValues.hpp" +#include "src/defines.hpp" #include #include @@ -27,6 +28,11 @@ static void onTimer(AWP ref) { } void CLabel::onTimerUpdate() { + if (m_pendingResource) { + Debug::log(WARN, "Trying to update label, but a resource is still pending! Skipping update."); + return; + } + std::string oldFormatted = label.formatted; label = formatString(labelPreFormat); @@ -34,21 +40,17 @@ void CLabel::onTimerUpdate() { if (label.formatted == oldFormatted && !label.alwaysUpdate) return; - if (pendingResourceID > 0) { - Debug::log(WARN, "Trying to update label, but resource {} is still pending! Skipping update.", pendingResourceID); - return; - } - // request new - request.text = label.formatted; + request.text = label.formatted; + m_pendingResource = true; AWP widget(m_self); if (label.cmd) { // Don't increment by one to avoid clashes with multiple widget using the same label command. m_dynamicRevision += label.updateEveryMs; - pendingResourceID = g_asyncResourceManager->requestTextCmd(request, m_dynamicRevision, widget.lock()); + g_asyncResourceManager->requestTextCmd(request, m_dynamicRevision, widget.lock()); } else - pendingResourceID = g_asyncResourceManager->requestText(request, widget.lock()); + g_asyncResourceManager->requestText(request, widget.lock()); } void CLabel::plantTimer() { @@ -120,7 +122,7 @@ void CLabel::reset() { g_asyncResourceManager->unload(asset); asset = nullptr; - pendingResourceID = 0; + m_pendingResource = false; resourceID = 0; } @@ -149,11 +151,12 @@ bool CLabel::draw(const SRenderData& data) { return false; } -void CLabel::onAssetUpdate(ASP newAsset) { - pendingResourceID = 0; +void CLabel::onAssetUpdate(ResourceID id, ASP newAsset) { + Debug::log(LOG, "Label update for resourceID {}", id); + m_pendingResource = false; if (!newAsset) - Debug::log(ERR, "asset update failed, resourceID: {} not available on update!", pendingResourceID); + Debug::log(ERR, "asset update failed, resourceID: {} not available on update!", id); else if (newAsset->m_iType == TEXTURE_INVALID) { g_asyncResourceManager->unload(newAsset); Debug::log(ERR, "New image asset has an invalid texture!"); @@ -161,10 +164,8 @@ void CLabel::onAssetUpdate(ASP newAsset) { // new asset is ready :D g_asyncResourceManager->unload(asset); asset = newAsset; - resourceID = pendingResourceID; + resourceID = id; updateShadow = true; - - g_pHyprlock->renderOutput(outputStringPort); } } diff --git a/src/renderer/widgets/Label.hpp b/src/renderer/widgets/Label.hpp index ff4dfcfd..45294327 100644 --- a/src/renderer/widgets/Label.hpp +++ b/src/renderer/widgets/Label.hpp @@ -22,7 +22,7 @@ class CLabel : public IWidget { virtual void configure(const std::unordered_map& prop, const SP& pOutput); virtual bool draw(const SRenderData& data); - virtual void onAssetUpdate(ASP newAsset); + virtual void onAssetUpdate(ResourceID id, ASP newAsset); virtual CBox getBoundingBoxWl() const; virtual void onClick(uint32_t button, bool down, const Vector2D& pos); @@ -49,7 +49,7 @@ class CLabel : public IWidget { double angle; ResourceID resourceID = 0; - ResourceID pendingResourceID = 0; + bool m_pendingResource = false; size_t m_dynamicRevision = 0; diff --git a/src/renderer/widgets/PasswordInputField.cpp b/src/renderer/widgets/PasswordInputField.cpp index f5a8283a..e7f63ee5 100644 --- a/src/renderer/widgets/PasswordInputField.cpp +++ b/src/renderer/widgets/PasswordInputField.cpp @@ -363,8 +363,8 @@ void CPasswordInputField::updatePlaceholder() { placeholder.resourceID = g_asyncResourceManager->requestText(request, widget); } -void CPasswordInputField::onAssetUpdate(ASP newAsset) { - g_pHyprlock->renderOutput(outputStringPort); +void CPasswordInputField::onAssetUpdate(ResourceID id, ASP newAsset) { + ; } void CPasswordInputField::updateWidth() { diff --git a/src/renderer/widgets/PasswordInputField.hpp b/src/renderer/widgets/PasswordInputField.hpp index d5c00759..ec0aa11f 100644 --- a/src/renderer/widgets/PasswordInputField.hpp +++ b/src/renderer/widgets/PasswordInputField.hpp @@ -24,7 +24,7 @@ class CPasswordInputField : public IWidget { virtual void configure(const std::unordered_map& prop, const SP& pOutput); virtual bool draw(const SRenderData& data); - virtual void onAssetUpdate(ASP newAsset); + virtual void onAssetUpdate(ResourceID id, ASP newAsset); virtual void onHover(const Vector2D& pos); virtual CBox getBoundingBoxWl() const; diff --git a/src/renderer/widgets/Shape.cpp b/src/renderer/widgets/Shape.cpp index 6777720d..51dfa4a4 100644 --- a/src/renderer/widgets/Shape.cpp +++ b/src/renderer/widgets/Shape.cpp @@ -105,7 +105,7 @@ bool CShape::draw(const SRenderData& data) { return data.opacity < 1.0; } -void CShape::onAssetUpdate(ASP newAsset) { +void CShape::onAssetUpdate(ResourceID id, ASP newAsset) { ; } diff --git a/src/renderer/widgets/Shape.hpp b/src/renderer/widgets/Shape.hpp index aaa8a592..b2be8620 100644 --- a/src/renderer/widgets/Shape.hpp +++ b/src/renderer/widgets/Shape.hpp @@ -18,7 +18,7 @@ class CShape : public IWidget { virtual void configure(const std::unordered_map& prop, const SP& pOutput); virtual bool draw(const SRenderData& data); - virtual void onAssetUpdate(ASP newAsset); + virtual void onAssetUpdate(ResourceID id, ASP newAsset); virtual CBox getBoundingBoxWl() const; virtual void onClick(uint32_t button, bool down, const Vector2D& pos); From 2a93b2cb1738feebf29e95de95b0a00acbb162d9 Mon Sep 17 00:00:00 2001 From: Maximilian Seidler Date: Mon, 29 Sep 2025 20:48:05 +0200 Subject: [PATCH 13/19] fix background reloads --- src/renderer/widgets/Background.cpp | 19 ++++++++++--------- src/renderer/widgets/Background.hpp | 6 +++--- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/renderer/widgets/Background.cpp b/src/renderer/widgets/Background.cpp index c9383406..9b7dc86a 100644 --- a/src/renderer/widgets/Background.cpp +++ b/src/renderer/widgets/Background.cpp @@ -251,10 +251,10 @@ bool CBackground::draw(const SRenderData& data) { } void CBackground::onAssetUpdate(ResourceID id, ASP newAsset) { - pendingResourceID = 0; + pendingResource = false; if (!newAsset) - Debug::log(ERR, "Background asset update failed, resourceID: {} not available on update!", pendingResourceID); + Debug::log(ERR, "Background asset update failed, resourceID: {} not available on update!", id); else if (newAsset->m_iType == TEXTURE_INVALID) { g_asyncResourceManager->unload(newAsset); Debug::log(ERR, "New background asset has an invalid texture!"); @@ -264,14 +264,13 @@ void CBackground::onAssetUpdate(ResourceID id, ASP newAsset) { *crossFadeProgress = 1.0; crossFadeProgress->setCallbackOnEnd( - [REF = m_self](auto) { + [REF = m_self, id](auto) { if (const auto PSELF = REF.lock()) { if (PSELF->asset) g_asyncResourceManager->unload(PSELF->asset); - PSELF->asset = PSELF->pendingAsset; - PSELF->pendingAsset = nullptr; - PSELF->resourceID = PSELF->pendingResourceID; - PSELF->pendingResourceID = 0; + PSELF->asset = PSELF->pendingAsset; + PSELF->pendingAsset = nullptr; + PSELF->resourceID = id; PSELF->blurredFB->destroyBuffer(); PSELF->blurredFB = std::move(PSELF->pendingBlurredFB); @@ -323,10 +322,12 @@ void CBackground::onReloadTimerUpdate() { return; } - if (pendingResourceID > 0) + if (pendingResource) return; + pendingResource = true; + // Issue the next request AWP widget(m_self); - pendingResourceID = g_asyncResourceManager->requestImage(path, m_imageRevision, widget); + g_asyncResourceManager->requestImage(path, m_imageRevision, widget); } diff --git a/src/renderer/widgets/Background.hpp b/src/renderer/widgets/Background.hpp index 52784a73..d3516c57 100644 --- a/src/renderer/widgets/Background.hpp +++ b/src/renderer/widgets/Background.hpp @@ -64,9 +64,9 @@ class CBackground : public IWidget { std::string outputPort; Hyprutils::Math::eTransform transform; - ResourceID resourceID = 0; - ResourceID scResourceID = 0; - ResourceID pendingResourceID = 0; + ResourceID resourceID = 0; + ResourceID scResourceID = 0; + bool pendingResource = false; PHLANIMVAR crossFadeProgress; From e81319b27951f67b76f5e312892855c03ffd382c Mon Sep 17 00:00:00 2001 From: Maximilian Seidler Date: Mon, 29 Sep 2025 20:58:25 +0200 Subject: [PATCH 14/19] comments --- src/renderer/widgets/Background.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/renderer/widgets/Background.cpp b/src/renderer/widgets/Background.cpp index 9b7dc86a..d9bc05fb 100644 --- a/src/renderer/widgets/Background.cpp +++ b/src/renderer/widgets/Background.cpp @@ -57,15 +57,13 @@ void CBackground::configure(const std::unordered_map& pro g_pAnimationManager->createAnimation(0.f, crossFadeProgress, g_pConfigManager->m_AnimationTree.getConfig("fadeIn")); - // 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. if (!g_asyncResourceManager->checkIdPresent(scResourceID)) { Debug::log(LOG, "Missing screenshot for output {}", outputPort); scResourceID = 0; } if (isScreenshot) { - resourceID = scResourceID; // Fallback to solid background:color when scResourceID=="" + resourceID = scResourceID; // Fallback to solid background:color when scResourceID==0 if (!g_pHyprlock->getScreencopy()) { Debug::log(ERR, "No screencopy support! path=screenshot won't work. Falling back to background color."); From b9850ee1407badd171433036ccd1e0596ae88426 Mon Sep 17 00:00:00 2001 From: Maximilian Seidler Date: Tue, 30 Sep 2025 19:53:02 +0200 Subject: [PATCH 15/19] LOG -> TRACE for label updates --- src/renderer/widgets/Label.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/widgets/Label.cpp b/src/renderer/widgets/Label.cpp index f391072b..263dca8e 100644 --- a/src/renderer/widgets/Label.cpp +++ b/src/renderer/widgets/Label.cpp @@ -152,7 +152,7 @@ bool CLabel::draw(const SRenderData& data) { } void CLabel::onAssetUpdate(ResourceID id, ASP newAsset) { - Debug::log(LOG, "Label update for resourceID {}", id); + Debug::log(TRACE, "Label update for resourceID {}", id); m_pendingResource = false; if (!newAsset) From 3b1385941db7ed78a533261051fda693fff99339 Mon Sep 17 00:00:00 2001 From: Maximilian Seidler Date: Sat, 4 Oct 2025 17:44:40 +0200 Subject: [PATCH 16/19] reset g_asyncResourceManager --- src/core/hyprlock.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/hyprlock.cpp b/src/core/hyprlock.cpp index 3df2b4e4..58353a0b 100644 --- a/src/core/hyprlock.cpp +++ b/src/core/hyprlock.cpp @@ -472,6 +472,7 @@ void CHyprlock::run() { m_vOutputs.clear(); g_pSeatManager.reset(); + g_asyncResourceManager.reset(); g_pRenderer.reset(); g_pEGL.reset(); From 592817c635cd0932b672d1c31ac550972f54c8e3 Mon Sep 17 00:00:00 2001 From: Maximilian Seidler Date: Sat, 4 Oct 2025 17:49:58 +0200 Subject: [PATCH 17/19] mask less bits to scope the resource ids --- src/renderer/AsyncResourceManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/AsyncResourceManager.cpp b/src/renderer/AsyncResourceManager.cpp index a015e811..b29d2807 100644 --- a/src/renderer/AsyncResourceManager.cpp +++ b/src/renderer/AsyncResourceManager.cpp @@ -16,7 +16,7 @@ using namespace Hyprgraphics; using namespace Hyprutils::OS; static inline ResourceID scopeResourceID(uint8_t scope, size_t in) { - return (in & ~0xff) | scope; + return (in & ~0x0f) | scope; } ResourceID CAsyncResourceManager::resourceIDForTextRequest(const CTextResource::STextResourceData& s) { From 2eef5d41d54773ac917ff7eb98bc821cbeb4dc26 Mon Sep 17 00:00:00 2001 From: Maximilian Seidler Date: Wed, 8 Oct 2025 09:16:22 +0200 Subject: [PATCH 18/19] remove nvidia workaround :) I tested and it seems like the resource manager revision makes the nvidia workaround obsolete. --- src/core/hyprlock.cpp | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/core/hyprlock.cpp b/src/core/hyprlock.cpp index 58353a0b..a92fa3f3 100644 --- a/src/core/hyprlock.cpp +++ b/src/core/hyprlock.cpp @@ -306,19 +306,6 @@ void CHyprlock::run() { g_pRenderer->removeWidgetsFor((*outputIt)->m_ID); m_vOutputs.erase(outputIt); } - - // TODO: Recreating the rendering context like this fixes an issue with nvidia graphics when reconnecting monitors. - // It only happens when there are no monitors left. - // This is either an nvidia bug (probably egl-wayland) or it is a hyprlock bug. In any case, the goal is to remove this at some point! - if (g_pEGL->m_isNvidia && m_vOutputs.empty()) { - Debug::log(LOG, "NVIDIA Workaround: destroying rendering context to avoid crash on reconnect!"); - - g_pRenderer.reset(); - g_pEGL.reset(); - g_pEGL = makeUnique(m_sWaylandState.display); - g_pRenderer = makeUnique(); - g_pRenderer->warpOpacity(1.0); - } }); wl_display_roundtrip(m_sWaylandState.display); From 90f073a9819a8b31ff4235b6b157799e582aad85 Mon Sep 17 00:00:00 2001 From: Maximilian Seidler Date: Wed, 8 Oct 2025 10:40:52 +0200 Subject: [PATCH 19/19] remove log --- src/renderer/AsyncResourceManager.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/renderer/AsyncResourceManager.cpp b/src/renderer/AsyncResourceManager.cpp index b29d2807..717cf524 100644 --- a/src/renderer/AsyncResourceManager.cpp +++ b/src/renderer/AsyncResourceManager.cpp @@ -248,7 +248,6 @@ bool CAsyncResourceManager::request(ResourceID id, const AWP& widget) { if (m_assets[id].texture) { // Asset already present. Dispatch the asset callback function. const auto& TEXTURE = m_assets[id].texture; - Debug::log(LOG, "onAssetUpdate {}", id); if (widget) widget->onAssetUpdate(id, TEXTURE);