renderer: fix frame sync (#13061)

* fix commit timing timer

* fix surface state lock/unlock

* debug state sync todos

* debug solitary vrr
This commit is contained in:
UjinT34 2026-01-22 18:16:52 +03:00 committed by GitHub
parent 22fc8136a2
commit 82de66a030
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 106 additions and 67 deletions

View file

@ -1830,6 +1830,30 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.type = CONFIG_OPTION_BOOL, .type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false}, .data = SConfigOptionDescription::SBoolData{false},
}, },
SConfigOptionDescription{
.value = "debug:ds_handle_same_buffer",
.description = "Special case for DS with unmodified buffer",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{true},
},
SConfigOptionDescription{
.value = "debug:ds_handle_same_buffer_fifo",
.description = "Special case for DS with unmodified buffer unlocks fifo",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{true},
},
SConfigOptionDescription{
.value = "debug:fifo_pending_workaround",
.description = "Fifo workaround for empty pending list",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{true},
},
SConfigOptionDescription{
.value = "debug:render_solitary_wo_damage",
.description = "Render solitary window with empty damage",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
},
/* /*
* dwindle: * dwindle:

View file

@ -571,6 +571,10 @@ CConfigManager::CConfigManager() {
registerConfigVar("debug:disable_scale_checks", Hyprlang::INT{0}); registerConfigVar("debug:disable_scale_checks", Hyprlang::INT{0});
registerConfigVar("debug:colored_stdout_logs", Hyprlang::INT{1}); registerConfigVar("debug:colored_stdout_logs", Hyprlang::INT{1});
registerConfigVar("debug:full_cm_proto", Hyprlang::INT{0}); registerConfigVar("debug:full_cm_proto", Hyprlang::INT{0});
registerConfigVar("debug:ds_handle_same_buffer", Hyprlang::INT{1});
registerConfigVar("debug:ds_handle_same_buffer_fifo", Hyprlang::INT{1});
registerConfigVar("debug:fifo_pending_workaround", Hyprlang::INT{1});
registerConfigVar("debug:render_solitary_wo_damage", Hyprlang::INT{0});
registerConfigVar("decoration:rounding", Hyprlang::INT{0}); registerConfigVar("decoration:rounding", Hyprlang::INT{0});
registerConfigVar("decoration:rounding_power", {2.F}); registerConfigVar("decoration:rounding_power", {2.F});

View file

@ -1823,7 +1823,10 @@ uint16_t CMonitor::isDSBlocked(bool full) {
} }
bool CMonitor::attemptDirectScanout() { bool CMonitor::attemptDirectScanout() {
const auto blockedReason = isDSBlocked(); static const auto PSAME = CConfigValue<Hyprlang::INT>("debug:ds_handle_same_buffer");
static const auto PSAMEFIFO = CConfigValue<Hyprlang::INT>("debug:ds_handle_same_buffer_fifo");
const auto blockedReason = isDSBlocked();
if (blockedReason) if (blockedReason)
return false; return false;
@ -1837,7 +1840,7 @@ bool CMonitor::attemptDirectScanout() {
auto PBUFFER = PSURFACE->m_current.buffer.m_buffer; auto PBUFFER = PSURFACE->m_current.buffer.m_buffer;
// #TODO this entire bit needs figuring out, vrr goes down the drain without it // #TODO this entire bit needs figuring out, vrr goes down the drain without it
if (PBUFFER == m_output->state->state().buffer) { if (PBUFFER == m_output->state->state().buffer && *PSAME) {
PSURFACE->presentFeedback(Time::steadyNow(), m_self.lock()); PSURFACE->presentFeedback(Time::steadyNow(), m_self.lock());
if (m_scanoutNeedsCursorUpdate) { if (m_scanoutNeedsCursorUpdate) {
@ -1856,7 +1859,7 @@ bool CMonitor::attemptDirectScanout() {
} }
//#TODO this entire bit is bootleg deluxe, above bit is to not make vrr go down the drain, returning early here means fifo gets forever locked. //#TODO this entire bit is bootleg deluxe, above bit is to not make vrr go down the drain, returning early here means fifo gets forever locked.
if (PSURFACE->m_fifo) if (PSURFACE->m_fifo && *PSAMEFIFO)
PSURFACE->m_stateQueue.unlockFirst(LOCK_REASON_FIFO); PSURFACE->m_stateQueue.unlockFirst(LOCK_REASON_FIFO);
return true; return true;

View file

@ -32,9 +32,7 @@ CCommitTimerResource::CCommitTimerResource(UP<CWpCommitTimerV1>&& resource_, SP<
const auto TIME_NOW = Time::steadyNow(); const auto TIME_NOW = Time::steadyNow();
if (TIME_NOW > TIME) { if (TIME_NOW > TIME) {
// TODO: should we err here? m_pendingTimeout.reset();
// for now just do nothing I guess, thats some lag.
m_pendingTimeout = Time::steady_dur::min();
} else } else
m_pendingTimeout = TIME - TIME_NOW; m_pendingTimeout = TIME - TIME_NOW;
}); });
@ -56,6 +54,7 @@ CCommitTimerResource::CCommitTimerResource(UP<CWpCommitTimerV1>&& resource_, SP<
m_surface->m_stateQueue.unlockFirst(LOCK_REASON_TIMER); m_surface->m_stateQueue.unlockFirst(LOCK_REASON_TIMER);
}, },
nullptr); nullptr);
g_pEventLoopManager->addTimer(timer);
} else } else
timer->updateTimeout(m_pendingTimeout); timer->updateTimeout(m_pendingTimeout);
@ -64,7 +63,8 @@ CCommitTimerResource::CCommitTimerResource(UP<CWpCommitTimerV1>&& resource_, SP<
} }
CCommitTimerResource::~CCommitTimerResource() { CCommitTimerResource::~CCommitTimerResource() {
; if (m_timerPresent)
g_pEventLoopManager->removeTimer(timer);
} }
bool CCommitTimerResource::good() { bool CCommitTimerResource::good() {

View file

@ -34,35 +34,40 @@ CFifoResource::CFifoResource(UP<CWpFifoV1>&& resource_, SP<CWLSurfaceResource> s
}); });
m_listeners.surfaceStateCommit = m_surface->m_events.stateCommit.listen([this](auto state) { m_listeners.surfaceStateCommit = m_surface->m_events.stateCommit.listen([this](auto state) {
static const auto PPEND = CConfigValue<Hyprlang::INT>("debug:fifo_pending_workaround");
if (!m_pending.surfaceLocked) if (!m_pending.surfaceLocked)
return; return;
//#TODO: if (*PPEND) {
// this feels wrong, but if we have no pending frames, presented might never come because //#TODO:
// we are waiting on the barrier to unlock and no damage is around. // this feels wrong, but if we have no pending frames, presented might never come because
if (m_surface->m_enteredOutputs.empty() && m_surface->m_hlSurface) { // we are waiting on the barrier to unlock and no damage is around.
for (auto& m : g_pCompositor->m_monitors) { // unlock on timeout instead?
if (!m || !m->m_enabled) if (m_surface->m_enteredOutputs.empty() && m_surface->m_hlSurface) {
continue; for (auto& m : g_pCompositor->m_monitors) {
if (!m || !m->m_enabled)
continue;
auto box = m_surface->m_hlSurface->getSurfaceBoxGlobal();
if (box && !box->intersection({m->m_position, m->m_size}).empty()) {
if (m->m_tearingState.activelyTearing)
return; // dont fifo lock on tearing.
g_pCompositor->scheduleFrameForMonitor(m, Aquamarine::IOutput::AQ_SCHEDULE_NEEDS_FRAME);
}
}
} else {
for (auto& m : m_surface->m_enteredOutputs) {
if (!m)
continue;
auto box = m_surface->m_hlSurface->getSurfaceBoxGlobal();
if (box && !box->intersection({m->m_position, m->m_size}).empty()) {
if (m->m_tearingState.activelyTearing) if (m->m_tearingState.activelyTearing)
return; // dont fifo lock on tearing. return; // dont fifo lock on tearing.
g_pCompositor->scheduleFrameForMonitor(m, Aquamarine::IOutput::AQ_SCHEDULE_NEEDS_FRAME); g_pCompositor->scheduleFrameForMonitor(m.lock(), Aquamarine::IOutput::AQ_SCHEDULE_NEEDS_FRAME);
} }
} }
} else {
for (auto& m : m_surface->m_enteredOutputs) {
if (!m)
continue;
if (m->m_tearingState.activelyTearing)
return; // dont fifo lock on tearing.
g_pCompositor->scheduleFrameForMonitor(m.lock(), Aquamarine::IOutput::AQ_SCHEDULE_NEEDS_FRAME);
}
} }
// only lock once its mapped. // only lock once its mapped.

View file

@ -506,13 +506,13 @@ void CWLSurfaceResource::scheduleState(WP<SSurfaceState> state) {
} }
} else if (state->buffer && state->buffer->isSynchronous()) { } else if (state->buffer && state->buffer->isSynchronous()) {
// synchronous (shm) buffers can be read immediately // synchronous (shm) buffers can be read immediately
m_stateQueue.unlock(state); m_stateQueue.unlock(state, LOCK_REASON_FENCE);
} else if (state->buffer && state->buffer->m_syncFd.isValid()) { } else if (state->buffer && state->buffer->m_syncFd.isValid()) {
// async buffer and is dmabuf, then we can wait on implicit fences // async buffer and is dmabuf, then we can wait on implicit fences
g_pEventLoopManager->doOnReadable(std::move(state->buffer->m_syncFd), [state, whenReadable]() { whenReadable(state, LOCK_REASON_FENCE); }); g_pEventLoopManager->doOnReadable(std::move(state->buffer->m_syncFd), [state, whenReadable]() { whenReadable(state, LOCK_REASON_FENCE); });
} else { } else {
// state commit without a buffer. // state commit without a buffer.
m_stateQueue.unlock(state); m_stateQueue.unlock(state, LOCK_REASON_FENCE);
} }
} }

View file

@ -21,6 +21,7 @@ void CSurfaceStateQueue::dropState(const WP<SSurfaceState>& state) {
} }
void CSurfaceStateQueue::lock(const WP<SSurfaceState>& weakState, eLockReason reason) { void CSurfaceStateQueue::lock(const WP<SSurfaceState>& weakState, eLockReason reason) {
ASSERT(reason != LOCK_REASON_NONE);
auto it = find(weakState); auto it = find(weakState);
if (it == m_queue.end()) if (it == m_queue.end())
return; return;
@ -29,6 +30,7 @@ void CSurfaceStateQueue::lock(const WP<SSurfaceState>& weakState, eLockReason re
} }
void CSurfaceStateQueue::unlock(const WP<SSurfaceState>& state, eLockReason reason) { void CSurfaceStateQueue::unlock(const WP<SSurfaceState>& state, eLockReason reason) {
ASSERT(reason != LOCK_REASON_NONE);
auto it = find(state); auto it = find(state);
if (it == m_queue.end()) if (it == m_queue.end())
return; return;
@ -38,6 +40,7 @@ void CSurfaceStateQueue::unlock(const WP<SSurfaceState>& state, eLockReason reas
} }
void CSurfaceStateQueue::unlockFirst(eLockReason reason) { void CSurfaceStateQueue::unlockFirst(eLockReason reason) {
ASSERT(reason != LOCK_REASON_NONE);
for (auto& it : m_queue) { for (auto& it : m_queue) {
if ((it->lockMask & reason) != LOCK_REASON_NONE) { if ((it->lockMask & reason) != LOCK_REASON_NONE) {
it->lockMask &= ~reason; it->lockMask &= ~reason;

View file

@ -15,7 +15,7 @@ class CSurfaceStateQueue {
WP<SSurfaceState> enqueue(UP<SSurfaceState>&& state); WP<SSurfaceState> enqueue(UP<SSurfaceState>&& state);
void dropState(const WP<SSurfaceState>& state); void dropState(const WP<SSurfaceState>& state);
void lock(const WP<SSurfaceState>& state, eLockReason reason); void lock(const WP<SSurfaceState>& state, eLockReason reason);
void unlock(const WP<SSurfaceState>& state, eLockReason reason = LOCK_REASON_NONE); void unlock(const WP<SSurfaceState>& state, eLockReason reason);
void unlockFirst(eLockReason reason); void unlockFirst(eLockReason reason);
void tryProcess(); void tryProcess();

View file

@ -1268,6 +1268,7 @@ void CHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) {
static auto PDEBUGOVERLAY = CConfigValue<Hyprlang::INT>("debug:overlay"); static auto PDEBUGOVERLAY = CConfigValue<Hyprlang::INT>("debug:overlay");
static auto PDAMAGETRACKINGMODE = CConfigValue<Hyprlang::INT>("debug:damage_tracking"); static auto PDAMAGETRACKINGMODE = CConfigValue<Hyprlang::INT>("debug:damage_tracking");
static auto PDAMAGEBLINK = CConfigValue<Hyprlang::INT>("debug:damage_blink"); static auto PDAMAGEBLINK = CConfigValue<Hyprlang::INT>("debug:damage_blink");
static auto PSOLDAMAGE = CConfigValue<Hyprlang::INT>("debug:render_solitary_wo_damage");
static auto PVFR = CConfigValue<Hyprlang::INT>("misc:vfr"); static auto PVFR = CConfigValue<Hyprlang::INT>("misc:vfr");
static int damageBlinkCleanup = 0; // because double-buffered static int damageBlinkCleanup = 0; // because double-buffered
@ -1398,46 +1399,45 @@ void CHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) {
bool renderCursor = true; bool renderCursor = true;
if (!finalDamage.empty()) { if (pMonitor->m_solitaryClient && (!finalDamage.empty() || *PSOLDAMAGE))
if (pMonitor->m_solitaryClient.expired()) { renderWindow(pMonitor->m_solitaryClient.lock(), pMonitor, NOW, false, RENDER_PASS_MAIN /* solitary = no popups */);
if (pMonitor->isMirror()) { else if (!finalDamage.empty()) {
g_pHyprOpenGL->blend(false); if (pMonitor->isMirror()) {
g_pHyprOpenGL->renderMirrored(); g_pHyprOpenGL->blend(false);
g_pHyprOpenGL->blend(true); g_pHyprOpenGL->renderMirrored();
EMIT_HOOK_EVENT("render", RENDER_POST_MIRROR); g_pHyprOpenGL->blend(true);
renderCursor = false; EMIT_HOOK_EVENT("render", RENDER_POST_MIRROR);
} else { renderCursor = false;
CBox renderBox = {0, 0, sc<int>(pMonitor->m_pixelSize.x), sc<int>(pMonitor->m_pixelSize.y)}; } else {
renderWorkspace(pMonitor, pMonitor->m_activeWorkspace, NOW, renderBox); CBox renderBox = {0, 0, sc<int>(pMonitor->m_pixelSize.x), sc<int>(pMonitor->m_pixelSize.y)};
renderWorkspace(pMonitor, pMonitor->m_activeWorkspace, NOW, renderBox);
renderLockscreen(pMonitor, NOW, renderBox); renderLockscreen(pMonitor, NOW, renderBox);
if (pMonitor == Desktop::focusState()->monitor()) { if (pMonitor == Desktop::focusState()->monitor()) {
g_pHyprNotificationOverlay->draw(pMonitor); g_pHyprNotificationOverlay->draw(pMonitor);
g_pHyprError->draw(); g_pHyprError->draw();
}
// for drawing the debug overlay
if (pMonitor == g_pCompositor->m_monitors.front() && *PDEBUGOVERLAY == 1) {
renderStartOverlay = std::chrono::high_resolution_clock::now();
g_pDebugOverlay->draw();
endRenderOverlay = std::chrono::high_resolution_clock::now();
}
if (*PDAMAGEBLINK && damageBlinkCleanup == 0) {
CRectPassElement::SRectData data;
data.box = {0, 0, pMonitor->m_transformedSize.x, pMonitor->m_transformedSize.y};
data.color = CHyprColor(1.0, 0.0, 1.0, 100.0 / 255.0);
m_renderPass.add(makeUnique<CRectPassElement>(data));
damageBlinkCleanup = 1;
} else if (*PDAMAGEBLINK) {
damageBlinkCleanup++;
if (damageBlinkCleanup > 3)
damageBlinkCleanup = 0;
}
} }
} else
renderWindow(pMonitor->m_solitaryClient.lock(), pMonitor, NOW, false, RENDER_PASS_MAIN /* solitary = no popups */); // for drawing the debug overlay
if (pMonitor == g_pCompositor->m_monitors.front() && *PDEBUGOVERLAY == 1) {
renderStartOverlay = std::chrono::high_resolution_clock::now();
g_pDebugOverlay->draw();
endRenderOverlay = std::chrono::high_resolution_clock::now();
}
if (*PDAMAGEBLINK && damageBlinkCleanup == 0) {
CRectPassElement::SRectData data;
data.box = {0, 0, pMonitor->m_transformedSize.x, pMonitor->m_transformedSize.y};
data.color = CHyprColor(1.0, 0.0, 1.0, 100.0 / 255.0);
m_renderPass.add(makeUnique<CRectPassElement>(data));
damageBlinkCleanup = 1;
} else if (*PDAMAGEBLINK) {
damageBlinkCleanup++;
if (damageBlinkCleanup > 3)
damageBlinkCleanup = 0;
}
}
} else if (!pMonitor->isMirror()) { } else if (!pMonitor->isMirror()) {
sendFrameEventsToWorkspace(pMonitor, pMonitor->m_activeWorkspace, NOW); sendFrameEventsToWorkspace(pMonitor, pMonitor->m_activeWorkspace, NOW);
if (pMonitor->m_activeSpecialWorkspace) if (pMonitor->m_activeSpecialWorkspace)