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.
This commit is contained in:
Maximilian Seidler 2025-09-29 18:55:23 +02:00
parent 4734f8bcd4
commit b4b6eb7718
12 changed files with 57 additions and 55 deletions

View file

@ -44,14 +44,14 @@ ResourceID CAsyncResourceManager::resourceIDForScreencopy(const std::string& por
ResourceID CAsyncResourceManager::requestText(const CTextResource::STextResourceData& params, const AWP<IWidget>& 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>(CTextResource::STextResourceData{params});
CAtomicSharedPointer<IAsyncResource> 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<IWidget>& 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<CTextCmdResource>(CTextResource::STextResourceData{params});
CAtomicSharedPointer<IAsyncResource> 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<IWidget>& 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<CImageResource>(absolutePath(path, ""));
CAtomicSharedPointer<IAsyncResource> 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<IWidget>& 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()) {

View file

@ -250,7 +250,7 @@ bool CBackground::draw(const SRenderData& data) {
return crossFadeProgress->isBeingAnimated() || data.opacity < 1.0;
}
void CBackground::onAssetUpdate(ASP<CTexture> newAsset) {
void CBackground::onAssetUpdate(ResourceID id, ASP<CTexture> newAsset) {
pendingResourceID = 0;
if (!newAsset)
@ -278,8 +278,6 @@ void CBackground::onAssetUpdate(ASP<CTexture> newAsset) {
}
},
true);
g_pHyprlock->renderOutput(outputPort);
}
}

View file

@ -24,7 +24,7 @@ class CBackground : public IWidget {
virtual void configure(const std::unordered_map<std::string, std::any>& props, const SP<COutput>& pOutput);
virtual bool draw(const SRenderData& data);
virtual void onAssetUpdate(ASP<CTexture> newAsset);
virtual void onAssetUpdate(ResourceID id, ASP<CTexture> newAsset);
void reset(); // Unload assets, remove timers, etc.

View file

@ -20,9 +20,10 @@ class IWidget {
virtual ~IWidget() = default;
virtual void configure(const std::unordered_map<std::string, std::any>& prop, const SP<COutput>& pOutput) = 0;
virtual bool draw(const SRenderData& data) = 0;
virtual void onAssetUpdate(ASP<CTexture> newAsset) = 0;
virtual void configure(const std::unordered_map<std::string, std::any>& prop, const SP<COutput>& pOutput) = 0;
virtual bool draw(const SRenderData& data) = 0;
// Never render within onAssetUpdate!
virtual void onAssetUpdate(ResourceID id, ASP<CTexture> 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);

View file

@ -25,6 +25,11 @@ static void onTimer(AWP<CImage> 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<IWidget> 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<CTexture> newAsset) {
pendingResourceID = 0;
void CImage::onAssetUpdate(ResourceID id, ASP<CTexture> 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<CTexture> newAsset) {
imageFB.destroyBuffer();
asset = newAsset;
resourceID = pendingResourceID;
resourceID = id;
firstRender = true;
g_pHyprlock->renderOutput(stringPort);
}
}

View file

@ -24,7 +24,7 @@ class CImage : public IWidget {
virtual void configure(const std::unordered_map<std::string, std::any>& props, const SP<COutput>& pOutput);
virtual bool draw(const SRenderData& data);
virtual void onAssetUpdate(ASP<CTexture> newAsset);
virtual void onAssetUpdate(ResourceID id, ASP<CTexture> 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<CTexture> asset = nullptr;
CShadowable shadow;

View file

@ -6,6 +6,7 @@
#include "../../helpers/Color.hpp"
#include "../../helpers/MiscFunctions.hpp"
#include "../../config/ConfigDataValues.hpp"
#include "src/defines.hpp"
#include <hyprlang.hpp>
#include <stdexcept>
@ -27,6 +28,11 @@ static void onTimer(AWP<CLabel> 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<IWidget> 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<CTexture> newAsset) {
pendingResourceID = 0;
void CLabel::onAssetUpdate(ResourceID id, ASP<CTexture> 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<CTexture> newAsset) {
// new asset is ready :D
g_asyncResourceManager->unload(asset);
asset = newAsset;
resourceID = pendingResourceID;
resourceID = id;
updateShadow = true;
g_pHyprlock->renderOutput(outputStringPort);
}
}

View file

@ -22,7 +22,7 @@ class CLabel : public IWidget {
virtual void configure(const std::unordered_map<std::string, std::any>& prop, const SP<COutput>& pOutput);
virtual bool draw(const SRenderData& data);
virtual void onAssetUpdate(ASP<CTexture> newAsset);
virtual void onAssetUpdate(ResourceID id, ASP<CTexture> 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;

View file

@ -363,8 +363,8 @@ void CPasswordInputField::updatePlaceholder() {
placeholder.resourceID = g_asyncResourceManager->requestText(request, widget);
}
void CPasswordInputField::onAssetUpdate(ASP<CTexture> newAsset) {
g_pHyprlock->renderOutput(outputStringPort);
void CPasswordInputField::onAssetUpdate(ResourceID id, ASP<CTexture> newAsset) {
;
}
void CPasswordInputField::updateWidth() {

View file

@ -24,7 +24,7 @@ class CPasswordInputField : public IWidget {
virtual void configure(const std::unordered_map<std::string, std::any>& prop, const SP<COutput>& pOutput);
virtual bool draw(const SRenderData& data);
virtual void onAssetUpdate(ASP<CTexture> newAsset);
virtual void onAssetUpdate(ResourceID id, ASP<CTexture> newAsset);
virtual void onHover(const Vector2D& pos);
virtual CBox getBoundingBoxWl() const;

View file

@ -105,7 +105,7 @@ bool CShape::draw(const SRenderData& data) {
return data.opacity < 1.0;
}
void CShape::onAssetUpdate(ASP<CTexture> newAsset) {
void CShape::onAssetUpdate(ResourceID id, ASP<CTexture> newAsset) {
;
}

View file

@ -18,7 +18,7 @@ class CShape : public IWidget {
virtual void configure(const std::unordered_map<std::string, std::any>& prop, const SP<COutput>& pOutput);
virtual bool draw(const SRenderData& data);
virtual void onAssetUpdate(ASP<CTexture> newAsset);
virtual void onAssetUpdate(ResourceID id, ASP<CTexture> newAsset);
virtual CBox getBoundingBoxWl() const;
virtual void onClick(uint32_t button, bool down, const Vector2D& pos);