Skip to content

Commit f1551c1

Browse files
Mathiasclaude
andcommitted
BlurCache: don't rebuild the cache when dirtyRegion is empty
When KWin paints the window with damage that doesn't intersect backgroundRect (e.g. shadow-only damage on focus changes) the dirtyRegion is empty: nothing behind the window was repainted, so there are no fresh pixels to compare against and prepareCache() bails before setting up the conditional render. Previously this caused an *ungated* re-blur: all blur passes ran and drawToCache() overwrote the cached texture - the only place the cache was rewritten outside the conditional render. Besides wasting GPU time on every such paint, this rebuilt the cache from the reference blit alone, which can contain never-initialized regions, and re-applied the noise and rounded corner passes to it. Track this case as useCachedOnly in the paint data and skip the scene blit, all blur passes and the cache writes, only drawing the existing cached texture. If no cache entry exists yet there is nothing valid to build one from - bail entirely; the on-screen draw is clipped to the deviceRegion anyway. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 708cb73 commit f1551c1

3 files changed

Lines changed: 54 additions & 7 deletions

File tree

src/blur.cpp

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1029,6 +1029,16 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi
10291029
#else
10301030
m_blurCache->preparePaintData(m_currentView, w, &dirtyRegion, renderInfo.framebuffers[0].get(), &backgroundRect, &scaledBackgroundRect, renderInfo.cache);
10311031

1032+
// BBDX: If the dirtyRegion doesn't intersect backgroundRect nothing behind the
1033+
// window was repainted this frame - the renderTarget only holds stale data
1034+
// there. Without a cache entry to fall back on there is nothing valid to
1035+
// draw or build a cache entry from, so bail. The on-screen draw would be
1036+
// clipped to the deviceRegion anyway.
1037+
if (dirtyRegion.isEmpty() && !renderInfo.cache.get()) {
1038+
return;
1039+
}
1040+
1041+
if (!m_blurCache->useCachedOnly()) {
10321042
// BBDX: Always blit the entire backgroundRect to avoid subtle rounding errors on scaled RenderViews.
10331043
// It took me way too many hours to figure out that this is what's causing sporadic
10341044
// pixel mismatches during textureCompare...
@@ -1037,6 +1047,7 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi
10371047
if (!renderInfo.cache.get() || renderInfo.cache.get()->isFlushing) {
10381048
renderInfo.framebuffers[0]->blitFromRenderTarget(renderTarget, viewport, backgroundRect, backgroundRect.translated(-backgroundRect.topLeft()));
10391049
}
1050+
} // indent intentional for KWin diff
10401051
#endif
10411052

10421053
// Upload the geometry: the first 6 vertices are used when downsampling and upsampling offscreen,
@@ -1160,6 +1171,16 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi
11601171
return;
11611172
}
11621173

