core: add option to render solid background immediatly when bg assets are not ready (#407)

* asyncResourceGatherer: start the asyncLoop at the same time as gather

This is a prerequesit for labels beeing drawn, while backgrounds are
note ready yet.

* core: allow immediate rendering even when backgrounds are not gathered yet

Note:
We don't really need to call `asyncResourceGatherer::apply` in the
`renderLock` function, since it will get called by a call to
`asyncResourceGatherer::getAssetById` anyways.

* background: render color rectangle when asset is not ready yet

* config: add general:immediate_render config option

* core: use the --immediate-render flag in attemptRestoreOnDeath
This commit is contained in:
Maximilian Seidler 2024-07-07 18:43:17 +02:00 committed by GitHub
parent a50296c181
commit 0552a1eddd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 66 additions and 40 deletions

View file

@ -55,6 +55,7 @@ void CConfigManager::init() {
m_config.addConfigValue("general:no_fade_in", Hyprlang::INT{0}); m_config.addConfigValue("general:no_fade_in", Hyprlang::INT{0});
m_config.addConfigValue("general:no_fade_out", Hyprlang::INT{0}); m_config.addConfigValue("general:no_fade_out", Hyprlang::INT{0});
m_config.addConfigValue("general:ignore_empty_input", Hyprlang::INT{0}); m_config.addConfigValue("general:ignore_empty_input", Hyprlang::INT{0});
m_config.addConfigValue("general:immediate_render", Hyprlang::INT{0});
m_config.addConfigValue("general:pam_module", Hyprlang::STRING{"hyprlock"}); m_config.addConfigValue("general:pam_module", Hyprlang::STRING{"hyprlock"});
m_config.addSpecialCategory("background", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true}); m_config.addSpecialCategory("background", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true});

View file

