diff options
| author | Vaxry <43317083+vaxerski@users.noreply.github.com> | 2025-07-08 12:41:10 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-07-08 12:41:10 +0200 |
| commit | 8f948827a69499c0b112043debad35f82a312b6b (patch) | |
| tree | 6d2c678a61db60e64c8cd1160267e2803f89066b /src/helpers/MonitorFrameScheduler.cpp | |
| parent | 9856563f8966856871a95f70757362f694ff22a6 (diff) | |
Renderer: Implement new render scheduling (#10936)
Implements a new render scheduling method, where we triple buffer when necessary.
Enabled by default, improves FPS on underpowered devices.
---------
Co-authored-by: Mihai Fufezan <mihai@fufexan.net>
Diffstat (limited to 'src/helpers/MonitorFrameScheduler.cpp')
| -rw-r--r-- | src/helpers/MonitorFrameScheduler.cpp | 130 |
1 files changed, 130 insertions, 0 deletions
diff --git a/src/helpers/MonitorFrameScheduler.cpp b/src/helpers/MonitorFrameScheduler.cpp new file mode 100644 index 00000000..0dc79159 --- /dev/null +++ b/src/helpers/MonitorFrameScheduler.cpp @@ -0,0 +1,130 @@ +#include "MonitorFrameScheduler.hpp" +#include "../config/ConfigValue.hpp" +#include "../Compositor.hpp" +#include "../render/Renderer.hpp" +#include "../managers/eventLoop/EventLoopManager.hpp" + +CMonitorFrameScheduler::CMonitorFrameScheduler(PHLMONITOR m) : m_monitor(m) { + ; +} + +void CMonitorFrameScheduler::onSyncFired() { + static auto PENABLENEW = CConfigValue<Hyprlang::INT>("render:new_render_scheduling"); + + if (!*PENABLENEW) + return; + + // Sync fired: reset submitted state, set as rendered. Check the last render time. If we are running + // late, we will instantly render here. + + if (std::chrono::duration_cast<std::chrono::microseconds>(hrc::now() - m_lastRenderBegun).count() / 1000.F < 1000.F / m_monitor->m_refreshRate) { + // we are in. Frame is valid. We can just render as normal. + Debug::log(TRACE, "CMonitorFrameScheduler: {} -> onSyncFired, didn't miss.", m_monitor->m_name); + m_renderAtFrame = true; + return; + } + + Debug::log(TRACE, "CMonitorFrameScheduler: {} -> onSyncFired, missed.", m_monitor->m_name); + + // we are out. The frame is taking too long to render. Begin rendering immediately, but don't commit yet. + m_pendingThird = true; + m_renderAtFrame = false; // block frame rendering, we already scheduled + + m_lastRenderBegun = hrc::now(); + g_pHyprRenderer->renderMonitor(m_monitor.lock(), false); + onFinishRender(); +} + +void CMonitorFrameScheduler::onPresented() { + static auto PENABLENEW = CConfigValue<Hyprlang::INT>("render:new_render_scheduling"); + + if (!*PENABLENEW) + return; + + if (!m_pendingThird) + return; + + Debug::log(TRACE, "CMonitorFrameScheduler: {} -> onPresented, missed, committing pending.", m_monitor->m_name); + + m_pendingThird = false; + + Debug::log(TRACE, "CMonitorFrameScheduler: {} -> onPresented, missed, committing pending at the earliest convenience.", m_monitor->m_name); + + m_pendingThird = false; + + g_pEventLoopManager->doLater([m = m_monitor.lock()] { + g_pHyprRenderer->commitPendingAndDoExplicitSync(m); // commit the pending frame. If it didn't fire yet (is not rendered) it doesn't matter. Syncs will wait. + + // schedule a frame: we might have some missed damage, which got cleared due to the above commit. + // TODO: this is not always necessary, but doesn't hurt in general. We likely won't hit this if nothing's happening anyways. + if (m->m_damage.hasChanged()) + g_pCompositor->scheduleFrameForMonitor(m); + }); +} + +void CMonitorFrameScheduler::onFrame() { + static auto PENABLENEW = CConfigValue<Hyprlang::INT>("render:new_render_scheduling"); + + if (!canRender()) + return; + + g_pHyprRenderer->recheckSolitaryForMonitor(m_monitor.lock()); + + m_monitor->m_tearingState.busy = false; + + if (m_monitor->m_tearingState.activelyTearing && m_monitor->m_solitaryClient.lock() /* can be invalidated by a recheck */) { + + if (!m_monitor->m_tearingState.frameScheduledWhileBusy) + return; // we did not schedule a frame yet to be displayed, but we are tearing. Why render? + + m_monitor->m_tearingState.nextRenderTorn = true; + m_monitor->m_tearingState.frameScheduledWhileBusy = false; + } + + if (!*PENABLENEW) { + m_monitor->m_lastPresentationTimer.reset(); + + g_pHyprRenderer->renderMonitor(m_monitor.lock()); + return; + } + + if (!m_renderAtFrame) { + Debug::log(TRACE, "CMonitorFrameScheduler: {} -> frame event, but m_renderAtFrame = false.", m_monitor->m_name); + return; + } + + Debug::log(TRACE, "CMonitorFrameScheduler: {} -> frame event, render = true, rendering normally.", m_monitor->m_name); + + m_lastRenderBegun = hrc::now(); + g_pHyprRenderer->renderMonitor(m_monitor.lock()); + onFinishRender(); +} + +void CMonitorFrameScheduler::onFinishRender() { + m_sync = CEGLSync::create(); // this destroys the old sync + g_pEventLoopManager->doOnReadable(m_sync->fd().duplicate(), [this, mon = m_monitor] { + if (!m_monitor) // might've gotten destroyed + return; + onSyncFired(); + }); +} + +bool CMonitorFrameScheduler::canRender() { + if ((g_pCompositor->m_aqBackend->hasSession() && !g_pCompositor->m_aqBackend->session->active) || !g_pCompositor->m_sessionActive || g_pCompositor->m_unsafeState) { + Debug::log(WARN, "Attempted to render frame on inactive session!"); + + if (g_pCompositor->m_unsafeState && std::ranges::any_of(g_pCompositor->m_monitors.begin(), g_pCompositor->m_monitors.end(), [&](auto& m) { + return m->m_output != g_pCompositor->m_unsafeOutput->m_output; + })) { + // restore from unsafe state + g_pCompositor->leaveUnsafeState(); + } + + return false; // cannot draw on session inactive (different tty) + } + + if (!m_monitor->m_enabled) + return false; + + return true; +} |
