Skip to content

Commit 6c347e2

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 daf5688 commit 6c347e2

3 files changed

Lines changed: 47 additions & 11 deletions

File tree

src/blur.cpp

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1021,12 +1021,23 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi
10211021
#else
10221022
m_blurCache->preparePaintData(m_currentView, w, &dirtyRegion, renderInfo.framebuffers[0].get(), &backgroundRect, &scaledBackgroundRect, renderInfo.cache);
10231023

1024+
// BBDX: If the dirtyRegion doesn't intersect backgroundRect nothing behind the
1025+
// window was repainted this frame - the renderTarget only holds stale data
1026+
// there. Without a cache entry to fall back on there is nothing valid to
1027+
// draw or build a cache entry from, so bail. The on-screen draw would be
1028+
// clipped to the deviceRegion anyway.
1029+
if (dirtyRegion.isEmpty() && !renderInfo.cache.get()) {
1030+
return;
1031+
}
1032+
1033+
if (!m_blurCache->useCachedOnly()) {
10241034
// BBDX: Always blit the entire backgroundRect to avoid subtle rounding errors on scaled RenderViews.
10251035
// It took me way too many hours to figure out that this is what's causing sporadic
10261036
// pixel mismatches during textureCompare...
10271037
// Note that this does not give us more usable data (everything outside the dirtyRegion is garbage
10281038
// not part of this paint), it just makes sure that the data we do get is properly aligned.
10291039
renderInfo.framebuffers[0]->blitFromRenderTarget(renderTarget, viewport, backgroundRect, backgroundRect.translated(-backgroundRect.topLeft()));
1040+
} // indent intentional for KWin diff
10301041
#endif
10311042

10321043
// Upload the geometry: the first 6 vertices are used when downsampling and upsampling offscreen,
@@ -1143,6 +1154,16 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi
11431154
return;
11441155
}
11451156

1157+
#if KWIN_VERSION < KWIN_VERSION_CODE(6, 6, 90)
1158+
const QMatrix4x4 &colorMatrix = blurInfo.colorMatrix ? *blurInfo.colorMatrix : m_colorMatrix;
1159+
#else
1160+
const QMatrix4x4 &colorMatrix = m_colorMatrix;
1161+
#endif
1162+
const float modulation = opacity * opacity;
1163+
1164+
// BBDX: without fresh data there is nothing to rebuild the cache from -
1165+
// skip all blur passes and only draw the cached texture below
1166+
if (!m_blurCache->useCachedOnly()) {
11461167
// The downsample pass of the dual Kawase algorithm: the background will be scaled down 50% every iteration.
11471168
{
11481169
ShaderManager::instance()->pushShader(m_downsamplePass.shader.get());
@@ -1204,13 +1225,6 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi
12041225
ShaderManager::instance()->popShader();
12051226
}
12061227

1207-
#if KWIN_VERSION < KWIN_VERSION_CODE(6, 6, 90)
1208-
const QMatrix4x4 &colorMatrix = blurInfo.colorMatrix ? *blurInfo.colorMatrix : m_colorMatrix;
1209-
#else
1210-
const QMatrix4x4 &colorMatrix = m_colorMatrix;
1211-
#endif
1212-
const float modulation = opacity * opacity;
1213-
12141228
#if BETTERBLUR_NOT_NEEDED
12151229
if (const BorderRadius cornerRadius = w->window()->borderRadius(); !cornerRadius.isNull()) {
12161230
ShaderManager::instance()->pushShader(m_roundedOnscreenPass.shader.get());
@@ -1345,6 +1359,7 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi
13451359
if (const BorderRadius cornerRadius = m_windowManager->getEffectiveBorderRadius(w); !cornerRadius.isNull()) {
13461360
m_roundedCornersPass->apply(cornerRadius, viewport, scaledBackgroundRect, renderInfo, w, data, vbo, m_blurCache.get());
13471361
}
1362+
} // indent intentional for KWin diff
13481363

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

src/blur_cache.cpp

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,8 @@ void BBDX::BlurCache::preparePaintData(const KWin::RenderView *view,
192192
}
193193
m_paintData.textureCompareVertexCount = m_paintData.textureCompareRegion.rects().size() * 6;
194194

195+
m_paintData.useCachedOnly = cache.get() && m_paintData.textureCompareRegion.isEmpty();
196+
195197
// the cache entry needs to stay in sync
196198
// so BlurCacheEntry::localDirtyRegion() returns
197199
// correct info
@@ -330,10 +332,11 @@ void BBDX::BlurCache::prepareCache(BBDX::BlurCacheLRU &cache,
330332
return;
331333
}
332334

333-
// Somehow we can end up here with an empty textureCompareRegion
334-
// which would mean there was no dirtyRegion and thus no blitted data.
335-
//
336-
// TODO: currently this just causes re-blur
335+
// We can end up here with an empty textureCompareRegion when KWin
336+
// paints the window with damage that doesn't intersect backgroundRect
337+
// (e.g. shadow-only damage on focus change) - there is no blitted data.
338+
// BlurEffect::blur() skips the blur passes via useCachedOnly() and
339+
// only draws the cached texture.
337340
if (m_paintData.textureCompareRegion.isEmpty()) {
338341
return;
339342
}

src/blur_cache.hpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,11 @@ class BlurCache {
192192
// on first paint before we have a usuable cache entry
193193
// we won't call glBeginConditionalRender
194194
bool glBeginConditionalRenderCalled{false};
195+
196+
// nothing inside backgroundRect is repainted this paint
197+
// but we have a valid cache entry - there is no fresh data
198+
// to compare against or blur so just draw the cached texture
199+
bool useCachedOnly{false};
195200
} m_paintData;
196201

197202
public:
@@ -216,6 +221,19 @@ class BlurCache {
216221
const KWin::Rect *scaledBackgroundRect,
217222
BlurCacheLRU &cache);
218223

224+
/**
225+
* Whether this paint should skip the blit and blur passes
226+
* and only draw the existing cached texture.
227+
*
228+
* True when the dirtyRegion doesn't intersect backgroundRect
229+
* while a valid cache entry exists. Without fresh pixels there
230+
* is nothing to compare against or blur - rebuilding anyway would
231+
* recreate the cache from stale data.
232+
*
233+
* Only valid after preparePaintData() was called.
234+
*/
235+
bool useCachedOnly() const { return m_paintData.useCachedOnly; }
236+
219237
/**
220238
* Injects the geometry used for the cache, in logical pixels
221239
* but scaled to what would be drawn on the device.

0 commit comments

Comments
 (0)