renderer: skip ds commits if buffer didn't change (#9556)

this fixes direct scanout glitches by ensuring that attemptDirectScanout doesn't try to recommit the same buffer to AQ
which would cause a pageflip event and the backendRelease to release the same buffer too early
This commit is contained in:
Ikalco 2025-03-08 13:24:22 -06:00 committed by GitHub
parent f15b49e0fd
commit d30cc19d25
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 54 additions and 61 deletions

View file

@ -1280,23 +1280,26 @@ bool CMonitor::attemptDirectScanout() {
const auto PSURFACE = g_pXWaylandManager->getWindowSurface(PCANDIDATE);
if (!PSURFACE || !PSURFACE->current.buffer || PSURFACE->current.bufferSize != vecPixelSize || PSURFACE->current.transform != transform)
if (!PSURFACE || !PSURFACE->current.texture || !PSURFACE->current.buffer || PSURFACE->current.buffer->buffer.expired())
return false;
if (PSURFACE->current.bufferSize != vecPixelSize || PSURFACE->current.transform != transform)
return false;
// we can't scanout shm buffers.
if (!PSURFACE->current.buffer || !PSURFACE->current.buffer->buffer || !PSURFACE->current.texture || !PSURFACE->current.texture->m_pEglImage /* dmabuf */)
const auto params = PSURFACE->current.buffer->buffer->dmabuf();
if (!params.success || !PSURFACE->current.texture->m_pEglImage /* dmabuf */)
return false;
Debug::log(TRACE, "attemptDirectScanout: surface {:x} passed, will attempt", (uintptr_t)PSURFACE.get());
Debug::log(TRACE, "attemptDirectScanout: surface {:x} passed, will attempt, buffer {}", (uintptr_t)PSURFACE.get(), (uintptr_t)PSURFACE->current.buffer->buffer.get());
auto PBUFFER = PSURFACE->current.buffer->buffer.lock();
if (PBUFFER == output->state->state().buffer)
return true;
// FIXME: make sure the buffer actually follows the available scanout dmabuf formats
// and comes from the appropriate device. This may implode on multi-gpu!!
const auto params = PSURFACE->current.buffer->buffer->dmabuf();
// scanout buffer isn't dmabuf, so no scanout
if (!params.success)
return false;
// entering into scanout, so save monitor format
if (lastScanout.expired())
prevDrmFormat = drmFormat;
@ -1306,7 +1309,7 @@ bool CMonitor::attemptDirectScanout() {
drmFormat = params.format;
}
output->state->setBuffer(PSURFACE->current.buffer->buffer.lock());
output->state->setBuffer(PBUFFER);
output->state->setPresentationMode(tearingState.activelyTearing ? Aquamarine::eOutputPresentationMode::AQ_OUTPUT_PRESENTATION_IMMEDIATE :
Aquamarine::eOutputPresentationMode::AQ_OUTPUT_PRESENTATION_VSYNC);
@ -1315,20 +1318,6 @@ bool CMonitor::attemptDirectScanout() {
return false;
}
auto explicitOptions = g_pHyprRenderer->getExplicitSyncSettings(output);
// wait for the explicit fence if present, and if kms explicit is allowed
bool DOEXPLICIT = PSURFACE->syncobj && PSURFACE->syncobj->current.acquireTimeline && PSURFACE->syncobj->current.acquireTimeline->timeline && explicitOptions.explicitKMSEnabled;
CFileDescriptor explicitWaitFD;
if (DOEXPLICIT) {
explicitWaitFD = PSURFACE->syncobj->current.acquireTimeline->timeline->exportAsSyncFileFD(PSURFACE->syncobj->current.acquirePoint);
if (!explicitWaitFD.isValid())
Debug::log(TRACE, "attemptDirectScanout: failed to acquire an explicit wait fd");
}
DOEXPLICIT = DOEXPLICIT && explicitWaitFD.isValid();
auto cleanup = CScopeGuard([this]() { output->state->resetExplicitFences(); });
timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
PSURFACE->presentFeedback(&now, self.lock());
@ -1336,11 +1325,24 @@ bool CMonitor::attemptDirectScanout() {
output->state->addDamage(CBox{{}, vecPixelSize});
output->state->resetExplicitFences();
auto cleanup = CScopeGuard([this]() { output->state->resetExplicitFences(); });
auto explicitOptions = g_pHyprRenderer->getExplicitSyncSettings(output);
bool DOEXPLICIT = PSURFACE->syncobj && PSURFACE->syncobj->current.acquireTimeline && PSURFACE->syncobj->current.acquireTimeline->timeline && explicitOptions.explicitKMSEnabled;
if (DOEXPLICIT) {
Debug::log(TRACE, "attemptDirectScanout: setting IN_FENCE for aq to {}", explicitWaitFD.get());
output->state->setExplicitInFence(explicitWaitFD.get());
// wait for surface's explicit fence if present
CFileDescriptor fd = PSURFACE->syncobj->current.acquireTimeline->timeline->exportAsSyncFileFD(PSURFACE->syncobj->current.acquirePoint);
if (fd.isValid()) {
Debug::log(TRACE, "attemptDirectScanout: setting IN_FENCE for aq to {}", fd.get());
output->state->setExplicitInFence(fd.get());
} else
Debug::log(TRACE, "attemptDirectScanout: failed to acquire an sync file fd for aq IN_FENCE");
DOEXPLICIT = fd.isValid();
}
commitSeq++;
bool ok = output->commit();
if (!ok && DOEXPLICIT) {
@ -1350,28 +1352,24 @@ bool CMonitor::attemptDirectScanout() {
ok = output->commit();
}
if (ok) {
if (!ok) {
Debug::log(TRACE, "attemptDirectScanout: failed to scanout surface");
lastScanout.reset();
return false;
}
if (lastScanout.expired()) {
lastScanout = PCANDIDATE;
Debug::log(LOG, "Entered a direct scanout to {:x}: \"{}\"", (uintptr_t)PCANDIDATE.get(), PCANDIDATE->m_szTitle);
}
// delay explicit sync feedback until kms release of the buffer
if (DOEXPLICIT) {
Debug::log(TRACE, "attemptDirectScanout: Delaying explicit sync release feedback until kms release");
PSURFACE->current.buffer->releaser->drop();
if (!PBUFFER->lockedByBackend)
return true;
PSURFACE->current.buffer->buffer->hlEvents.backendRelease2 = PSURFACE->current.buffer->buffer->events.backendRelease.registerListener([PSURFACE](std::any d) {
const bool DOEXPLICIT = PSURFACE->syncobj && PSURFACE->syncobj->current.releaseTimeline && PSURFACE->syncobj->current.releaseTimeline->timeline;
if (DOEXPLICIT)
PSURFACE->syncobj->current.releaseTimeline->timeline->signal(PSURFACE->syncobj->current.releasePoint);
});
}
} else {
Debug::log(TRACE, "attemptDirectScanout: failed to scanout surface");
lastScanout.reset();
return false;
}
// lock buffer while DRM/KMS is using it, then release it when page flip happens since DRM/KMS should be done by then
// btw buffer's syncReleaser will take care of signaling release point, so we don't do that here
PBUFFER->lock();
PBUFFER->onBackendRelease([PBUFFER]() { PBUFFER->unlock(); });
return true;
}

View file

@ -440,7 +440,6 @@ void CWLSurfaceResource::unlockPendingState() {
void CWLSurfaceResource::commitPendingState() {
static auto PDROP = CConfigValue<Hyprlang::INT>("render:allow_early_buffer_release");
auto const previousBuffer = current.buffer;
current = pending;
pending.damage.clear();
pending.bufferDamage.clear();
@ -495,15 +494,6 @@ void CWLSurfaceResource::commitPendingState() {
nullptr);
}
// for async buffers, we can only release the buffer once we are unrefing it from current.
// if the backend took it, ref it with the lambda. Otherwise, the end of this scope will release it.
if (previousBuffer && previousBuffer->buffer && !previousBuffer->buffer->isSynchronous()) {
if (previousBuffer->buffer->lockedByBackend && !previousBuffer->buffer->hlEvents.backendRelease) {
previousBuffer->buffer->lock();
previousBuffer->buffer->unlockOnBufferRelease(self);
}
}
lastBuffer = current.buffer ? current.buffer->buffer : WP<IHLBuffer>{};
}

View file

@ -26,9 +26,14 @@ bool IHLBuffer::locked() {
return nLocks > 0;
}
void IHLBuffer::unlockOnBufferRelease(WP<CWLSurfaceResource> surf) {
hlEvents.backendRelease = events.backendRelease.registerListener([this](std::any data) {
unlock();
void IHLBuffer::onBackendRelease(const std::function<void()>& fn) {
if (hlEvents.backendRelease) {
hlEvents.backendRelease->emit(nullptr);
Debug::log(LOG, "backendRelease emitted early");
}
hlEvents.backendRelease = events.backendRelease.registerListener([this, fn](std::any) {
fn();
hlEvents.backendRelease.reset();
});
}

View file

@ -21,7 +21,7 @@ class IHLBuffer : public Aquamarine::IBuffer {
virtual void unlock();
virtual bool locked();
void unlockOnBufferRelease(WP<CWLSurfaceResource> surf /* optional */);
void onBackendRelease(const std::function<void()>& fn);
SP<CTexture> texture;
bool opaque = false;

View file

@ -1486,6 +1486,8 @@ static hdr_output_metadata createHDRMetadata(SImageDescription settings, Aquamar
}
bool CHyprRenderer::commitPendingAndDoExplicitSync(PHLMONITOR pMonitor) {
pMonitor->commitSeq++;
// apply timelines for explicit sync
// save inFD otherwise reset will reset it
CFileDescriptor inFD{pMonitor->output->state->state().explicitInFence};
@ -2278,8 +2280,6 @@ void CHyprRenderer::endRender() {
const auto PMONITOR = g_pHyprOpenGL->m_RenderData.pMonitor;
static auto PNVIDIAANTIFLICKER = CConfigValue<Hyprlang::INT>("opengl:nvidia_anti_flicker");
PMONITOR->commitSeq++;
g_pHyprOpenGL->m_RenderData.damage = m_sRenderPass.render(g_pHyprOpenGL->m_RenderData.damage);
auto cleanup = CScopeGuard([this]() {