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 <vlad.zahorodnii@kde.org>
master
David Edmundson 1 year ago committed by Vlad Zahorodnii
parent fe1d4ffbc5
commit 10ed34bc9d

@ -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());

@ -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();

@ -203,6 +203,7 @@ private:
void postPaintPass(RenderLayer *layer);
void preparePaintPass(RenderLayer *layer, QRegion *repaint);
void paintPass(RenderLayer *layer, const RenderTarget &renderTarget, const QRegion &region);
void framePass(RenderLayer *layer);
State m_state = State::Off;
std::unique_ptr<CompositorSelectionOwner> m_selectionOwner;

@ -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

@ -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 &region);
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<RenderLayerDelegate> m_delegate;
QRegion m_repaints;
QRectF m_boundingRect;

@ -24,6 +24,10 @@ QRegion RenderLayerDelegate::repaints() const
return QRegion();
}
void RenderLayerDelegate::frame()
{
}
void RenderLayerDelegate::prePaint()
{
}

@ -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();

@ -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;

@ -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.

@ -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 &region)
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 &regio
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<SceneDelegate *> delegates = m_scene->delegates();
for (SceneDelegate *delegate : delegates) {
if (delegate->viewport().intersects(geometry)) {
delegate->layer()->loop()->scheduleRepaint(this);
delegate->layer()->scheduleRepaint(this);
}
}
}

@ -49,6 +49,11 @@ void SceneDelegate::paint(const RenderTarget &renderTarget, const QRegion &regio
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"

@ -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 &region) override;
@ -85,6 +86,7 @@ public:
virtual void prePaint(SceneDelegate *delegate) = 0;
virtual void postPaint() = 0;
virtual void paint(const RenderTarget &renderTarget, const QRegion &region) = 0;
virtual void frame(SceneDelegate *delegate);
Q_SIGNALS:
void delegateRemoved(SceneDelegate *delegate);

@ -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<std::chrono::milliseconds>(output->renderLoop()->lastPresentationTimestamp());
const QList<Item *> items = m_containerItem->sortedChildItems();
for (Item *item : items) {
if (!item->isVisible()) {
continue;
}
Window *window = static_cast<WindowItem *>(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<std::chrono::milliseconds>(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());
}

@ -60,6 +60,7 @@ public:
void prePaint(SceneDelegate *delegate) override;
void postPaint() override;
void paint(const RenderTarget &renderTarget, const QRegion &region) override;
void frame(SceneDelegate *delegate) override;
virtual bool makeOpenGLContextCurrent();
virtual void doneOpenGLContextCurrent();

Loading…
Cancel
Save