* directscanout: fix dgpu directscanout explicit sync without setting an infence, AQ doesnt explicit sync, nor recreate the dgpu fence for the blit work. and as such attemptdirectscanout path artifacts and breaks. create a dummy CEGLSync even tho we dont really have any pending glwork to get a proper fence, and set it. * monitor: dont use new scheduling if direct scanout using the new_render_scheduling makes no sense in the direct scanout path, add a if guard against it.
151 lines
5.4 KiB
C++
151 lines
5.4 KiB
C++
#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) {
|
|
;
|
|
}
|
|
|
|
bool CMonitorFrameScheduler::newSchedulingEnabled() {
|
|
static auto PENABLENEW = CConfigValue<Hyprlang::INT>("render:new_render_scheduling");
|
|
|
|
return *PENABLENEW && g_pHyprOpenGL->explicitSyncSupported() && m_monitor && !m_monitor->m_directScanoutIsActive;
|
|
}
|
|
|
|
void CMonitorFrameScheduler::onSyncFired() {
|
|
|
|
if (!newSchedulingEnabled())
|
|
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.
|
|
Log::logger->log(Log::TRACE, "CMonitorFrameScheduler: {} -> onSyncFired, didn't miss.", m_monitor->m_name);
|
|
m_renderAtFrame = true;
|
|
return;
|
|
}
|
|
|
|
Log::logger->log(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();
|
|
|
|
// get a ref to ourselves. renderMonitor can destroy this scheduler if it decides to perform a monitor reload
|
|
// FIXME: this is horrible. "renderMonitor" should not be able to do that.
|
|
auto self = m_self;
|
|
|
|
g_pHyprRenderer->renderMonitor(m_monitor.lock(), false);
|
|
|
|
if (!self)
|
|
return;
|
|
|
|
onFinishRender();
|
|
}
|
|
|
|
void CMonitorFrameScheduler::onPresented() {
|
|
if (!newSchedulingEnabled())
|
|
return;
|
|
|
|
if (!m_pendingThird)
|
|
return;
|
|
|
|
Log::logger->log(Log::TRACE, "CMonitorFrameScheduler: {} -> onPresented, missed, committing pending.", m_monitor->m_name);
|
|
|
|
m_pendingThird = false;
|
|
|
|
Log::logger->log(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()] {
|
|
if (!m)
|
|
return;
|
|
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() {
|
|
if (!canRender())
|
|
return;
|
|
|
|
m_monitor->recheckSolitary();
|
|
|
|
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 (!newSchedulingEnabled()) {
|
|
m_monitor->m_lastPresentationTimer.reset();
|
|
|
|
g_pHyprRenderer->renderMonitor(m_monitor.lock());
|
|
return;
|
|
}
|
|
|
|
if (!m_renderAtFrame) {
|
|
Log::logger->log(Log::TRACE, "CMonitorFrameScheduler: {} -> frame event, but m_renderAtFrame = false.", m_monitor->m_name);
|
|
return;
|
|
}
|
|
|
|
Log::logger->log(Log::TRACE, "CMonitorFrameScheduler: {} -> frame event, render = true, rendering normally.", m_monitor->m_name);
|
|
|
|
m_lastRenderBegun = hrc::now();
|
|
|
|
// get a ref to ourselves. renderMonitor can destroy this scheduler if it decides to perform a monitor reload
|
|
// FIXME: this is horrible. "renderMonitor" should not be able to do that.
|
|
auto self = m_self;
|
|
|
|
g_pHyprRenderer->renderMonitor(m_monitor.lock());
|
|
|
|
if (!self)
|
|
return;
|
|
|
|
onFinishRender();
|
|
}
|
|
|
|
void CMonitorFrameScheduler::onFinishRender() {
|
|
m_sync = CEGLSync::create(); // this destroys the old sync
|
|
g_pEventLoopManager->doOnReadable(m_sync->fd().duplicate(), [this, self = m_self] {
|
|
if (!self) // 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) {
|
|
Log::logger->log(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;
|
|
}
|