From 10ed34bc9df64bb5b7bd0bfc14e09de5628b9ba4 Mon Sep 17 00:00:00 2001 From: David Edmundson Date: Thu, 22 Jun 2023 14:44:07 +0200 Subject: [PATCH] Avoid painting unchanged scene layers Currently when we move the mouse the one render loop triggers a repaint. When the cursor layer needs a new update we end up in the compositor repainting the main content. Even though painting should mostly no-op it still goes through all existing items and effects to collect damage, still potentially making the GL context current which could stall. A waste when we know we haven't got anything to do. It's enough to cause noticable mouse lag on some hardware. Co-authored-by: Vlad Zahorodnii --- autotests/drm/drmTest.cpp | 3 +- src/composite.cpp | 68 +++++++++++++++++++------------- src/composite.h | 1 + src/core/renderlayer.cpp | 20 ++++++++++ src/core/renderlayer.h | 12 ++++++ src/core/renderlayerdelegate.cpp | 4 ++ src/core/renderlayerdelegate.h | 9 ++++- src/core/renderloop.cpp | 8 +++- src/core/renderloop.h | 8 +++- src/scene/item.cpp | 7 ++-- src/scene/scene.cpp | 9 +++++ src/scene/scene.h | 2 + src/scene/workspacescene.cpp | 46 ++++++++++++--------- src/scene/workspacescene.h | 1 + 14 files changed, 142 insertions(+), 56 deletions(-) diff --git a/autotests/drm/drmTest.cpp b/autotests/drm/drmTest.cpp index 10df17c570..5ec9f7e00d 100644 --- a/autotests/drm/drmTest.cpp +++ b/autotests/drm/drmTest.cpp @@ -404,7 +404,8 @@ void DrmTest::testModeset() const auto output = gpu->drmOutputs().front(); const auto layer = renderBackend->primaryLayer(output); layer->beginFrame(); - output->renderLoop()->beginFrame(); + output->renderLoop()->prepareNewFrame(); + output->renderLoop()->beginPaint(); layer->endFrame(infiniteRegion(), infiniteRegion()); QVERIFY(gpu->drmOutputs().front()->present()); diff --git a/src/composite.cpp b/src/composite.cpp index f69ce58331..10e61d9fe9 100644 --- a/src/composite.cpp +++ b/src/composite.cpp @@ -31,11 +31,9 @@ #include "scene/surfaceitem_x11.h" #include "scene/workspacescene_opengl.h" #include "scene/workspacescene_qpainter.h" -#include "shadow.h" #include "useractions.h" #include "utils/common.h" #include "utils/xcbutils.h" -#include "wayland/surface_interface.h" #include "wayland_server.h" #include "workspace.h" #include "x11syncmanager.h" @@ -685,44 +683,51 @@ void Compositor::composite(RenderLoop *renderLoop) fTraceDuration("Paint (", output->name(), ")"); RenderLayer *superLayer = m_superlayers[renderLoop]; - prePaintPass(superLayer); superLayer->setOutputLayer(primaryLayer); - SurfaceItem *scanoutCandidate = superLayer->delegate()->scanoutCandidate(); - renderLoop->setFullscreenSurface(scanoutCandidate); - output->setContentType(scanoutCandidate ? scanoutCandidate->contentType() : ContentType::None); + renderLoop->prepareNewFrame(); - renderLoop->beginFrame(); - bool directScanout = false; - if (scanoutCandidate) { - const auto sublayers = superLayer->sublayers(); - const bool scanoutPossible = std::none_of(sublayers.begin(), sublayers.end(), [](RenderLayer *sublayer) { - return sublayer->isVisible(); - }); - if (scanoutPossible && !output->directScanoutInhibited()) { - directScanout = primaryLayer->scanout(scanoutCandidate); + if (superLayer->needsRepaint()) { + renderLoop->beginPaint(); + prePaintPass(superLayer); + + SurfaceItem *scanoutCandidate = superLayer->delegate()->scanoutCandidate(); + renderLoop->setFullscreenSurface(scanoutCandidate); + output->setContentType(scanoutCandidate ? scanoutCandidate->contentType() : ContentType::None); + + bool directScanout = false; + if (scanoutCandidate) { + const auto sublayers = superLayer->sublayers(); + const bool scanoutPossible = std::none_of(sublayers.begin(), sublayers.end(), [](RenderLayer *sublayer) { + return sublayer->isVisible(); + }); + if (scanoutPossible && !output->directScanoutInhibited()) { + directScanout = primaryLayer->scanout(scanoutCandidate); + } } - } - if (!directScanout) { - QRegion surfaceDamage = primaryLayer->repaints(); - primaryLayer->resetRepaints(); - preparePaintPass(superLayer, &surfaceDamage); + if (!directScanout) { + QRegion surfaceDamage = primaryLayer->repaints(); + primaryLayer->resetRepaints(); + preparePaintPass(superLayer, &surfaceDamage); - if (auto beginInfo = primaryLayer->beginFrame()) { - auto &[renderTarget, repaint] = beginInfo.value(); + if (auto beginInfo = primaryLayer->beginFrame()) { + auto &[renderTarget, repaint] = beginInfo.value(); - const QRegion bufferDamage = surfaceDamage.united(repaint).intersected(superLayer->rect().toAlignedRect()); + const QRegion bufferDamage = surfaceDamage.united(repaint).intersected(superLayer->rect().toAlignedRect()); - paintPass(superLayer, renderTarget, bufferDamage); - primaryLayer->endFrame(bufferDamage, surfaceDamage); + paintPass(superLayer, renderTarget, bufferDamage); + primaryLayer->endFrame(bufferDamage, surfaceDamage); + } } - } - postPaintPass(superLayer); + postPaintPass(superLayer); + } m_backend->present(output); + framePass(superLayer); + // TODO: Put it inside the cursor layer once the cursor layer can be backed by a real output layer. if (waylandServer()) { const std::chrono::milliseconds frameTime = @@ -737,6 +742,15 @@ void Compositor::composite(RenderLoop *renderLoop) } } +void Compositor::framePass(RenderLayer *layer) +{ + layer->delegate()->frame(); + const auto sublayers = layer->sublayers(); + for (RenderLayer *sublayer : sublayers) { + framePass(sublayer); + } +} + void Compositor::prePaintPass(RenderLayer *layer) { layer->delegate()->prePaint(); diff --git a/src/composite.h b/src/composite.h index 25090feca6..933ff9a375 100644 --- a/src/composite.h +++ b/src/composite.h @@ -203,6 +203,7 @@ private: void postPaintPass(RenderLayer *layer); void preparePaintPass(RenderLayer *layer, QRegion *repaint); void paintPass(RenderLayer *layer, const RenderTarget &renderTarget, const QRegion ®ion); + void framePass(RenderLayer *layer); State m_state = State::Off; std::unique_ptr m_selectionOwner; diff --git a/src/core/renderlayer.cpp b/src/core/renderlayer.cpp index 9e1dd60521..564698af76 100644 --- a/src/core/renderlayer.cpp +++ b/src/core/renderlayer.cpp @@ -134,6 +134,25 @@ void RenderLayer::setGeometry(const QRectF &geometry) } } +void RenderLayer::scheduleRepaint(Item *item) +{ + m_repaintScheduled = true; + m_loop->scheduleRepaint(item); +} + +bool RenderLayer::needsRepaint() const +{ + if (m_repaintScheduled) { + return true; + } + if (!m_repaints.isEmpty()) { + return true; + } + return std::any_of(m_sublayers.constBegin(), m_sublayers.constEnd(), [](RenderLayer *layer) { + return layer->needsRepaint(); + }); +} + void RenderLayer::updateBoundingRect() { QRectF boundingRect = rect(); @@ -183,6 +202,7 @@ QRegion RenderLayer::repaints() const void RenderLayer::resetRepaints() { m_repaints = QRegion(); + m_repaintScheduled = false; } bool RenderLayer::isVisible() const diff --git a/src/core/renderlayer.h b/src/core/renderlayer.h index 639afbc2fc..379f2122fe 100644 --- a/src/core/renderlayer.h +++ b/src/core/renderlayer.h @@ -21,6 +21,7 @@ namespace KWin class OutputLayer; class RenderLayerDelegate; class RenderLoop; +class Item; /** * The RenderLayer class represents a composited layer. @@ -72,6 +73,16 @@ public: QRectF geometry() const; void setGeometry(const QRectF &rect); + /** + * Mark this layer as dirty and trigger a repaint + * on the render loop + */ + void scheduleRepaint(Item *item); + /** + * Returns true if this or a sublayer has requested an update + */ + bool needsRepaint() const; + void addRepaint(const QRegion ®ion); void addRepaint(const QRect &rect); void addRepaint(int x, int y, int width, int height); @@ -87,6 +98,7 @@ private: bool computeEffectiveVisibility() const; RenderLoop *m_loop; + bool m_repaintScheduled = false; std::unique_ptr m_delegate; QRegion m_repaints; QRectF m_boundingRect; diff --git a/src/core/renderlayerdelegate.cpp b/src/core/renderlayerdelegate.cpp index c7d06e9df3..79258154e1 100644 --- a/src/core/renderlayerdelegate.cpp +++ b/src/core/renderlayerdelegate.cpp @@ -24,6 +24,10 @@ QRegion RenderLayerDelegate::repaints() const return QRegion(); } +void RenderLayerDelegate::frame() +{ +} + void RenderLayerDelegate::prePaint() { } diff --git a/src/core/renderlayerdelegate.h b/src/core/renderlayerdelegate.h index dab1b95722..16e552aba1 100644 --- a/src/core/renderlayerdelegate.h +++ b/src/core/renderlayerdelegate.h @@ -35,13 +35,18 @@ public: virtual QRegion repaints() const; /** - * This function is called by the compositor before starting compositing. Reimplement + * This function is called by the compositor after compositing the frame. + */ + virtual void frame(); + + /** + * This function is called by the compositor before starting painting. Reimplement * this function to do frame initialization. */ virtual void prePaint(); /** - * This function is called by the compositor after finishing compositing. Reimplement + * This function is called by the compositor after finishing painting. Reimplement * this function to do post frame cleanup. */ virtual void postPaint(); diff --git a/src/core/renderloop.cpp b/src/core/renderloop.cpp index dd1af6eaef..ac919f8df5 100644 --- a/src/core/renderloop.cpp +++ b/src/core/renderloop.cpp @@ -198,12 +198,16 @@ void RenderLoop::uninhibit() } } -void RenderLoop::beginFrame() +void RenderLoop::prepareNewFrame() { - d->pendingRepaint = false; d->pendingFrameCount++; } +void RenderLoop::beginPaint() +{ + d->pendingRepaint = false; +} + int RenderLoop::refreshRate() const { return d->refreshRate; diff --git a/src/core/renderloop.h b/src/core/renderloop.h index f092fc66b7..19fd02a908 100644 --- a/src/core/renderloop.h +++ b/src/core/renderloop.h @@ -46,11 +46,17 @@ public: */ void uninhibit(); + /** + * This function must be called before the Compositor sumbits the next + * frame. + */ + void prepareNewFrame(); + /** * This function must be called before the Compositor starts rendering the next * frame. */ - void beginFrame(); + void beginPaint(); /** * Returns the refresh rate at which the output is being updated, in millihertz. diff --git a/src/scene/item.cpp b/src/scene/item.cpp index 66622701fb..4c4794a245 100644 --- a/src/scene/item.cpp +++ b/src/scene/item.cpp @@ -6,7 +6,6 @@ #include "scene/item.h" #include "core/renderlayer.h" -#include "core/renderloop.h" #include "scene/scene.h" #include "utils/common.h" @@ -310,7 +309,7 @@ void Item::scheduleRepaintInternal(const QRegion ®ion) const QRegion dirtyRegion = globalRegion & delegate->viewport(); if (!dirtyRegion.isEmpty()) { m_repaints[delegate] += dirtyRegion; - delegate->layer()->loop()->scheduleRepaint(this); + delegate->layer()->scheduleRepaint(this); } } } @@ -321,7 +320,7 @@ void Item::scheduleRepaintInternal(SceneDelegate *delegate, const QRegion ®io const QRegion dirtyRegion = globalRegion & delegate->viewport(); if (!dirtyRegion.isEmpty()) { m_repaints[delegate] += dirtyRegion; - delegate->layer()->loop()->scheduleRepaint(this); + delegate->layer()->scheduleRepaint(this); } } @@ -334,7 +333,7 @@ void Item::scheduleFrame() const QList delegates = m_scene->delegates(); for (SceneDelegate *delegate : delegates) { if (delegate->viewport().intersects(geometry)) { - delegate->layer()->loop()->scheduleRepaint(this); + delegate->layer()->scheduleRepaint(this); } } } diff --git a/src/scene/scene.cpp b/src/scene/scene.cpp index 622d32938e..ea731355cb 100644 --- a/src/scene/scene.cpp +++ b/src/scene/scene.cpp @@ -49,6 +49,11 @@ void SceneDelegate::paint(const RenderTarget &renderTarget, const QRegion ®io m_scene->paint(renderTarget, region == infiniteRegion() ? infiniteRegion() : region.translated(viewport().topLeft())); } +void SceneDelegate::frame() +{ + m_scene->frame(this); +} + Output *SceneDelegate::output() const { return m_output; @@ -139,6 +144,10 @@ SurfaceItem *Scene::scanoutCandidate() const return nullptr; } +void Scene::frame(SceneDelegate *delegate) +{ +} + } // namespace KWin #include "moc_scene.cpp" diff --git a/src/scene/scene.h b/src/scene/scene.h index a9ecf7f8a4..83f356d726 100644 --- a/src/scene/scene.h +++ b/src/scene/scene.h @@ -31,6 +31,7 @@ public: QRegion repaints() const override; SurfaceItem *scanoutCandidate() const override; + void frame() override; void prePaint() override; void postPaint() override; void paint(const RenderTarget &renderTarget, const QRegion ®ion) override; @@ -85,6 +86,7 @@ public: virtual void prePaint(SceneDelegate *delegate) = 0; virtual void postPaint() = 0; virtual void paint(const RenderTarget &renderTarget, const QRegion ®ion) = 0; + virtual void frame(SceneDelegate *delegate); Q_SIGNALS: void delegateRemoved(SceneDelegate *delegate); diff --git a/src/scene/workspacescene.cpp b/src/scene/workspacescene.cpp index 52f081ed0e..1f9fce0830 100644 --- a/src/scene/workspacescene.cpp +++ b/src/scene/workspacescene.cpp @@ -184,6 +184,33 @@ SurfaceItem *WorkspaceScene::scanoutCandidate() const return candidate; } +void WorkspaceScene::frame(SceneDelegate *delegate) +{ + if (waylandServer()) { + Output *output = delegate->output(); + const std::chrono::milliseconds frameTime = + std::chrono::duration_cast(output->renderLoop()->lastPresentationTimestamp()); + + const QList items = m_containerItem->sortedChildItems(); + for (Item *item : items) { + if (!item->isVisible()) { + continue; + } + Window *window = static_cast(item)->window(); + if (!window->isOnOutput(output)) { + continue; + } + if (auto surface = window->surface()) { + surface->frameRendered(frameTime.count()); + } + } + + if (m_dndIcon) { + m_dndIcon->frameRendered(frameTime.count()); + } + } +} + void WorkspaceScene::prePaint(SceneDelegate *delegate) { createStackingOrder(); @@ -320,25 +347,6 @@ void WorkspaceScene::preparePaintSimpleScreen() void WorkspaceScene::postPaint() { - if (waylandServer()) { - const std::chrono::milliseconds frameTime = - std::chrono::duration_cast(painted_screen->renderLoop()->lastPresentationTimestamp()); - - for (WindowItem *windowItem : std::as_const(stacking_order)) { - Window *window = windowItem->window(); - if (!window->isOnOutput(painted_screen)) { - continue; - } - if (auto surface = window->surface()) { - surface->frameRendered(frameTime.count()); - } - } - - if (m_dndIcon) { - m_dndIcon->frameRendered(frameTime.count()); - } - } - for (WindowItem *w : std::as_const(stacking_order)) { effects->postPaintWindow(w->window()->effectWindow()); } diff --git a/src/scene/workspacescene.h b/src/scene/workspacescene.h index 4512c3c84f..d7886e1d31 100644 --- a/src/scene/workspacescene.h +++ b/src/scene/workspacescene.h @@ -60,6 +60,7 @@ public: void prePaint(SceneDelegate *delegate) override; void postPaint() override; void paint(const RenderTarget &renderTarget, const QRegion ®ion) override; + void frame(SceneDelegate *delegate) override; virtual bool makeOpenGLContextCurrent(); virtual void doneOpenGLContextCurrent();