buffer: dont drop damage until actually read (#15718)

we cant drop dmabuf damage until we have actually hit a frame that reads
it, otherwise clients like firefox attaches a buffer with damage,
eventually sends a .frame() and .commit() then we clear the damage and
the render has nothing to use.
This commit is contained in:
Tom Englund
2026-08-06 15:04:55 +02:00
committed by GitHub
parent 64962f89e4
commit 4ffd88e5e6
5 changed files with 76 additions and 50 deletions
+21 -21
View File
@@ -85,52 +85,52 @@ CRegion CWLSurface::computeDamage(const std::optional<CBox>& box) const {
if (!m_resource->m_current.texture)
return {};
CRegion damage = m_resource->m_current.accumulateBufferDamage();
damage.transform(Math::wlTransformToHyprutils(m_resource->m_current.transform), m_resource->m_current.bufferSize.x, m_resource->m_current.bufferSize.y);
const auto BUFSIZE = m_resource->m_current.bufferSize;
if (BUFSIZE.x <= 0 || BUFSIZE.y <= 0)
return {};
const auto CORRECTVEC = correctSmallVecBuf();
if (m_resource->m_current.viewport.hasSource)
damage.intersect(m_resource->m_current.viewport.source);
const auto SCALEDSRCSIZE =
m_resource->m_current.viewport.hasSource ? m_resource->m_current.viewport.source.size() * m_resource->m_current.scale : m_resource->m_current.bufferSize;
if (SCALEDSRCSIZE.x <= 0 || SCALEDSRCSIZE.y <= 0)
return {};
damage.scale({BUFSIZE.x / SCALEDSRCSIZE.x, BUFSIZE.y / SCALEDSRCSIZE.y});
damage.translate(CORRECTVEC);
// go from buffer coords in the damage to hl logical
const auto SURFSIZE = m_resource->m_current.size;
if (SURFSIZE.x <= 0 || SURFSIZE.y <= 0)
return {};
const Vector2D SCALE = SURFSIZE / m_resource->m_current.bufferSize;
damage.scale(SCALE);
std::optional<Vector2D> boxSize;
if (box.has_value()) {
auto boxSize = box->size();
boxSize = box->size();
if (m_view->type() == VIEW_TYPE_WINDOW) {
const auto WINDOW = dynamicPointerCast<CWindow>(m_view.lock());
if (!WINDOW)
return {};
boxSize = boxSize * WINDOW->m_X11SurfaceScaledBy;
boxSize = boxSize.value() * WINDOW->m_X11SurfaceScaledBy;
}
if (boxSize.x <= 0 || boxSize.y <= 0)
if (boxSize->x <= 0 || boxSize->y <= 0)
return {};
damage.intersect(CBox{{}, boxSize});
}
const auto CORRECTVEC = correctSmallVecBuf();
CRegion damage = m_resource->m_current.accumulateBufferDamage();
damage.transform(Math::wlTransformToHyprutils(m_resource->m_current.transform), BUFSIZE.x, BUFSIZE.y);
if (m_resource->m_current.viewport.hasSource)
damage.intersect(m_resource->m_current.viewport.source);
damage.scale({BUFSIZE.x / SCALEDSRCSIZE.x, BUFSIZE.y / SCALEDSRCSIZE.y});
damage.translate(CORRECTVEC);
// go from buffer coords in the damage to hl logical
damage.scale(SURFSIZE / BUFSIZE);
if (boxSize)
damage.intersect(CBox{{}, boxSize.value()});
return damage;
}
+8 -10
View File
@@ -2785,6 +2785,8 @@ void CWindow::commitWindow() {
const auto PMONITOR = m_monitor.lock();
// damageSurface consumes damage, so snapshot it for the tearing check below
const bool HADDAMAGE = !wlSurface()->resource()->m_current.damage.empty() || !wlSurface()->resource()->m_current.bufferDamage.empty();
g_pHyprRenderer->damageSurface(wlSurface()->resource(), m_realPosition->goal().x, m_realPosition->goal().y, m_isX11 ? 1.0 / m_X11SurfaceScaledBy : 1.0);
if (!m_isX11) {
@@ -2794,16 +2796,12 @@ void CWindow::commitWindow() {
// tearing: if solitary, redraw it. This still might be a single surface window
if (PMONITOR && PMONITOR->m_solitaryClient.lock() == m_self.lock() && canBeTorn() && PMONITOR->m_tearingState.canTear && wlSurface()->resource()->m_current.texture &&
!PMONITOR->isTearingBlocked()) {
CRegion damageBox{wlSurface()->resource()->m_current.accumulateBufferDamage()};
if (!damageBox.empty()) {
if (PMONITOR->m_tearingState.busy) {
PMONITOR->m_tearingState.frameScheduledWhileBusy = true;
} else {
PMONITOR->m_tearingState.nextRenderTorn = true;
g_pHyprRenderer->renderMonitor(PMONITOR);
}
!PMONITOR->isTearingBlocked() && HADDAMAGE) {
if (PMONITOR->m_tearingState.busy) {
PMONITOR->m_tearingState.frameScheduledWhileBusy = true;
} else {
PMONITOR->m_tearingState.nextRenderTorn = true;
g_pHyprRenderer->renderMonitor(PMONITOR);
}
}
}
+11 -1
View File
@@ -2207,7 +2207,13 @@ bool CMonitor::attemptDirectScanout() {
PSURFACE->presentFeedback(Time::steadyNow(), m_self.lock());
m_output->state->addDamage(PSURFACE->m_current.accumulateBufferDamage());
// the commit path already added the damage into the damagering
CRegion scanoutDamage = m_damage.getBufferDamage(1);
// the ring is in transformed space, the fb we hand to KMS is in pixel space
scanoutDamage.transform(Math::wlTransformToHyprutils(Math::invertTransform(m_transform)), m_transformedSize.x, m_transformedSize.y);
// expand to not miss pixels from rounding, being a bit over is safe, going below is stale pixels.
scanoutDamage.expand(1).intersect(CBox{{}, m_pixelSize});
m_output->state->addDamage(scanoutDamage);
// multigpu needs a fence to trigger fence syncing blits and also committing with the recreated dgpu fence
if (g_pHyprRenderer->explicitSyncSupported() && isMultiGPU()) {
@@ -2235,6 +2241,9 @@ bool CMonitor::attemptDirectScanout() {
scanoutCommitted = true;
// the flip has used the damage, rotate it.
m_damage.rotate();
if (m_lastScanout.expired()) {
m_lastScanout = PCANDIDATE;
Log::logger->log(Log::DEBUG, "Entered a direct scanout to {:x}: \"{}\"", rc<uintptr_t>(PCANDIDATE.get()), PCANDIDATE->m_title);
@@ -2268,6 +2277,7 @@ void CMonitor::handleDSleave() {
m_drmFormat = m_prevDrmFormat;
m_blurFBDirty = true;
m_damage.damageEntire();
}
bool CMonitor::canAttemptDirectScanoutFast() const {
+35 -18
View File
@@ -17,24 +17,32 @@ Vector2D SSurfaceState::sourceSize() {
return trc / scale;
}
CRegion SSurfaceState::accumulateBufferDamage() {
if (damage.empty())
return bufferDamage;
bool SSurfaceState::consumeBufferDamage() const {
return buffer && !buffer->isSynchronous();
}
CRegion surfaceDamage = damage;
if (viewport.hasDestination) {
Vector2D scale = sourceSize() / viewport.destination;
surfaceDamage.scale(scale);
CRegion SSurfaceState::accumulateBufferDamage() {
if (!damage.empty()) {
CRegion surfaceDamage = damage;
if (viewport.hasDestination) {
Vector2D scale = sourceSize() / viewport.destination;
surfaceDamage.scale(scale);
}
if (viewport.hasSource)
surfaceDamage.translate(viewport.source.pos());
Vector2D trc = transform % 2 == 1 ? Vector2D{bufferSize.y, bufferSize.x} : bufferSize;
bufferDamage = surfaceDamage.scale(scale).transform(Math::wlTransformToHyprutils(Math::invertTransform(transform)), trc.x, trc.y).add(bufferDamage);
damage.clear();
}
if (viewport.hasSource)
surfaceDamage.translate(viewport.source.pos());
auto taken = bufferDamage;
if (consumeBufferDamage())
bufferDamage.clear();
Vector2D trc = transform % 2 == 1 ? Vector2D{bufferSize.y, bufferSize.x} : bufferSize;
bufferDamage = surfaceDamage.scale(scale).transform(Math::wlTransformToHyprutils(Math::invertTransform(transform)), trc.x, trc.y).add(bufferDamage);
damage.clear();
return bufferDamage;
return taken;
}
CRegion SSurfaceState::effectiveInputRegion() const {
@@ -181,10 +189,19 @@ void SSurfaceState::updateFrom(SSurfaceState& ref) {
}
if (ref.updated.bits.damage) {
damage = ref.damage;
bufferDamage = ref.bufferDamage;
} else {
// damage is always relative to the current commit
if (consumeBufferDamage()) {
// what we still hold is only consumed on rendering.
// dropping it here would leave those pixels stale.
damage.add(ref.damage);
bufferDamage.add(ref.bufferDamage);
} else {
damage = ref.damage;
bufferDamage = ref.bufferDamage;
}
} else if (ref.updated.bits.buffer || !consumeBufferDamage()) {
// damage is relative to the buffer, shm drops the buffer on commit
// dmabuf doesnt drop it until we recieve a new one. and we cant clear
// the dmabuf damage until the renderer has actually consumed it.
damage.clear();
bufferDamage.clear();
}
+1
View File
@@ -119,6 +119,7 @@ struct SSurfaceState {
// helpers
CRegion accumulateBufferDamage(); // transforms state.damage and merges it into state.bufferDamage
bool consumeBufferDamage() const; // whether accumulateBufferDamage() takes the damage rather than leaving it
CRegion effectiveInputRegion() const; // materializes the input region clipped to the current surface size
void updateFrom(SSurfaceState& ref); // updates this state based on a reference state.
void reset(); // resets pending state after commit