From aac0609bb99d76d54a4861458b0859356f76c5ca Mon Sep 17 00:00:00 2001 From: Vlad Zahorodnii Date: Wed, 16 Feb 2022 19:13:57 +0200 Subject: [PATCH] scene: Rework surface damage tracking It's not possible to get the surface damage before calling Scene::paint(), which is a big problem because it blocks proper surface damage and buffer damage calculation when walking render layer tree. This change reworks the scene compositing stages to allow getting the next surface damage before calling Scene::paint(). The main challenge is that the effects can expand the surface damage. We have to call prePaintWindow() and prePaintScreen() before actually starting painting. However, prePaintWindow() is called after starting rendering. This change makes Scene call prePaintWindow() and prePaintScreen() so it's possible to know the surface damage beforehand. Unfortunately, it's also a breaking change. Some fullscreen effects will have to adapt to the new Scene paint order. Paint hooks will be invoked in the following order: * prePaintScreen() once per frame * prePaintWindow() once per frame * paintScreen() can be called multiple times * paintWindow() can be called as many times as paintScreen() * postPaintWindow() once per frame * postPaintScreen() once per frame After walking the render layer tree, the Compositor will poke the render backend for the back buffer repair region and combine it with the surface damage to get the buffer damage, which can be passed to the render backend (in order to optimize performance with tiled gpus) and Scene::paint(), which will determine what parts of the scene have to repainted based on the buffer damage. --- src/composite.cpp | 24 +- src/composite.h | 2 +- src/cursorview.cpp | 12 +- src/cursorview.h | 2 +- src/effects.cpp | 3 +- .../scenes/opengl/openglbackend.cpp | 6 - .../scenes/opengl/openglbackend.h | 6 - src/renderbackend.cpp | 6 + src/renderbackend.h | 7 + src/renderlayerdelegate.cpp | 5 + src/renderlayerdelegate.h | 7 +- src/scene.cpp | 445 +++++++----------- src/scene.h | 39 +- src/scenes/opengl/scene_opengl.cpp | 37 +- src/scenes/opengl/scene_opengl.h | 4 +- src/scenes/qpainter/scene_qpainter.cpp | 4 +- src/scenes/qpainter/scene_qpainter.h | 2 +- 17 files changed, 237 insertions(+), 374 deletions(-) diff --git a/src/composite.cpp b/src/composite.cpp index ac0629a3b0..f84095d095 100644 --- a/src/composite.cpp +++ b/src/composite.cpp @@ -666,14 +666,15 @@ void Compositor::composite(RenderLoop *renderLoop) if (directScanout) { renderLoop->endFrame(); } else { - QRegion repaint = outputLayer->repaints(); + QRegion surfaceDamage = outputLayer->repaints(); outputLayer->resetRepaints(); - preparePaintPass(superLayer, &repaint); + preparePaintPass(superLayer, &surfaceDamage); - QRegion surfaceDamage; - QRegion bufferDamage; const QRegion repair = m_backend->beginFrame(output); - paintPass(superLayer, repaint, repair, &surfaceDamage, &bufferDamage); + const QRegion bufferDamage = surfaceDamage.united(repair); + m_backend->aboutToStartPainting(output, bufferDamage); + + paintPass(superLayer, bufferDamage); renderLoop->endFrame(); m_backend->endFrame(output, bufferDamage, surfaceDamage); } @@ -715,7 +716,7 @@ void Compositor::postPaintPass(RenderLayer *layer) void Compositor::preparePaintPass(RenderLayer *layer, QRegion *repaint) { // TODO: Cull opaque region. - *repaint += layer->mapToGlobal(layer->repaints()); + *repaint += layer->mapToGlobal(layer->repaints() + layer->delegate()->repaints()); layer->resetRepaints(); const auto sublayers = layer->sublayers(); for (RenderLayer *sublayer : sublayers) { @@ -725,19 +726,14 @@ void Compositor::preparePaintPass(RenderLayer *layer, QRegion *repaint) } } -void Compositor::paintPass(RenderLayer *layer, const QRegion &repaint, const QRegion &repair, QRegion *surfaceDamage, QRegion *bufferDamage) +void Compositor::paintPass(RenderLayer *layer, const QRegion ®ion) { - QRegion localSurfaceDamage; - QRegion localBufferDamage; - - layer->delegate()->paint(repaint, repair, localSurfaceDamage, localBufferDamage); - *surfaceDamage += localSurfaceDamage; - *bufferDamage += localBufferDamage; + layer->delegate()->paint(region); const auto sublayers = layer->sublayers(); for (RenderLayer *sublayer : sublayers) { if (sublayer->isVisible()) { - paintPass(sublayer, repaint, repair, surfaceDamage, bufferDamage); + paintPass(sublayer, region); } } } diff --git a/src/composite.h b/src/composite.h index 9c23ddd928..80ea6124c8 100644 --- a/src/composite.h +++ b/src/composite.h @@ -141,7 +141,7 @@ private: void prePaintPass(RenderLayer *layer); void postPaintPass(RenderLayer *layer); void preparePaintPass(RenderLayer *layer, QRegion *repaint); - void paintPass(RenderLayer *layer, const QRegion &repaint, const QRegion &repair, QRegion *surfaceDamage, QRegion *bufferDamage); + void paintPass(RenderLayer *layer, const QRegion ®ion); State m_state = State::Off; CompositorSelectionOwner *m_selectionOwner = nullptr; diff --git a/src/cursorview.cpp b/src/cursorview.cpp index f6cddb855a..7dc77cbe07 100644 --- a/src/cursorview.cpp +++ b/src/cursorview.cpp @@ -23,17 +23,11 @@ CursorDelegate::CursorDelegate(AbstractOutput *output, CursorView *view) { } -void CursorDelegate::paint(const QRegion &damage, const QRegion &repaint, QRegion &update, QRegion &valid) +void CursorDelegate::paint(const QRegion ®ion) { const Cursor *cursor = Cursors::self()->currentCursor(); - const QRegion cursorDamage = damage.intersected(cursor->geometry()); - const QRegion cursorRepaint = repaint.intersected(cursor->geometry()); - - update = cursorDamage; - valid = cursorDamage.united(cursorRepaint); - - if (!valid.isEmpty()) { - m_view->paint(m_output, valid); + if (region.intersects(cursor->geometry())) { + m_view->paint(m_output, region); } } diff --git a/src/cursorview.h b/src/cursorview.h index 4d995643db..2d5016ed6c 100644 --- a/src/cursorview.h +++ b/src/cursorview.h @@ -30,7 +30,7 @@ class KWIN_EXPORT CursorDelegate : public RenderLayerDelegate public: CursorDelegate(AbstractOutput *output, CursorView *view); - void paint(const QRegion &damage, const QRegion &repaint, QRegion &update, QRegion &valid) override; + void paint(const QRegion ®ion) override; private: CursorView *m_view; diff --git a/src/effects.cpp b/src/effects.cpp index 75255bca24..de979bd64f 100644 --- a/src/effects.cpp +++ b/src/effects.cpp @@ -1797,8 +1797,7 @@ void EffectsHandlerImpl::renderScreen(EffectScreen *screen) { auto output = static_cast(screen)->platformOutput(); m_scene->prePaint(output); - QRegion update, valid; - m_scene->paint(output->geometry(), QRect(), update, valid); + m_scene->paint(output->geometry()); m_scene->postPaint(); } diff --git a/src/platformsupport/scenes/opengl/openglbackend.cpp b/src/platformsupport/scenes/opengl/openglbackend.cpp index 9c4fe5ea17..0f064fd1b8 100644 --- a/src/platformsupport/scenes/opengl/openglbackend.cpp +++ b/src/platformsupport/scenes/opengl/openglbackend.cpp @@ -62,12 +62,6 @@ QSharedPointer OpenGLBackend::textureForOutput(AbstractOutput* return {}; } -void OpenGLBackend::aboutToStartPainting(AbstractOutput *output, const QRegion &damage) -{ - Q_UNUSED(output) - Q_UNUSED(damage) -} - SurfaceTexture *OpenGLBackend::createSurfaceTextureInternal(SurfacePixmapInternal *pixmap) { Q_UNUSED(pixmap) diff --git a/src/platformsupport/scenes/opengl/openglbackend.h b/src/platformsupport/scenes/opengl/openglbackend.h index def1d9fddf..bbccee9ffc 100644 --- a/src/platformsupport/scenes/opengl/openglbackend.h +++ b/src/platformsupport/scenes/opengl/openglbackend.h @@ -55,12 +55,6 @@ public: virtual SurfaceTexture *createSurfaceTextureX11(SurfacePixmapX11 *pixmap); virtual SurfaceTexture *createSurfaceTextureWayland(SurfacePixmapWayland *pixmap); - /** - * Notifies about starting to paint. - * - * @p damage contains the reported damage as suggested by windows and effects on prepaint calls. - */ - virtual void aboutToStartPainting(AbstractOutput *output, const QRegion &damage); virtual bool makeCurrent() = 0; virtual void doneCurrent() = 0; diff --git a/src/renderbackend.cpp b/src/renderbackend.cpp index 318c63ad3d..6067616942 100644 --- a/src/renderbackend.cpp +++ b/src/renderbackend.cpp @@ -24,6 +24,12 @@ bool RenderBackend::checkGraphicsReset() return false; } +void RenderBackend::aboutToStartPainting(AbstractOutput *output, const QRegion &damage) +{ + Q_UNUSED(output) + Q_UNUSED(damage) +} + bool RenderBackend::scanout(AbstractOutput *output, SurfaceItem *surfaceItem) { Q_UNUSED(output) diff --git a/src/renderbackend.h b/src/renderbackend.h index 111ea7c6d5..15030c487d 100644 --- a/src/renderbackend.h +++ b/src/renderbackend.h @@ -32,6 +32,13 @@ public: virtual bool checkGraphicsReset(); + /** + * Notifies about starting to paint. + * + * @p damage contains the reported damage as suggested by windows and effects on prepaint calls. + */ + virtual void aboutToStartPainting(AbstractOutput *output, const QRegion &damage); + virtual QRegion beginFrame(AbstractOutput *output) = 0; virtual void endFrame(AbstractOutput *output, const QRegion &renderedRegion, const QRegion &damagedRegion) = 0; diff --git a/src/renderlayerdelegate.cpp b/src/renderlayerdelegate.cpp index afbb1b3d0b..c3a1d2d0a8 100644 --- a/src/renderlayerdelegate.cpp +++ b/src/renderlayerdelegate.cpp @@ -24,6 +24,11 @@ void RenderLayerDelegate::setLayer(RenderLayer *layer) m_layer = layer; } +QRegion RenderLayerDelegate::repaints() const +{ + return QRegion(); +} + void RenderLayerDelegate::prePaint() { } diff --git a/src/renderlayerdelegate.h b/src/renderlayerdelegate.h index bd8812324f..25c39d758b 100644 --- a/src/renderlayerdelegate.h +++ b/src/renderlayerdelegate.h @@ -30,6 +30,11 @@ public: RenderLayer *layer() const; void setLayer(RenderLayer *layer); + /** + * Returns the repaints schduled for the next frame. + */ + virtual QRegion repaints() const; + /** * This function is called by the compositor before starting compositing. Reimplement * this function to do frame initialization. @@ -52,7 +57,7 @@ public: * This function is called when the compositor wants the render layer delegate * to repaint its contents. */ - virtual void paint(const QRegion &damage, const QRegion &repaint, QRegion &update, QRegion &valid) = 0; + virtual void paint(const QRegion ®ion) = 0; private: RenderLayer *m_layer = nullptr; diff --git a/src/scene.cpp b/src/scene.cpp index 108af23d9f..6e5cd3c70e 100644 --- a/src/scene.cpp +++ b/src/scene.cpp @@ -102,6 +102,11 @@ SceneDelegate::~SceneDelegate() m_scene->removeDelegate(this); } +QRegion SceneDelegate::repaints() const +{ + return m_scene->damage(); +} + SurfaceItem *SceneDelegate::scanoutCandidate() const { return m_scene->scanoutCandidate(); @@ -117,9 +122,9 @@ void SceneDelegate::postPaint() m_scene->postPaint(); } -void SceneDelegate::paint(const QRegion &damage, const QRegion &repaint, QRegion &update, QRegion &valid) +void SceneDelegate::paint(const QRegion ®ion) { - m_scene->paint(damage, repaint, update, valid); + m_scene->paint(region); } //**************************************** @@ -175,6 +180,11 @@ void Scene::addRepaint(const QRegion ®ion) } } +QRegion Scene::damage() const +{ + return m_paintContext.damage; +} + QRect Scene::geometry() const { return m_geometry; @@ -266,10 +276,151 @@ void Scene::prePaint(AbstractOutput *output) setRenderTargetRect(painted_screen->geometry()); setRenderTargetScale(painted_screen->scale()); } + + const RenderLoop *renderLoop = painted_screen->renderLoop(); + const std::chrono::milliseconds presentTime = + std::chrono::duration_cast(renderLoop->nextPresentationTimestamp()); + + if (Q_UNLIKELY(presentTime < m_expectedPresentTimestamp)) { + qCDebug(KWIN_CORE, + "Provided presentation timestamp is invalid: %lld (current: %lld)", + static_cast(presentTime.count()), + static_cast(m_expectedPresentTimestamp.count())); + } else { + m_expectedPresentTimestamp = presentTime; + } + + // preparation step + auto effectsImpl = static_cast(effects); + effectsImpl->startPaint(); + + ScreenPrePaintData prePaintData; + prePaintData.mask = 0; + prePaintData.screen = EffectScreenImpl::get(painted_screen); + + effects->prePaintScreen(prePaintData, m_expectedPresentTimestamp); + m_paintContext.damage = prePaintData.paint; + m_paintContext.mask = prePaintData.mask; + m_paintContext.phase2Data.clear(); + + if (m_paintContext.mask & (PAINT_SCREEN_TRANSFORMED | PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS)) { + preparePaintGenericScreen(); + } else { + preparePaintSimpleScreen(); + } +} + +static void resetRepaintsHelper(Item *item, AbstractOutput *output) +{ + item->resetRepaints(output); + + const auto childItems = item->childItems(); + for (Item *childItem : childItems) { + resetRepaintsHelper(childItem, output); + } +} + +static void accumulateRepaints(Item *item, AbstractOutput *output, QRegion *repaints) +{ + *repaints += item->repaints(output); + item->resetRepaints(output); + + const auto childItems = item->childItems(); + for (Item *childItem : childItems) { + accumulateRepaints(childItem, output, repaints); + } +} + +void Scene::preparePaintGenericScreen() +{ + for (Window *sceneWindow : std::as_const(stacking_order)) { + resetRepaintsHelper(sceneWindow->windowItem(), painted_screen); + + WindowPrePaintData data; + data.mask = m_paintContext.mask | (sceneWindow->isOpaque() ? PAINT_WINDOW_OPAQUE : PAINT_WINDOW_TRANSLUCENT); + data.paint = infiniteRegion(); // no clipping, so doesn't really matter + + sceneWindow->resetPaintingEnabled(); + effects->prePaintWindow(effectWindow(sceneWindow), data, m_expectedPresentTimestamp); + if (sceneWindow->isPaintingEnabled()) { + m_paintContext.phase2Data.append(Phase2Data{ + .window = sceneWindow, + .region = infiniteRegion(), + .clip = data.clip, + .mask = data.mask, + }); + } + } + + m_paintContext.damage = QRect(QPoint(0, 0), renderTargetRect().size()); +} + +void Scene::preparePaintSimpleScreen() +{ + for (Window *sceneWindow : std::as_const(stacking_order)) { + const Toplevel *toplevel = sceneWindow->window(); + WindowPrePaintData data; + data.mask = m_paintContext.mask | (sceneWindow->isOpaque() ? PAINT_WINDOW_OPAQUE : PAINT_WINDOW_TRANSLUCENT); + accumulateRepaints(sceneWindow->windowItem(), painted_screen, &data.paint); + + // Clip out the decoration for opaque windows; the decoration is drawn in the second pass. + if (sceneWindow->isOpaque()) { + const SurfaceItem *surfaceItem = sceneWindow->surfaceItem(); + if (surfaceItem) { + data.clip |= surfaceItem->mapToGlobal(surfaceItem->shape()); + } + } else if (toplevel->hasAlpha() && toplevel->opacity() == 1.0) { + const SurfaceItem *surfaceItem = sceneWindow->surfaceItem(); + if (surfaceItem) { + const QRegion shape = surfaceItem->shape(); + const QRegion opaque = surfaceItem->opaque(); + data.clip = surfaceItem->mapToGlobal(shape & opaque); + if (opaque == shape) { + data.mask = m_paintContext.mask | PAINT_WINDOW_OPAQUE; + } + } + } + + const AbstractClient *client = dynamic_cast(toplevel); + if (client && !client->decorationHasAlpha() && toplevel->opacity() == 1.0) { + data.clip |= sceneWindow->decorationShape().translated(sceneWindow->pos()); + } + + sceneWindow->resetPaintingEnabled(); + effects->prePaintWindow(effectWindow(sceneWindow), data, m_expectedPresentTimestamp); + if (sceneWindow->isPaintingEnabled()) { + m_paintContext.phase2Data.append(Phase2Data{ + .window = sceneWindow, + .region = data.paint, + .clip = data.clip, + .mask = data.mask, + }); + } + } + + // Perform an occlusion cull pass, remove surface damage occluded by opaque windows. + QRegion surfaceDamage; + QRegion opaque; + for (int i = m_paintContext.phase2Data.size() - 1; i >= 0; --i) { + const auto &paintData = m_paintContext.phase2Data.at(i); + surfaceDamage += paintData.region - opaque; + if (!(paintData.mask & PAINT_WINDOW_TRANSLUCENT)) { + opaque += paintData.clip; + } + } + + m_paintContext.damage += surfaceDamage & renderTargetRect(); + m_paintContext.damage.translate(-renderTargetRect().topLeft()); } void Scene::postPaint() { + for (Window *w : std::as_const(stacking_order)) { + effects->postPaintWindow(effectWindow(w)); + } + + effects->postPaintScreen(); + if (waylandServer()) { const std::chrono::milliseconds frameTime = std::chrono::duration_cast(painted_screen->renderLoop()->lastPresentationTimestamp()); @@ -353,296 +504,64 @@ QRegion Scene::mapToRenderTarget(const QRegion ®ion) const return result; } -// returns mask and possibly modified region -void Scene::paintScreen(const QRegion &damage, const QRegion &repaint, - QRegion *updateRegion, QRegion *validRegion) +void Scene::paintScreen(const QRegion ®ion) { - const RenderLoop *renderLoop = painted_screen->renderLoop(); - const std::chrono::milliseconds presentTime = - std::chrono::duration_cast(renderLoop->nextPresentationTimestamp()); - - if (Q_UNLIKELY(presentTime < m_expectedPresentTimestamp)) { - qCDebug(KWIN_CORE, - "Provided presentation timestamp is invalid: %lld (current: %lld)", - static_cast(presentTime.count()), - static_cast(m_expectedPresentTimestamp.count())); - } else { - m_expectedPresentTimestamp = presentTime; - } - - // preparation step - auto effectsImpl = static_cast(effects); - effectsImpl->startPaint(); - - const QRegion displayRegion(renderTargetRect()); - QRegion region = damage; - - auto screen = EffectScreenImpl::get(painted_screen); - ScreenPrePaintData pdata; - pdata.mask = (damage == displayRegion) ? 0 : PAINT_SCREEN_REGION; - pdata.paint = region; - pdata.screen = screen; - - effects->prePaintScreen(pdata, m_expectedPresentTimestamp); - region = pdata.paint; - - int mask = pdata.mask; - if (mask & (PAINT_SCREEN_TRANSFORMED | PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS)) { - // Region painting is not possible with transformations, - // because screen damage doesn't match transformed positions. - mask &= ~PAINT_SCREEN_REGION; - region = infiniteRegion(); - } else if (mask & PAINT_SCREEN_REGION) { - // make sure not to go outside visible screen - region &= displayRegion; - } else { - // whole screen, not transformed, force region to be full - region = displayRegion; - } - - painted_region = region; - repaint_region = repaint; - - ScreenPaintData data(m_renderTargetProjectionMatrix, screen); - effects->paintScreen(mask, region, data); - - Q_EMIT frameRendered(); - - for (Window *w : qAsConst(stacking_order)) { - effects->postPaintWindow(effectWindow(w)); - } - - effects->postPaintScreen(); - - // make sure not to go outside of the screen area - *updateRegion = damaged_region; - *validRegion = (region | painted_region) & displayRegion; - - repaint_region = QRegion(); - damaged_region = QRegion(); + ScreenPaintData data(m_renderTargetProjectionMatrix, EffectScreenImpl::get(painted_screen)); + effects->paintScreen(m_paintContext.mask, region, data); m_paintScreenCount = 0; + Q_EMIT frameRendered(); } // the function that'll be eventually called by paintScreen() above void Scene::finalPaintScreen(int mask, const QRegion ®ion, ScreenPaintData& data) { m_paintScreenCount++; - if (mask & (PAINT_SCREEN_TRANSFORMED | PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS)) + if (mask & (PAINT_SCREEN_TRANSFORMED | PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS)) { paintGenericScreen(mask, data); - else + } else { paintSimpleScreen(mask, region); -} - -static void resetRepaintsHelper(Item *item, AbstractOutput *output) -{ - item->resetRepaints(output); - - const auto childItems = item->childItems(); - for (Item *childItem : childItems) { - resetRepaintsHelper(childItem, output); } } // The generic painting code that can handle even transformations. // It simply paints bottom-to-top. -void Scene::paintGenericScreen(int orig_mask, const ScreenPaintData &) +void Scene::paintGenericScreen(int, const ScreenPaintData &) { - QVector phase2; - phase2.reserve(stacking_order.size()); - for (Window * w : qAsConst(stacking_order)) { // bottom to top - // Reset the repaint_region. - // This has to be done here because many effects schedule a repaint for - // the next frame within Effects::prePaintWindow. - resetRepaintsHelper(w->windowItem(), painted_screen); - - WindowPrePaintData data; - data.mask = orig_mask | (w->isOpaque() ? PAINT_WINDOW_OPAQUE : PAINT_WINDOW_TRANSLUCENT); - w->resetPaintingEnabled(); - data.paint = infiniteRegion(); // no clipping, so doesn't really matter - data.clip = QRegion(); - // preparation step - effects->prePaintWindow(effectWindow(w), data, m_expectedPresentTimestamp); - if (!w->isPaintingEnabled()) { - continue; - } - phase2.append({w, infiniteRegion(), data.clip, data.mask,}); - } - - damaged_region = renderTargetRect(); - if (m_paintScreenCount == 1) { - aboutToStartPainting(painted_screen, damaged_region); - - if (orig_mask & PAINT_SCREEN_BACKGROUND_FIRST) { + if (m_paintContext.mask & PAINT_SCREEN_BACKGROUND_FIRST) { + if (m_paintScreenCount == 1) { paintBackground(infiniteRegion()); } - } - - if (!(orig_mask & PAINT_SCREEN_BACKGROUND_FIRST)) { + } else { paintBackground(infiniteRegion()); } - for (const Phase2Data &d : qAsConst(phase2)) { - paintWindow(d.window, d.mask, d.region); - } -} -static void accumulateRepaints(Item *item, AbstractOutput *output, QRegion *repaints) -{ - *repaints += item->repaints(output); - item->resetRepaints(output); - - const auto childItems = item->childItems(); - for (Item *childItem : childItems) { - accumulateRepaints(childItem, output, repaints); + for (const Phase2Data &paintData : std::as_const(m_paintContext.phase2Data)) { + paintWindow(paintData.window, paintData.mask, paintData.region); } } // The optimized case without any transformations at all. // It can paint only the requested region and can use clipping // to reduce painting and improve performance. -void Scene::paintSimpleScreen(int orig_mask, const QRegion ®ion) +void Scene::paintSimpleScreen(int, const QRegion ®ion) { - Q_ASSERT((orig_mask & (PAINT_SCREEN_TRANSFORMED - | PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS)) == 0); - QVector phase2data; - phase2data.reserve(stacking_order.size()); - - QRegion dirtyArea = region; - bool opaqueFullscreen = false; - - // Traverse the scene windows from bottom to top. - for (int i = 0; i < stacking_order.count(); ++i) { - Window *window = stacking_order[i]; - Toplevel *toplevel = window->window(); - WindowPrePaintData data; - data.mask = orig_mask | (window->isOpaque() ? PAINT_WINDOW_OPAQUE : PAINT_WINDOW_TRANSLUCENT); - window->resetPaintingEnabled(); - data.paint = region; - accumulateRepaints(window->windowItem(), painted_screen, &data.paint); - - // Clip out the decoration for opaque windows; the decoration is drawn in the second pass - opaqueFullscreen = false; // TODO: do we care about unmanged windows here (maybe input windows?) - AbstractClient *client = dynamic_cast(toplevel); - if (window->isOpaque()) { - if (client) { - opaqueFullscreen = client->isFullScreen(); - } - - const SurfaceItem *surfaceItem = window->surfaceItem(); - if (surfaceItem) { - data.clip |= surfaceItem->mapToGlobal(surfaceItem->shape()); - } - } else if (toplevel->hasAlpha() && toplevel->opacity() == 1.0) { - const SurfaceItem *surfaceItem = window->surfaceItem(); - if (surfaceItem) { - const QRegion shape = surfaceItem->shape(); - const QRegion opaque = surfaceItem->opaque(); - data.clip = surfaceItem->mapToGlobal(shape & opaque); - - if (opaque == shape) { - data.mask = orig_mask | PAINT_WINDOW_OPAQUE; - } - } - } else { - data.clip = QRegion(); - } - - if (client && !client->decorationHasAlpha() && toplevel->opacity() == 1.0) { - data.clip |= window->decorationShape().translated(window->pos()); - } - - // preparation step - effects->prePaintWindow(effectWindow(window), data, m_expectedPresentTimestamp); - if (!window->isPaintingEnabled()) { - continue; - } - dirtyArea |= data.paint; - // Schedule the window for painting - phase2data.append({ window, data.paint, data.clip, data.mask, }); - } - - // Save the part of the repaint region that's exclusively rendered to - // bring a reused back buffer up to date. Then union the dirty region - // with the repaint region. - const QRegion repaintClip = repaint_region - dirtyArea; - dirtyArea |= repaint_region; - - const QRegion displayRegion(renderTargetRect()); - bool fullRepaint(dirtyArea == displayRegion); // spare some expensive region operations - if (!fullRepaint) { - extendPaintRegion(dirtyArea, opaqueFullscreen); - fullRepaint = (dirtyArea == displayRegion); - } - - QRegion allclips, upperTranslucentDamage; - upperTranslucentDamage = repaint_region; - // This is the occlusion culling pass - for (int i = phase2data.count() - 1; i >= 0; --i) { - Phase2Data *data = &phase2data[i]; + QRegion visible = region; + for (int i = m_paintContext.phase2Data.size() - 1; i >= 0; --i) { + Phase2Data *data = &m_paintContext.phase2Data[i]; + const Item *item = data->window->windowItem(); - if (fullRepaint) { - data->region = displayRegion; - } else { - data->region |= upperTranslucentDamage; - } - - // subtract the parts which will possibly been drawn as part of - // a higher opaque window - data->region -= allclips; - - // Here we rely on WindowPrePaintData::setTranslucent() to remove - // the clip if needed. - if (!data->clip.isEmpty() && !(data->mask & PAINT_WINDOW_TRANSLUCENT)) { - // clip away the opaque regions for all windows below this one - allclips |= data->clip; - // extend the translucent damage for windows below this by remaining (translucent) regions - if (!fullRepaint) { - upperTranslucentDamage |= data->region - data->clip; - } - } else if (!fullRepaint) { - upperTranslucentDamage |= data->region; + data->region = visible & item->mapToGlobal(item->boundingRect()); + if (!(data->mask & PAINT_WINDOW_TRANSLUCENT)) { + visible -= data->clip; } } - QRegion paintedArea; - // Fill any areas of the root window not covered by opaque windows - if (m_paintScreenCount == 1) { - aboutToStartPainting(painted_screen, dirtyArea); + paintBackground(visible); - if (orig_mask & PAINT_SCREEN_BACKGROUND_FIRST) { - paintBackground(infiniteRegion()); - } - } - if (!(orig_mask & PAINT_SCREEN_BACKGROUND_FIRST)) { - paintedArea = dirtyArea - allclips; - paintBackground(paintedArea); - } - - // Now walk the list bottom to top and draw the windows. - for (int i = 0; i < phase2data.count(); ++i) { - Phase2Data *data = &phase2data[i]; - - // add all regions which have been drawn so far - paintedArea |= data->region; - data->region = paintedArea; - - paintWindow(data->window, data->mask, data->region); - } - - if (fullRepaint) { - painted_region = displayRegion; - damaged_region = displayRegion - repaintClip; - } else { - painted_region |= paintedArea; - - // Clip the repainted region from the damaged region. - // It's important that we don't add the union of the damaged region - // and the repainted region to the damage history. Otherwise the - // repaint region will grow with every frame until it eventually - // covers the whole back buffer, at which point we're always doing - // full repaints. - damaged_region = paintedArea - repaintClip; + for (const Phase2Data &paintData : std::as_const(m_paintContext.phase2Data)) { + paintWindow(paintData.window, paintData.mask, paintData.region); } } @@ -733,12 +652,6 @@ void Scene::paintDesktop(int desktop, int mask, const QRegion ®ion, ScreenPai static_cast(effects)->paintDesktop(desktop, mask, region, data); } -void Scene::aboutToStartPainting(AbstractOutput *output, const QRegion &damage) -{ - Q_UNUSED(output) - Q_UNUSED(damage) -} - // the function that'll be eventually called by paintWindow() above void Scene::finalPaintWindow(EffectWindowImpl* w, int mask, const QRegion ®ion, WindowPaintData& data) { @@ -754,12 +667,6 @@ void Scene::finalDrawWindow(EffectWindowImpl* w, int mask, const QRegion ®ion w->sceneWindow()->performPaint(mask, region, data); } -void Scene::extendPaintRegion(QRegion ®ion, bool opaqueFullscreen) -{ - Q_UNUSED(region); - Q_UNUSED(opaqueFullscreen); -} - bool Scene::makeOpenGLContextCurrent() { return false; diff --git a/src/scene.h b/src/scene.h index 8d4506d082..17e0c018e7 100644 --- a/src/scene.h +++ b/src/scene.h @@ -53,10 +53,11 @@ public: explicit SceneDelegate(Scene *scene, AbstractOutput *output, QObject *parent = nullptr); ~SceneDelegate() override; + QRegion repaints() const override; SurfaceItem *scanoutCandidate() const override; void prePaint() override; void postPaint() override; - void paint(const QRegion &damage, const QRegion &repaint, QRegion &update, QRegion &valid) override; + void paint(const QRegion ®ion) override; private: Scene *m_scene; @@ -82,6 +83,7 @@ public: void addRepaint(const QRect &rect); void addRepaint(int x, int y, int width, int height); void addRepaintFull(); + QRegion damage() const; QRect geometry() const; void setGeometry(const QRect &rect); @@ -96,7 +98,7 @@ public: SurfaceItem *scanoutCandidate() const; void prePaint(AbstractOutput *output); void postPaint(); - virtual void paint(const QRegion &damage, const QRegion &repaint, QRegion &update, QRegion &valid) = 0; + virtual void paint(const QRegion ®ion) = 0; /** * Adds the Toplevel to the Scene. @@ -222,34 +224,25 @@ protected: void createStackingOrder(); void clearStackingOrder(); // shared implementation, starts painting the screen - void paintScreen(const QRegion &damage, const QRegion &repaint, - QRegion *updateRegion, QRegion *validRegion); + void paintScreen(const QRegion ®ion); friend class EffectsHandlerImpl; // called after all effects had their paintScreen() called void finalPaintScreen(int mask, const QRegion ®ion, ScreenPaintData& data); // shared implementation of painting the screen in the generic // (unoptimized) way + void preparePaintGenericScreen(); virtual void paintGenericScreen(int mask, const ScreenPaintData &data); // shared implementation of painting the screen in an optimized way + void preparePaintSimpleScreen(); virtual void paintSimpleScreen(int mask, const QRegion ®ion); // paint the background (not the desktop background - the whole background) virtual void paintBackground(const QRegion ®ion) = 0; - - /** - * Notifies about starting to paint. - * - * @p damage contains the reported damage as suggested by windows and effects on prepaint calls. - */ - virtual void aboutToStartPainting(AbstractOutput *output, const QRegion &damage); // called after all effects had their paintWindow() called void finalPaintWindow(EffectWindowImpl* w, int mask, const QRegion ®ion, WindowPaintData& data); // shared implementation, starts painting the window virtual void paintWindow(Window* w, int mask, const QRegion ®ion); // called after all effects had their drawWindow() called virtual void finalDrawWindow(EffectWindowImpl* w, int mask, const QRegion ®ion, WindowPaintData& data); - // let the scene decide whether it's better to paint more of the screen, eg. in order to allow a buffer swap - // the default is NOOP - virtual void extendPaintRegion(QRegion ®ion, bool opaqueFullscreen); virtual void paintOffscreenQuickView(OffscreenQuickView *w) = 0; @@ -260,16 +253,13 @@ protected: QRegion clip; int mask = 0; }; - // The region which actually has been painted by paintScreen() and should be - // copied from the buffer to the screen. I.e. the region returned from Scene::paintScreen(). - // Since prePaintWindow() can extend areas to paint, these changes would have to propagate - // up all the way from paintSimpleScreen() up to paintScreen(), so save them here rather - // than propagate them up in arguments. - QRegion painted_region; - // Additional damage that needs to be repaired to bring a reused back buffer up to date - QRegion repaint_region; - // The dirty region before it was unioned with repaint_region - QRegion damaged_region; + + struct PaintContext { + QRegion damage; + int mask = 0; + QVector phase2Data; + }; + // The screen that is being currently painted AbstractOutput *painted_screen = nullptr; @@ -285,6 +275,7 @@ private: qreal m_renderTargetScale = 1; // how many times finalPaintScreen() has been called int m_paintScreenCount = 0; + PaintContext m_paintContext; }; // The base class for windows representations in composite backends diff --git a/src/scenes/opengl/scene_opengl.cpp b/src/scenes/opengl/scene_opengl.cpp index 716066e00a..5ef7623b03 100644 --- a/src/scenes/opengl/scene_opengl.cpp +++ b/src/scenes/opengl/scene_opengl.cpp @@ -97,15 +97,10 @@ bool SceneOpenGL::initFailed() const return !init_ok; } -void SceneOpenGL::aboutToStartPainting(AbstractOutput *output, const QRegion &damage) -{ - m_backend->aboutToStartPainting(output, damage); -} - -void SceneOpenGL::paint(const QRegion &damage, const QRegion &repaint, QRegion &update, QRegion &valid) +void SceneOpenGL::paint(const QRegion ®ion) { GLVertexBuffer::streamingBuffer()->beginFrame(); - paintScreen(damage, repaint, &update, &valid); + paintScreen(region); GLVertexBuffer::streamingBuffer()->endOfFrame(); } @@ -154,34 +149,6 @@ void SceneOpenGL::paintBackground(const QRegion ®ion) } } -void SceneOpenGL::extendPaintRegion(QRegion ®ion, bool opaqueFullscreen) -{ - if (m_backend->supportsBufferAge()) - return; - - if (options->glPreferBufferSwap() == Options::ExtendDamage) { // only Extend "large" repaints - const QRegion displayRegion(geometry()); - uint damagedPixels = 0; - const QSize &sceneSize = geometry().size(); - const uint fullRepaintLimit = (opaqueFullscreen?0.49f:0.748f)*sceneSize.width()*sceneSize.height(); - // 16:9 is 75% of 4:3 and 2.55:1 is 49.01% of 5:4 - // (5:4 is the most square format and 2.55:1 is Cinemascope55 - the widest ever shot - // movie aspect - two times ;-) It's a Fox format, though, so maybe we want to restrict - // to 2.20:1 - Panavision - which has actually been used for interesting movies ...) - // would be 57% of 5/4 - for (const QRect &r : region) { -// damagedPixels += r.width() * r.height(); // combined window damage test - damagedPixels = r.width() * r.height(); // experimental single window damage testing - if (damagedPixels > fullRepaintLimit) { - region = displayRegion; - return; - } - } - } else if (options->glPreferBufferSwap() == Options::PaintFullScreen) { // forced full rePaint - region = QRegion(geometry()); - } -} - void SceneOpenGL::paintDesktop(int desktop, int mask, const QRegion ®ion, ScreenPaintData &data) { const QRect r = region.boundingRect(); diff --git a/src/scenes/opengl/scene_opengl.h b/src/scenes/opengl/scene_opengl.h index cca3af1e76..736d699a78 100644 --- a/src/scenes/opengl/scene_opengl.h +++ b/src/scenes/opengl/scene_opengl.h @@ -33,7 +33,7 @@ public: explicit SceneOpenGL(OpenGLBackend *backend, QObject *parent = nullptr); ~SceneOpenGL() override; bool initFailed() const override; - void paint(const QRegion &damage, const QRegion &repaint, QRegion &update, QRegion &valid) override; + void paint(const QRegion ®ion) override; Scene::EffectFrame *createEffectFrame(EffectFrameImpl *frame) override; Shadow *createShadow(Toplevel *toplevel) override; bool makeOpenGLContextCurrent() override; @@ -59,8 +59,6 @@ public: protected: void paintBackground(const QRegion ®ion) override; - void aboutToStartPainting(AbstractOutput *output, const QRegion &damage) override; - void extendPaintRegion(QRegion ®ion, bool opaqueFullscreen) override; QMatrix4x4 transformation(int mask, const ScreenPaintData &data) const; void paintDesktop(int desktop, int mask, const QRegion ®ion, ScreenPaintData &data) override; void paintOffscreenQuickView(OffscreenQuickView *w) override; diff --git a/src/scenes/qpainter/scene_qpainter.cpp b/src/scenes/qpainter/scene_qpainter.cpp index 74dd361191..cc27081514 100644 --- a/src/scenes/qpainter/scene_qpainter.cpp +++ b/src/scenes/qpainter/scene_qpainter.cpp @@ -68,13 +68,13 @@ void SceneQPainter::paintGenericScreen(int mask, const ScreenPaintData &data) m_painter->restore(); } -void SceneQPainter::paint(const QRegion &damage, const QRegion &repaint, QRegion &update, QRegion &valid) +void SceneQPainter::paint(const QRegion ®ion) { QImage *buffer = m_backend->bufferForScreen(painted_screen); if (buffer && !buffer->isNull()) { m_painter->begin(buffer); m_painter->setWindow(painted_screen->geometry()); - paintScreen(damage, repaint, &update, &valid); + paintScreen(region); m_painter->end(); } } diff --git a/src/scenes/qpainter/scene_qpainter.h b/src/scenes/qpainter/scene_qpainter.h index 2ee10e1d44..997c79201d 100644 --- a/src/scenes/qpainter/scene_qpainter.h +++ b/src/scenes/qpainter/scene_qpainter.h @@ -23,7 +23,7 @@ class KWIN_EXPORT SceneQPainter : public Scene public: ~SceneQPainter() override; - void paint(const QRegion &damage, const QRegion &repaint, QRegion &update, QRegion &valid) override; + void paint(const QRegion ®ion) override; void paintGenericScreen(int mask, const ScreenPaintData &data) override; bool initFailed() const override; EffectFrame *createEffectFrame(EffectFrameImpl *frame) override;