@ -18,7 +18,7 @@
#include <fstream> #include <fstream>
#include <algorithm> #include <algorithm>
CHyprlock::CHyprlock(const std::string& wlDisplay, const bool immediate) { CHyprlock::CHyprlock(const std::string& wlDisplay, const bool immediate, const bool immediateRender) {
m_sWaylandState.display = wl_display_connect(wlDisplay.empty() ? nullptr : wlDisplay.c_str()); m_sWaylandState.display = wl_display_connect(wlDisplay.empty() ? nullptr : wlDisplay.c_str());
if (!m_sWaylandState.display) { if (!m_sWaylandState.display) {
Debug::log(CRIT, "Couldn't connect to a wayland compositor"); Debug::log(CRIT, "Couldn't connect to a wayland compositor");
@ -32,11 +32,14 @@ CHyprlock::CHyprlock(const std::string& wlDisplay, const bool immediate) {
Debug::log(ERR, "Failed to create xkb context"); Debug::log(ERR, "Failed to create xkb context");
if (!immediate) { if (!immediate) {
const auto GRACE = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("general:grace"); const auto PGRACE = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("general:grace");
m_tGraceEnds = **GRACE ? std::chrono::system_clock::now() + std::chrono::seconds(**GRACE) : std::chrono::system_clock::from_time_t(0); m_tGraceEnds = **PGRACE ? std::chrono::system_clock::now() + std::chrono::seconds(**PGRACE) : std::chrono::system_clock::from_time_t(0);
} else { } else {
m_tGraceEnds = std::chrono::system_clock::from_time_t(0); m_tGraceEnds = std::chrono::system_clock::from_time_t(0);
} }
const auto PIMMEDIATERENDER = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("general:immediate_render");
m_bImmediateRender = immediateRender || **PIMMEDIATERENDER;
} }
CHyprlock::~CHyprlock() { CHyprlock::~CHyprlock() {
@ -383,7 +386,7 @@ void CHyprlock::run() {
// Hyprland violates the protocol a bit to allow for this. // Hyprland violates the protocol a bit to allow for this.
if (SZCURRENTD != "Hyprland") { if (SZCURRENTD != "Hyprland") {
while (!g_pRenderer->asyncResourceGatherer->ready) { while (!g_pRenderer->asyncResourceGatherer->gathered) {
wl_display_flush(m_sWaylandState.display); wl_display_flush(m_sWaylandState.display);
if (wl_display_prepare_read(m_sWaylandState.display) == 0) { if (wl_display_prepare_read(m_sWaylandState.display) == 0) {
wl_display_read_events(m_sWaylandState.display); wl_display_read_events(m_sWaylandState.display);
@ -1137,5 +1140,5 @@ void CHyprlock::attemptRestoreOnDeath() {
ofs.close(); ofs.close();
spawnSync("hyprctl keyword misc:allow_session_lock_restore true"); spawnSync("hyprctl keyword misc:allow_session_lock_restore true");
spawnAsync("sleep 2 && hyprlock --immediate & disown"); spawnAsync("sleep 2 && hyprlock --immediate --immediate-render & disown");
} }

View file

@ -28,7 +28,7 @@ struct SDMABUFModifier {
class CHyprlock { class CHyprlock {
public: public:
CHyprlock(const std::string& wlDisplay, const bool immediate); CHyprlock(const std::string& wlDisplay, const bool immediate, const bool immediateRender);
~CHyprlock(); ~CHyprlock();
void run(); void run();
@ -100,6 +100,8 @@ class CHyprlock {
bool m_bNumLock = false; bool m_bNumLock = false;
bool m_bCtrl = false; bool m_bCtrl = false;
bool m_bFadeStarted = false; bool m_bFadeStarted = false;
bool m_bImmediateRender = false;
// //
std::chrono::system_clock::time_point m_tGraceEnds; std::chrono::system_clock::time_point m_tGraceEnds;
std::chrono::system_clock::time_point m_tFadeEnds; std::chrono::system_clock::time_point m_tFadeEnds;

View file

@ -11,6 +11,7 @@ void help() {
" -c FILE, --config FILE - Specify config file to use\n" " -c FILE, --config FILE - Specify config file to use\n"
" --display (display) - Specify the Wayland display to connect to\n" " --display (display) - Specify the Wayland display to connect to\n"
" --immediate - Lock immediately, ignoring any configured grace period\n" " --immediate - Lock immediately, ignoring any configured grace period\n"
" --immediate-render - Do not wait for resources before drawing the background\n"
" -h, --help - Show this help message\n"; " -h, --help - Show this help message\n";
} }
@ -26,8 +27,9 @@ std::optional<std::string> parseArg(const std::vector<std::string>& args, const
int main(int argc, char** argv, char** envp) { int main(int argc, char** argv, char** envp) {
std::string configPath; std::string configPath;
std::string wlDisplay; std::string wlDisplay;
bool immediate = false; bool immediate = false;
bool showHelp = false; bool immediateRender = false;
bool showHelp = false;
std::vector<std::string> args(argv, argv + argc); std::vector<std::string> args(argv, argv + argc);
@ -55,6 +57,9 @@ int main(int argc, char** argv, char** envp) {
} else if (arg == "--immediate") } else if (arg == "--immediate")
immediate = true; immediate = true;
else if (arg == "--immediate-render")
immediateRender = true;
else if (arg == "--help" || arg == "-h") { else if (arg == "--help" || arg == "-h") {
showHelp = true; showHelp = true;
break; break;
@ -83,7 +88,7 @@ int main(int argc, char** argv, char** envp) {
} }
try { try {
g_pHyprlock = std::make_unique<CHyprlock>(wlDisplay, immediate); g_pHyprlock = std::make_unique<CHyprlock>(wlDisplay, immediate, immediateRender);
g_pHyprlock->run(); g_pHyprlock->run();
} catch (const std::exception& ex) { } catch (const std::exception& ex) {
Debug::log(CRIT, "Hyprlock threw: {}", ex.what()); Debug::log(CRIT, "Hyprlock threw: {}", ex.what());

View file

@ -49,10 +49,10 @@ CAsyncResourceGatherer::CAsyncResourceGatherer() {
dmas.emplace_back(std::make_unique<CDMAFrame>(PMONITOR)); dmas.emplace_back(std::make_unique<CDMAFrame>(PMONITOR));
} }
asyncLoopThread = std::thread([this]() { initialGatherThread = std::thread([this]() { this->gather(); });
this->gather(); /* inital gather */ initialGatherThread.detach();
this->asyncAssetSpinLock();
}); asyncLoopThread = std::thread([this]() { this->asyncAssetSpinLock(); });
asyncLoopThread.detach(); asyncLoopThread.detach();
} }
@ -158,16 +158,19 @@ void CAsyncResourceGatherer::gather() {
const auto CAIRO = cairo_create(CAIROISURFACE); const auto CAIRO = cairo_create(CAIROISURFACE);
cairo_scale(CAIRO, 1, 1); cairo_scale(CAIRO, 1, 1);
const auto TARGET = &preloadTargets.emplace_back(CAsyncResourceGatherer::SPreloadTarget{}); {
std::lock_guard lg{preloadTargetsMutex};
const auto TARGET = &preloadTargets.emplace_back(CAsyncResourceGatherer::SPreloadTarget{});
TARGET->size = {cairo_image_surface_get_width(CAIROISURFACE), cairo_image_surface_get_height(CAIROISURFACE)}; TARGET->size = {cairo_image_surface_get_width(CAIROISURFACE), cairo_image_surface_get_height(CAIROISURFACE)};
TARGET->type = TARGET_IMAGE; TARGET->type = TARGET_IMAGE;
TARGET->id = id; TARGET->id = id;
const auto DATA = cairo_image_surface_get_data(CAIROISURFACE); const auto DATA = cairo_image_surface_get_data(CAIROISURFACE);
TARGET->cairo = CAIRO; TARGET->cairo = CAIRO;
TARGET->cairosurface = CAIROISURFACE; TARGET->cairosurface = CAIROISURFACE;
TARGET->data = DATA; TARGET->data = DATA;
}
} }
} }
@ -175,7 +178,7 @@ void CAsyncResourceGatherer::gather() {
std::this_thread::sleep_for(std::chrono::milliseconds(1)); std::this_thread::sleep_for(std::chrono::milliseconds(1));
} }
ready = true; gathered = true;
} }
bool CAsyncResourceGatherer::apply() { bool CAsyncResourceGatherer::apply() {
@ -225,7 +228,6 @@ bool CAsyncResourceGatherer::apply() {
} }
} }
applied = true;
return true; return true;
} }
@ -414,4 +416,6 @@ void CAsyncResourceGatherer::notify() {
void CAsyncResourceGatherer::await() { void CAsyncResourceGatherer::await() {
if (asyncLoopThread.joinable()) if (asyncLoopThread.joinable())
asyncLoopThread.join(); asyncLoopThread.join();
if (initialGatherThread.joinable())
initialGatherThread.join();
} }

View file

@ -16,8 +16,7 @@
class CAsyncResourceGatherer { class CAsyncResourceGatherer {
public: public:
CAsyncResourceGatherer(); CAsyncResourceGatherer();
std::atomic<bool> ready = false; std::atomic<bool> gathered = false;
std::atomic<bool> applied = false;
std::atomic<float> progress = 0; std::atomic<float> progress = 0;
@ -52,6 +51,7 @@ class CAsyncResourceGatherer {
private: private:
std::thread asyncLoopThread; std::thread asyncLoopThread;
std::thread initialGatherThread;
void asyncAssetSpinLock(); void asyncAssetSpinLock();
void renderText(const SPreloadRequest& rq); void renderText(const SPreloadRequest& rq);

View file

@ -159,7 +159,8 @@ CRenderer::CRenderer() {
asyncResourceGatherer = std::make_unique<CAsyncResourceGatherer>(); asyncResourceGatherer = std::make_unique<CAsyncResourceGatherer>();
} }
static int frames = 0; static int frames = 0;
static bool firstFullFrame = false;
// //
CRenderer::SRenderFeedback CRenderer::renderLock(const CSessionLockSurface& surf) { CRenderer::SRenderFeedback CRenderer::renderLock(const CSessionLockSurface& surf) {
@ -183,12 +184,11 @@ CRenderer::SRenderFeedback CRenderer::renderLock(const CSessionLockSurface& surf
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
SRenderFeedback feedback; SRenderFeedback feedback;
float bga = 0.0;
const bool WAITFORASSETS = !g_pHyprlock->m_bImmediateRender && !asyncResourceGatherer->gathered;
float bga = asyncResourceGatherer->applied ? if (WAITFORASSETS) {
std::clamp(std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now() - gatheredAt).count() / 500000.0, 0.0, 1.0) :
0.0;
if (!asyncResourceGatherer->ready) {
// render status // render status
if (!**PDISABLEBAR) { if (!**PDISABLEBAR) {
CBox progress = {0, 0, asyncResourceGatherer->progress * surf.size.x, 2}; CBox progress = {0, 0, asyncResourceGatherer->progress * surf.size.x, 2};
@ -196,11 +196,13 @@ CRenderer::SRenderFeedback CRenderer::renderLock(const CSessionLockSurface& surf
} }
} else { } else {
if (!asyncResourceGatherer->applied) { if (!firstFullFrame) {
asyncResourceGatherer->apply(); firstFullFrameTime = std::chrono::system_clock::now();
gatheredAt = std::chrono::system_clock::now(); firstFullFrame = true;
} }
bga = std::clamp(std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now() - firstFullFrameTime).count() / 500000.0, 0.0, 1.0);
if (**PNOFADEIN) if (**PNOFADEIN)
bga = 1.0; bga = 1.0;
@ -220,7 +222,7 @@ CRenderer::SRenderFeedback CRenderer::renderLock(const CSessionLockSurface& surf
Debug::log(TRACE, "frame {}", frames); Debug::log(TRACE, "frame {}", frames);
feedback.needsFrame = feedback.needsFrame || !asyncResourceGatherer->ready || bga < 1.0; feedback.needsFrame = feedback.needsFrame || !asyncResourceGatherer->gathered || bga < 1.0;
glDisable(GL_BLEND); glDisable(GL_BLEND);
@ -325,7 +327,7 @@ std::vector<std::unique_ptr<IWidget>>* CRenderer::getOrCreateWidgetsFor(const CS
resourceID = CDMAFrame::getResourceId(surf->output); resourceID = CDMAFrame::getResourceId(surf->output);
// When the initial gather of the asyncResourceGatherer is completed (ready), all DMAFrames are available. // When the initial gather of the asyncResourceGatherer is completed (ready), all DMAFrames are available.
// Dynamic ones are tricky, because a screencopy would copy hyprlock itself. // Dynamic ones are tricky, because a screencopy would copy hyprlock itself.
if (asyncResourceGatherer->ready) { if (asyncResourceGatherer->gathered) {
if (!asyncResourceGatherer->getAssetByID(resourceID)) if (!asyncResourceGatherer->getAssetByID(resourceID))
resourceID = ""; // Fallback to solid color (background:color) resourceID = ""; // Fallback to solid color (background:color)
} }

View file

@ -36,7 +36,7 @@ class CRenderer {
void blurFB(const CFramebuffer& outfb, SBlurParams params); void blurFB(const CFramebuffer& outfb, SBlurParams params);
std::unique_ptr<CAsyncResourceGatherer> asyncResourceGatherer; std::unique_ptr<CAsyncResourceGatherer> asyncResourceGatherer;
std::chrono::system_clock::time_point gatheredAt; std::chrono::system_clock::time_point firstFullFrameTime;
void pushFb(GLint fb); void pushFb(GLint fb);
void popFb(); void popFb();

View file

@ -15,21 +15,29 @@ CBackground::CBackground(const Vector2D& viewport_, COutput* output_, const std:
contrast = std::any_cast<Hyprlang::FLOAT>(props.at("contrast")); contrast = std::any_cast<Hyprlang::FLOAT>(props.at("contrast"));
} }
void CBackground::renderRect(CColor color) {
CBox monbox = {0, 0, viewport.x, viewport.y};
g_pRenderer->renderRect(monbox, color, 0);
}
bool CBackground::draw(const SRenderData& data) { bool CBackground::draw(const SRenderData& data) {
if (resourceID.empty()) { if (resourceID.empty()) {
CBox monbox = {0, 0, viewport.x, viewport.y}; CColor col = color;
CColor col = color;
col.a *= data.opacity; col.a *= data.opacity;
g_pRenderer->renderRect(monbox, col, 0); renderRect(col);
return data.opacity < 1.0; return data.opacity < 1.0;
} }
if (!asset) if (!asset)
asset = g_pRenderer->asyncResourceGatherer->getAssetByID(resourceID); asset = g_pRenderer->asyncResourceGatherer->getAssetByID(resourceID);
if (!asset) if (!asset) {
CColor col = color;
col.a *= data.opacity;
renderRect(col);
return true; return true;
}
if (asset->texture.m_iType == TEXTURE_INVALID) { if (asset->texture.m_iType == TEXTURE_INVALID) {
g_pRenderer->asyncResourceGatherer->unloadAsset(asset); g_pRenderer->asyncResourceGatherer->unloadAsset(asset);

View file

@ -16,6 +16,7 @@ class CBackground : public IWidget {
CBackground(const Vector2D& viewport, COutput* output_, const std::string& resourceID, const std::unordered_map<std::string, std::any>& props, bool ss_); CBackground(const Vector2D& viewport, COutput* output_, const std::string& resourceID, const std::unordered_map<std::string, std::any>& props, bool ss_);
virtual bool draw(const SRenderData& data); virtual bool draw(const SRenderData& data);
void renderRect(CColor color);
private: private:
// if needed // if needed