1174+
#if KWIN_VERSION < KWIN_VERSION_CODE(6, 6, 90)
1175+
const QMatrix4x4 &colorMatrix = blurInfo.colorMatrix ? *blurInfo.colorMatrix : m_colorMatrix;
1176+
#else
1177+
const QMatrix4x4 &colorMatrix = m_colorMatrix;
1178+
#endif
1179+
const float modulation = opacity * opacity;
1180+
1181+
// BBDX: without fresh data there is nothing to rebuild the cache from -
1182+
// skip all blur passes and only draw the cached texture below
1183+
if (!m_blurCache->useCachedOnly()) {
11631184
// The downsample pass of the dual Kawase algorithm: the background will be scaled down 50% every iteration.
11641185
{
11651186
ShaderManager::instance()->pushShader(m_downsamplePass.shader.get());
@@ -1221,13 +1242,6 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi
12211242
ShaderManager::instance()->popShader();
12221243
}
12231244

1224-
#if KWIN_VERSION < KWIN_VERSION_CODE(6, 6, 90)
1225-
const QMatrix4x4 &colorMatrix = blurInfo.colorMatrix ? *blurInfo.colorMatrix : m_colorMatrix;
1226-
#else
1227-
const QMatrix4x4 &colorMatrix = m_colorMatrix;
1228-
#endif
1229-
const float modulation = opacity * opacity;
1230-
12311245
#if BETTERBLUR_NOT_NEEDED
12321246
if (const BorderRadius cornerRadius = w->window()->borderRadius(); !cornerRadius.isNull()) {
12331247
ShaderManager::instance()->pushShader(m_roundedOnscreenPass.shader.get());
@@ -1362,6 +1376,7 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi
13621376
if (const BorderRadius cornerRadius = m_windowManager->getEffectiveBorderRadius(w); !cornerRadius.isNull()) {
13631377
m_roundedCornersPass->apply(cornerRadius, viewport, scaledBackgroundRect, renderInfo, w, data, vbo, m_blurCache.get());
13641378
}
1379+
} // indent intentional for KWin diff
13651380

13661381
// BBDX:
13671382
m_blurCache->drawCached(viewport, renderInfo, vbo, vertexCount, modulation);

src/blur_cache.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,11 @@ void BBDX::BlurCache::preparePaintData(const KWin::RenderView *view,
220220
m_paintData.scaledBackgroundRect = scaledBackgroundRect;
221221
m_paintData.glBeginConditionalRenderCalled = false;
222222

223+
// BBDX: nothing inside backgroundRect was repainted this frame but we have a
224+
// cache entry - there are no fresh pixels to compare against or blur,
225+
// so BlurEffect::blur() only draws the cached texture (useCachedOnly()).
226+
m_paintData.useCachedOnly = cache.get() && dirtyRegion->isEmpty();
227+
223228
// the cache entry needs to stay in sync
224229
// so BlurCacheEntry::localDirtyRegion() returns
225230
// correct info
@@ -304,6 +309,15 @@ void BBDX::BlurCache::prepareCache(BBDX::BlurCacheLRU &cache) {
304309
return;
305310
}
306311

312+
// BBDX: a flush frame can still have an empty dirtyRegion when KWin paints
313+
// the window with damage that doesn't intersect backgroundRect (e.g.
314+
// shadow-only damage on focus change). There is no blitted data to
315+
// compare against - skip the compare and conditional render setup,
316+
// BlurEffect::blur() only draws the cached texture in this case.
317+
if (useCachedOnly()) {
318+
return;
319+
}
320+
307321
auto textureCompareWindowData = cache.textureCompareWindowData();
308322
if (!textureCompareWindowData) [[unlikely]] {
309323
// GL resource alloc failed

src/blur_cache.hpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,11 @@ class BlurCache {
184184
// on first paint before we have a usuable cache entry
185185
// we won't call glBeginConditionalRender
186186
bool glBeginConditionalRenderCalled{false};
187+
188+
// nothing inside backgroundRect is repainted this paint
189+
// but we have a valid cache entry - there is no fresh data
190+
// to compare against or blur so just draw the cached texture
191+
bool useCachedOnly{false};
187192
} m_paintData;
188193

189194
public:
@@ -208,6 +213,19 @@ class BlurCache {
208213
const KWin::Rect *scaledBackgroundRect,
209214
BlurCacheLRU &cache);
210215

216+
/**
217+
* Whether this paint should skip the blit and blur passes
218+
* and only draw the existing cached texture.
219+
*
220+
* True when the dirtyRegion doesn't intersect backgroundRect
221+
* while a valid cache entry exists. Without fresh pixels there
222+
* is nothing to compare against or blur - rebuilding anyway would
223+
* recreate the cache from stale data.
224+
*
225+
* Only valid after preparePaintData() was called.
226+
*/
227+
bool useCachedOnly() const { return m_paintData.useCachedOnly; }
228+
211229
/**
212230
* Injects the geometry used for the cache, in logical pixels
213231
* but scaled to what would be drawn on the device.

0 commit comments

Comments
 (0)