diff --git a/CMakeLists.txt b/CMakeLists.txt index 8be3a93a..0cfaa269 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -104,7 +104,7 @@ find_package(Threads REQUIRED) set(GLES_VERSION "GLES3") find_package(OpenGL REQUIRED COMPONENTS ${GLES_VERSION}) -pkg_check_modules(aquamarine_dep REQUIRED IMPORTED_TARGET aquamarine>=0.8.0) +pkg_check_modules(aquamarine_dep REQUIRED IMPORTED_TARGET aquamarine>=0.9.0) pkg_check_modules(hyprlang_dep REQUIRED IMPORTED_TARGET hyprlang>=0.3.2) pkg_check_modules(hyprcursor_dep REQUIRED IMPORTED_TARGET hyprcursor>=0.1.7) pkg_check_modules(hyprutils_dep REQUIRED IMPORTED_TARGET hyprutils>=0.7.0) diff --git a/meson.build b/meson.build index 58ec2475..51cac45f 100644 --- a/meson.build +++ b/meson.build @@ -31,7 +31,7 @@ if cpp_compiler.check_header('execinfo.h') add_project_arguments('-DHAS_EXECINFO', language: 'cpp') endif -aquamarine = dependency('aquamarine', version: '>=0.8.0') +aquamarine = dependency('aquamarine', version: '>=0.9.0') hyprcursor = dependency('hyprcursor', version: '>=0.1.7') hyprgraphics = dependency('hyprgraphics', version: '>= 0.1.3') hyprlang = dependency('hyprlang', version: '>= 0.3.2') diff --git a/src/Compositor.cpp b/src/Compositor.cpp index 64583b1c..3c0a4ec3 100644 --- a/src/Compositor.cpp +++ b/src/Compositor.cpp @@ -62,6 +62,7 @@ #include "hyprerror/HyprError.hpp" #include "debug/HyprNotificationOverlay.hpp" #include "debug/HyprDebugOverlay.hpp" +#include "helpers/MonitorFrameScheduler.hpp" #include #include @@ -3097,7 +3098,7 @@ void CCompositor::onNewMonitor(SP output) { } g_pHyprRenderer->damageMonitor(PNEWMONITOR); - PNEWMONITOR->onMonitorFrame(); + PNEWMONITOR->m_frameScheduler->onFrame(); if (PROTO::colorManagement && shouldChangePreferredImageDescription()) { Debug::log(ERR, "FIXME: color management protocol is enabled, need a preferred image description id"); diff --git a/src/config/ConfigDescriptions.hpp b/src/config/ConfigDescriptions.hpp index 1af5a3ca..cf256244 100644 --- a/src/config/ConfigDescriptions.hpp +++ b/src/config/ConfigDescriptions.hpp @@ -1488,6 +1488,12 @@ inline static const std::vector CONFIG_OPTIONS = { .type = CONFIG_OPTION_INT, .data = SConfigOptionDescription::SRangeData{.value = 1, .min = 0, .max = 2}, }, + SConfigOptionDescription{ + .value = "render:new_render_scheduling", + .description = "enable new render scheduling, which should improve FPS on underpowered devices. This does not add latency when your PC can keep up.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, /* * cursor: @@ -1897,16 +1903,21 @@ inline static const std::vector CONFIG_OPTIONS = { .type = CONFIG_OPTION_BOOL, .data = SConfigOptionDescription::SBoolData{true}, }, - SConfigOptionDescription{ - .value = "experimental:xx_color_management_v4", - .description = "enable color management protocol", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, SConfigOptionDescription{ .value = "master:always_keep_position", .description = "whether to keep the master window in its configured position when there are no slave windows", .type = CONFIG_OPTION_BOOL, .data = SConfigOptionDescription::SBoolData{false}, }, + + /* + * Experimental + */ + + SConfigOptionDescription{ + .value = "experimental:xx_color_management_v4", + .description = "enable color management protocol", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, }; diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index f98efee3..7b886097 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -749,6 +749,7 @@ CConfigManager::CConfigManager() { registerConfigVar("render:cm_enabled", Hyprlang::INT{1}); registerConfigVar("render:send_content_type", Hyprlang::INT{1}); registerConfigVar("render:cm_auto_hdr", Hyprlang::INT{1}); + registerConfigVar("render:new_render_scheduling", Hyprlang::INT{1}); registerConfigVar("ecosystem:no_update_news", Hyprlang::INT{0}); registerConfigVar("ecosystem:no_donation_nag", Hyprlang::INT{0}); diff --git a/src/helpers/DamageRing.hpp b/src/helpers/DamageRing.hpp index f53d6bf0..4750648b 100644 --- a/src/helpers/DamageRing.hpp +++ b/src/helpers/DamageRing.hpp @@ -3,7 +3,7 @@ #include "./math/Math.hpp" #include -constexpr static int DAMAGE_RING_PREVIOUS_LEN = 2; +constexpr static int DAMAGE_RING_PREVIOUS_LEN = 3; class CDamageRing { public: diff --git a/src/helpers/Monitor.cpp b/src/helpers/Monitor.cpp index 6874ac53..e29d9963 100644 --- a/src/helpers/Monitor.cpp +++ b/src/helpers/Monitor.cpp @@ -29,6 +29,7 @@ #include #include "debug/Log.hpp" #include "debug/HyprNotificationOverlay.hpp" +#include "MonitorFrameScheduler.hpp" #include #include #include @@ -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(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("misc:render_ahead_of_time"); - static auto PRATSAFE = CConfigValue("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); } diff --git a/src/helpers/Monitor.hpp b/src/helpers/Monitor.hpp index 946f6a58..1cde19f1 100644 --- a/src/helpers/Monitor.hpp +++ b/src/helpers/Monitor.hpp @@ -20,6 +20,8 @@ #include #include +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 m_frameScheduler; + // mirroring PHLMONITORREF m_mirrorOf; std::vector m_mirrors; @@ -228,7 +232,6 @@ class CMonitor { void onCursorMovedOnMonitor(); void debugLastPresentation(const std::string& message); - void onMonitorFrame(); bool supportsWideColor(); bool supportsHDR(); 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("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(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("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("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; +} diff --git a/src/helpers/MonitorFrameScheduler.hpp b/src/helpers/MonitorFrameScheduler.hpp new file mode 100644 index 00000000..f2663342 --- /dev/null +++ b/src/helpers/MonitorFrameScheduler.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include "Monitor.hpp" + +#include + +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 m_sync; +}; diff --git a/src/render/Renderer.cpp b/src/render/Renderer.cpp index e85d59b5..da52eaef 100644 --- a/src/render/Renderer.cpp +++ b/src/render/Renderer.cpp @@ -1153,7 +1153,7 @@ void CHyprRenderer::calculateUVForSurface(PHLWINDOW pWindow, SPm_output->state->setPresentationMode(shouldTear ? Aquamarine::eOutputPresentationMode::AQ_OUTPUT_PRESENTATION_IMMEDIATE : Aquamarine::eOutputPresentationMode::AQ_OUTPUT_PRESENTATION_VSYNC); - commitPendingAndDoExplicitSync(pMonitor); + if (commit) + commitPendingAndDoExplicitSync(pMonitor); if (shouldTear) pMonitor->m_tearingState.busy = true; @@ -2247,12 +2248,10 @@ bool CHyprRenderer::beginRender(PHLMONITOR pMonitor, CRegion& damage, eRenderMod return true; } - /* This is a constant expression, as we always use double-buffering in our swapchain - TODO: Rewrite the CDamageRing to take advantage of that maybe? It's made to support longer swapchains atm because we used to do wlroots */ - static constexpr const int HL_BUFFER_AGE = 2; + int bufferAge = 0; if (!buffer) { - m_currentBuffer = pMonitor->m_output->swapchain->next(nullptr); + m_currentBuffer = pMonitor->m_output->swapchain->next(&bufferAge); if (!m_currentBuffer) { Debug::log(ERR, "Failed to acquire swapchain buffer for {}", pMonitor->m_name); return false; @@ -2273,7 +2272,7 @@ bool CHyprRenderer::beginRender(PHLMONITOR pMonitor, CRegion& damage, eRenderMod } if (mode == RENDER_MODE_NORMAL) { - damage = pMonitor->m_damage.getBufferDamage(HL_BUFFER_AGE); + damage = pMonitor->m_damage.getBufferDamage(bufferAge); pMonitor->m_damage.rotate(); } diff --git a/src/render/Renderer.hpp b/src/render/Renderer.hpp index 2fac401a..ad4819eb 100644 --- a/src/render/Renderer.hpp +++ b/src/render/Renderer.hpp @@ -51,7 +51,7 @@ class CHyprRenderer { CHyprRenderer(); ~CHyprRenderer(); - void renderMonitor(PHLMONITOR pMonitor); + void renderMonitor(PHLMONITOR pMonitor, bool commit = true); void arrangeLayersForMonitor(const MONITORID&); void damageSurface(SP, double, double, double scale = 1.0); void damageWindow(PHLWINDOW, bool forceFull = false); @@ -156,6 +156,7 @@ class CHyprRenderer { friend class CInputManager; friend class CPointerManager; friend class CMonitor; + friend class CMonitorFrameScheduler; }; inline UP g_pHyprRenderer;