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>
This commit is contained in:
Vaxry 2025-07-08 12:41:10 +02:00 committed by GitHub
parent 9856563f89
commit 8f948827a6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 209 additions and 84 deletions

View file

@ -3,7 +3,7 @@
#include "./math/Math.hpp"
#include <array>
constexpr static int DAMAGE_RING_PREVIOUS_LEN = 2;
constexpr static int DAMAGE_RING_PREVIOUS_LEN = 3;
class CDamageRing {
public:

View file

@ -29,6 +29,7 @@
#include <aquamarine/output/Output.hpp>
#include "debug/Log.hpp"
#include "debug/HyprNotificationOverlay.hpp"
#include "MonitorFrameScheduler.hpp"
#include <hyprutils/string/String.hpp>
#include <hyprutils/utils/ScopeGuard.hpp>
#include <cstring>
@ -67,7 +68,7 @@ void CMonitor::onConnect(bool noRule) {
g_pEventLoopManager->doLater([] { g_pConfigManager->ensurePersistentWorkspacesPresent(); });
m_listeners.frame = m_output->events.frame.registerListener([this](std::any d) { onMonitorFrame(); });
m_listeners.frame = m_output->events.frame.registerListener([this](std::any d) { m_frameScheduler->onFrame(); });
m_listeners.commit = m_output->events.commit.registerListener([this](std::any d) {
if (true) { // FIXME: E->state->committed & WLR_OUTPUT_STATE_BUFFER
PROTO::screencopy->onOutputCommit(m_self.lock());
@ -92,6 +93,8 @@ void CMonitor::onConnect(bool noRule) {
PROTO::presentation->onPresented(m_self.lock(), Time::steadyNow(), E.refresh, E.seq, E.flags);
else
PROTO::presentation->onPresented(m_self.lock(), Time::fromTimespec(E.when), E.refresh, E.seq, E.flags);
m_frameScheduler->onPresented();
});
m_listeners.destroy = m_output->events.destroy.registerListener([this](std::any d) {
@ -134,6 +137,8 @@ void CMonitor::onConnect(bool noRule) {
applyMonitorRule(&rule);
});
m_frameScheduler = makeUnique<CMonitorFrameScheduler>(m_self.lock());
m_tearingState.canTear = m_output->getBackend()->type() == Aquamarine::AQ_BACKEND_DRM;
m_name = m_output->name;
@ -306,6 +311,8 @@ void CMonitor::onDisconnect(bool destroy) {
m_renderTimer = nullptr;
}
m_frameScheduler.reset();
if (!m_enabled || g_pCompositor->m_isShuttingDown)
return;
@ -1562,69 +1569,6 @@ void CMonitor::debugLastPresentation(const std::string& message) {
m_lastPresentationTimer.getMillis() > 0 ? 1000.0f / m_lastPresentationTimer.getMillis() : 0.0f);
}
void CMonitor::onMonitorFrame() {
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; // cannot draw on session inactive (different tty)
}
if (!m_enabled)
return;
g_pHyprRenderer->recheckSolitaryForMonitor(m_self.lock());
m_tearingState.busy = false;
if (m_tearingState.activelyTearing && m_solitaryClient.lock() /* can be invalidated by a recheck */) {
if (!m_tearingState.frameScheduledWhileBusy)
return; // we did not schedule a frame yet to be displayed, but we are tearing. Why render?
m_tearingState.nextRenderTorn = true;
m_tearingState.frameScheduledWhileBusy = false;
}
static auto PENABLERAT = CConfigValue<Hyprlang::INT>("misc:render_ahead_of_time");
static auto PRATSAFE = CConfigValue<Hyprlang::INT>("misc:render_ahead_safezone");
m_lastPresentationTimer.reset();
if (*PENABLERAT && !m_tearingState.nextRenderTorn) {
if (!m_ratsScheduled) {
// render
g_pHyprRenderer->renderMonitor(m_self.lock());
}
m_ratsScheduled = false;
const auto& [avg, max, min] = g_pHyprRenderer->getRenderTimes(m_self.lock());
if (max + *PRATSAFE > 1000.0 / m_refreshRate)
return;
const auto MSLEFT = (1000.0 / m_refreshRate) - m_lastPresentationTimer.getMillis();
m_ratsScheduled = true;
const auto ESTRENDERTIME = std::ceil(avg + *PRATSAFE);
const auto TIMETOSLEEP = std::floor(MSLEFT - ESTRENDERTIME);
if (MSLEFT < 1 || MSLEFT < ESTRENDERTIME || TIMETOSLEEP < 1)
g_pHyprRenderer->renderMonitor(m_self.lock());
else
wl_event_source_timer_update(m_renderTimer, TIMETOSLEEP);
} else
g_pHyprRenderer->renderMonitor(m_self.lock());
}
void CMonitor::onCursorMovedOnMonitor() {
if (!m_tearingState.activelyTearing || !m_solitaryClient || !g_pHyprRenderer->shouldRenderCursor())
return;
@ -1717,7 +1661,7 @@ bool CMonitorState::updateSwapchain() {
}
options.format = m_owner->m_drmFormat;
options.scanout = true;
options.length = 2;
options.length = 3;
options.size = MODE->pixelSize;
return m_owner->m_output->swapchain->reconfigure(options);
}

View file

@ -20,6 +20,8 @@
#include <aquamarine/allocator/Swapchain.hpp>
#include <hyprutils/os/FileDescriptor.hpp>
class CMonitorFrameScheduler;
// Enum for the different types of auto directions, e.g. auto-left, auto-up.
enum eAutoDirs : uint8_t {
DIR_AUTO_NONE = 0, /* None will be treated as right. */
@ -160,6 +162,8 @@ class CMonitor {
PHLMONITORREF m_self;
UP<CMonitorFrameScheduler> m_frameScheduler;
// mirroring
PHLMONITORREF m_mirrorOf;
std::vector<PHLMONITORREF> m_mirrors;
@ -228,7 +232,6 @@ class CMonitor {
void onCursorMovedOnMonitor();
void debugLastPresentation(const std::string& message);
void onMonitorFrame();
bool supportsWideColor();
bool supportsHDR();

View file

@ -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;
}

View file

@ -0,0 +1,35 @@
#pragma once
#include "Monitor.hpp"
#include <chrono>
class CEGLSync;
class CMonitorFrameScheduler {
public:
using hrc = std::chrono::high_resolution_clock;
CMonitorFrameScheduler(PHLMONITOR m);
CMonitorFrameScheduler(const CMonitorFrameScheduler&) = delete;
CMonitorFrameScheduler(CMonitorFrameScheduler&&) = delete;
CMonitorFrameScheduler& operator=(const CMonitorFrameScheduler&) = delete;
CMonitorFrameScheduler& operator=(CMonitorFrameScheduler&&) = delete;
void onSyncFired();
void onPresented();
void onFrame();
private:
bool canRender();
void onFinishRender();
bool m_renderAtFrame = true;
bool m_pendingThird = false;
hrc::time_point m_lastRenderBegun;
PHLMONITORREF m_monitor;
UP<CEGLSync> m_sync;
};