Move direct scanout management to Compositor

The responsibilities of the Scene must be reduced to painting only so we
can move forward with the layer-based compositing.

This change moves direct scanout logic from the opengl scene to the base
scene class and the compositor. It makes the opengl scene less
overloaded and allows to share direct scanout logic.
This commit is contained in:
Vlad Zahorodnii 2022-02-04 19:38:58 +02:00
parent 886173cabe
commit 203d7b3b8a
15 changed files with 164 additions and 197 deletions

View file

@ -306,11 +306,6 @@ QSharedPointer<GLTexture> EglGbmBackend::textureForOutput(AbstractOutput *output
return m_surfaces[output]->texture();
}
bool EglGbmBackend::directScanoutAllowed(AbstractOutput *output) const
{
return !output->usesSoftwareCursor() && !output->directScanoutInhibited();
}
GbmFormat EglGbmBackend::gbmFormatForDrmFormat(uint32_t format) const
{
// TODO use a hardcoded lookup table where needed instead?

View file

@ -69,8 +69,6 @@ public:
QSharedPointer<GLTexture> textureForOutput(AbstractOutput *requestedOutput) const override;
bool directScanoutAllowed(AbstractOutput *output) const override;
QSharedPointer<DrmBuffer> testBuffer(DrmAbstractOutput *output);
EGLConfig config(uint32_t format) const;
GbmFormat gbmFormatForDrmFormat(uint32_t format) const;

View file

@ -591,38 +591,6 @@ void Compositor::handleFrameRequested(RenderLoop *renderLoop)
composite(renderLoop);
}
QList<Toplevel *> Compositor::windowsToRender() const
{
// Create a list of all windows in the stacking order
QList<Toplevel *> windows = Workspace::self()->xStackingOrder();
// Move elevated windows to the top of the stacking order
const QList<EffectWindow *> elevatedList = static_cast<EffectsHandlerImpl *>(effects)->elevatedWindows();
for (EffectWindow *c : elevatedList) {
Toplevel *t = static_cast<EffectWindowImpl *>(c)->window();
windows.removeAll(t);
windows.append(t);
}
// Skip windows that are not yet ready for being painted and if screen is locked skip windows
// that are neither lockscreen nor inputmethod windows.
//
// TODO? This cannot be used so carelessly - needs protections against broken clients, the
// window should not get focus before it's displayed, handle unredirected windows properly and
// so on.
for (Toplevel *win : windows) {
if (!win->readyForPainting()) {
windows.removeAll(win);
}
if (waylandServer() && waylandServer()->isScreenLocked()) {
if(!win->isLockScreen() && !win->isInputMethod()) {
windows.removeAll(win);
}
}
}
return windows;
}
void Compositor::composite(RenderLoop *renderLoop)
{
if (m_backend->checkGraphicsReset()) {
@ -635,39 +603,31 @@ void Compositor::composite(RenderLoop *renderLoop)
AbstractOutput *output = findOutput(renderLoop);
fTraceDuration("Paint (", output->name(), ")");
const auto windows = windowsToRender();
const QRegion repaints = m_scene->repaints(output);
const QRegion damage = m_scene->repaints(output);
m_scene->resetRepaints(output);
m_scene->prePaint(output);
m_scene->paint(output, repaints, windows, renderLoop);
SurfaceItem *scanoutCandidate = m_scene->scanoutCandidate();
renderLoop->setFullscreenSurface(scanoutCandidate);
if (waylandServer()) {
const std::chrono::milliseconds frameTime =
std::chrono::duration_cast<std::chrono::milliseconds>(renderLoop->lastPresentationTimestamp());
for (Toplevel *window : windows) {
if (!window->readyForPainting()) {
continue;
}
if (waylandServer()->isScreenLocked() &&
!(window->isLockScreen() || window->isInputMethod())) {
continue;
}
if (!window->isOnOutput(output)) {
continue;
}
if (auto surface = window->surface()) {
surface->frameRendered(frameTime.count());
}
}
if (!Cursors::self()->isCursorHidden()) {
Cursor *cursor = Cursors::self()->currentCursor();
if (cursor->geometry().intersects(output->geometry())) {
cursor->markAsRendered(frameTime);
}
renderLoop->beginFrame();
bool directScanout = false;
if (scanoutCandidate) {
if (!output->usesSoftwareCursor() && !output->directScanoutInhibited()) {
directScanout = m_backend->scanout(output, scanoutCandidate);
}
}
if (directScanout) {
renderLoop->endFrame();
} else {
QRegion update, valid;
const QRegion repaint = m_backend->beginFrame(output);
m_scene->paint(damage, repaint, update, valid);
renderLoop->endFrame();
m_backend->endFrame(output, valid, update);
}
m_scene->postPaint();
}
bool Compositor::isActive()

View file

@ -84,7 +84,6 @@ public:
// for delayed supportproperty management of effects
void keepSupportProperty(xcb_atom_t atom);
void removeSupportProperty(xcb_atom_t atom);
QList<Toplevel *> windowsToRender() const;
Q_SIGNALS:
void compositingToggled(bool active);

View file

@ -1776,7 +1776,7 @@ void EffectsHandlerImpl::slotOutputDisabled(AbstractOutput *output)
void EffectsHandlerImpl::renderScreen(EffectScreen *screen)
{
auto output = static_cast<EffectScreenImpl *>(screen)->platformOutput();
scene()->paintScreen(output, Compositor::self()->windowsToRender());
scene()->paintScreen(output);
}
bool EffectsHandlerImpl::isCursorHidden() const

View file

@ -43,13 +43,6 @@ void OpenGLBackend::setFailed(const QString &reason)
m_failed = true;
}
bool OpenGLBackend::scanout(AbstractOutput *output, SurfaceItem *surfaceItem)
{
Q_UNUSED(output)
Q_UNUSED(surfaceItem)
return false;
}
void OpenGLBackend::copyPixels(const QRegion &region)
{
const int height = screens()->size().height();
@ -75,13 +68,6 @@ void OpenGLBackend::aboutToStartPainting(AbstractOutput *output, const QRegion &
Q_UNUSED(damage)
}
bool OpenGLBackend::directScanoutAllowed(AbstractOutput *output) const
{
Q_UNUSED(output);
return false;
}
SurfaceTexture *OpenGLBackend::createSurfaceTextureInternal(SurfacePixmapInternal *pixmap)
{
Q_UNUSED(pixmap)

View file

@ -63,11 +63,6 @@ public:
virtual void aboutToStartPainting(AbstractOutput *output, const QRegion &damage);
virtual bool makeCurrent() = 0;
virtual void doneCurrent() = 0;
/**
* Tries to directly scan out a surface to the screen)
* @return if the scanout fails (or is not supported on the specified screen)
*/
virtual bool scanout(AbstractOutput *output, SurfaceItem *surfaceItem);
/**
* @brief Whether the creation of the Backend failed.
@ -109,7 +104,6 @@ public:
{
return m_haveNativeFence;
}
virtual bool directScanoutAllowed(AbstractOutput *output) const;
/**
* The backend specific extensions (e.g. EGL/GLX extensions).

View file

@ -24,4 +24,11 @@ bool RenderBackend::checkGraphicsReset()
return false;
}
bool RenderBackend::scanout(AbstractOutput *output, SurfaceItem *surfaceItem)
{
Q_UNUSED(output)
Q_UNUSED(surfaceItem)
return false;
}
} // namespace KWin

View file

@ -15,6 +15,7 @@ namespace KWin
class AbstractOutput;
class OverlayWindow;
class SurfaceItem;
/**
* The RenderBackend class is the base class for all rendering backends.
@ -33,6 +34,12 @@ public:
virtual QRegion beginFrame(AbstractOutput *output) = 0;
virtual void endFrame(AbstractOutput *output, const QRegion &renderedRegion, const QRegion &damagedRegion) = 0;
/**
* Tries to directly scan out a surface to the screen
* Returns @c true if scanout succeeds, @c false if rendering is necessary
*/
virtual bool scanout(AbstractOutput *output, SurfaceItem *surfaceItem);
};
} // namespace KWin

View file

@ -76,6 +76,8 @@
#include "composite.h"
#include <QtMath>
#include <KWaylandServer/surface_interface.h>
namespace KWin
{
@ -186,6 +188,84 @@ void Scene::removeRepaints(AbstractOutput *output)
m_repaints.remove(output);
}
static SurfaceItem *findTopMostSurface(SurfaceItem *item)
{
const QList<Item *> children = item->childItems();
if (children.isEmpty()) {
return item;
} else {
return findTopMostSurface(static_cast<SurfaceItem *>(children.constLast()));
}
}
SurfaceItem *Scene::scanoutCandidate() const
{
SurfaceItem *candidate = nullptr;
if (!static_cast<EffectsHandlerImpl*>(effects)->blocksDirectScanout()) {
for (int i = stacking_order.count() - 1; i >=0; i--) {
Window *window = stacking_order[i];
Toplevel *toplevel = window->window();
if (painted_screen && toplevel->isOnOutput(painted_screen) && window->isVisible() && toplevel->opacity() > 0) {
AbstractClient *c = dynamic_cast<AbstractClient*>(toplevel);
if (!c || !c->isFullScreen()) {
break;
}
if (!window->surfaceItem()) {
break;
}
SurfaceItem *topMost = findTopMostSurface(window->surfaceItem());
auto pixmap = topMost->pixmap();
if (!pixmap) {
break;
}
pixmap->update();
// the subsurface has to be able to cover the whole window
if (topMost->position() != QPoint(0, 0)) {
break;
}
// and it has to be completely opaque
if (!window->isOpaque() && !topMost->opaque().contains(QRect(0, 0, window->width(), window->height()))) {
break;
}
candidate = topMost;
break;
}
}
}
return candidate;
}
void Scene::prePaint(AbstractOutput *output)
{
createStackingOrder();
painted_screen = output;
}
void Scene::postPaint()
{
if (waylandServer()) {
const std::chrono::milliseconds frameTime =
std::chrono::duration_cast<std::chrono::milliseconds>(painted_screen->renderLoop()->lastPresentationTimestamp());
for (Window *window : std::as_const(m_windows)) {
Toplevel *toplevel = window->window();
if (!toplevel->isOnOutput(painted_screen)) {
continue;
}
if (auto surface = toplevel->surface()) {
surface->frameRendered(frameTime.count());
}
}
if (!Cursors::self()->isCursorHidden()) {
Cursor *cursor = Cursors::self()->currentCursor();
if (cursor->geometry().intersects(painted_screen->geometry())) {
cursor->markAsRendered(frameTime);
}
}
}
clearStackingOrder();
}
static QMatrix4x4 createProjectionMatrix(const QRect &rect)
{
@ -252,9 +332,9 @@ QRegion Scene::mapToRenderTarget(const QRegion &region) const
return result;
}
void Scene::paintScreen(AbstractOutput *output, const QList<Toplevel *> &toplevels)
void Scene::paintScreen(AbstractOutput *output)
{
createStackingOrder(toplevels);
createStackingOrder();
painted_screen = output;
QRegion update, valid;
@ -594,10 +674,38 @@ void Scene::windowClosed(Toplevel *toplevel, Deleted *deleted)
m_windows[deleted] = window;
}
void Scene::createStackingOrder(const QList<Toplevel *> &toplevels)
void Scene::createStackingOrder()
{
// Create a list of all windows in the stacking order
QList<Toplevel *> windows = Workspace::self()->xStackingOrder();
// Move elevated windows to the top of the stacking order
const QList<EffectWindow *> elevatedList = static_cast<EffectsHandlerImpl *>(effects)->elevatedWindows();
for (EffectWindow *c : elevatedList) {
Toplevel *t = static_cast<EffectWindowImpl *>(c)->window();
windows.removeAll(t);
windows.append(t);
}
// Skip windows that are not yet ready for being painted and if screen is locked skip windows
// that are neither lockscreen nor inputmethod windows.
//
// TODO? This cannot be used so carelessly - needs protections against broken clients, the
// window should not get focus before it's displayed, handle unredirected windows properly and
// so on.
for (Toplevel *win : windows) {
if (!win->readyForPainting()) {
windows.removeAll(win);
}
if (waylandServer() && waylandServer()->isScreenLocked()) {
if(!win->isLockScreen() && !win->isInputMethod()) {
windows.removeAll(win);
}
}
}
// TODO: cache the stacking_order in case it has not changed
for (Toplevel *c : toplevels) {
for (Toplevel *c : std::as_const(windows)) {
Q_ASSERT(m_windows.contains(c));
stacking_order.append(m_windows[ c ]);
}

View file

@ -74,15 +74,13 @@ public:
// Returns true if the ctor failed to properly initialize.
virtual bool initFailed() const = 0;
// Repaints the given screen areas, windows provides the stacking order.
// The entry point for the main part of the painting pass.
// returns the time since the last vblank signal - if there's one
// ie. "what of this frame is lost to painting"
virtual void paint(AbstractOutput *output, const QRegion &damage, const QList<Toplevel *> &windows,
RenderLoop *renderLoop) = 0;
SurfaceItem *scanoutCandidate() const;
void prePaint(AbstractOutput *output);
void postPaint();
virtual void paint(const QRegion &damage, const QRegion &repaint, QRegion &update, QRegion &valid) = 0;
void paintScreen(AbstractOutput *output, const QList<Toplevel *> &toplevels);
void paintScreen(AbstractOutput *output);
/**
* Adds the Toplevel to the Scene.
@ -205,7 +203,7 @@ public Q_SLOTS:
void windowClosed(KWin::Toplevel* c, KWin::Deleted* deleted);
protected:
virtual Window *createWindow(Toplevel *toplevel) = 0;
void createStackingOrder(const QList<Toplevel *> &toplevels);
void createStackingOrder();
void clearStackingOrder();
// shared implementation, starts painting the screen
void paintScreen(const QRegion &damage, const QRegion &repaint,

View file

@ -174,83 +174,12 @@ void SceneOpenGL::aboutToStartPainting(AbstractOutput *output, const QRegion &da
m_backend->aboutToStartPainting(output, damage);
}
static SurfaceItem *findTopMostSurface(SurfaceItem *item)
void SceneOpenGL::paint(const QRegion &damage, const QRegion &repaint, QRegion &update, QRegion &valid)
{
const QList<Item *> children = item->childItems();
if (children.isEmpty()) {
return item;
} else {
return findTopMostSurface(static_cast<SurfaceItem *>(children.constLast()));
}
}
void SceneOpenGL::paint(AbstractOutput *output, const QRegion &damage, const QList<Toplevel *> &toplevels,
RenderLoop *renderLoop)
{
painted_screen = output;
// actually paint the frame, flushed with the NEXT frame
createStackingOrder(toplevels);
QRegion update;
QRegion valid;
QRegion repaint;
renderLoop->beginFrame();
SurfaceItem *fullscreenSurface = nullptr;
for (int i = stacking_order.count() - 1; i >=0; i--) {
Window *window = stacking_order[i];
Toplevel *toplevel = window->window();
if (output && toplevel->isOnOutput(output) && window->isVisible() && toplevel->opacity() > 0) {
AbstractClient *c = dynamic_cast<AbstractClient*>(toplevel);
if (!c || !c->isFullScreen()) {
break;
}
if (!window->surfaceItem()) {
break;
}
SurfaceItem *topMost = findTopMostSurface(window->surfaceItem());
auto pixmap = topMost->pixmap();
if (!pixmap) {
break;
}
pixmap->update();
// the subsurface has to be able to cover the whole window
if (topMost->position() != QPoint(0, 0)) {
break;
}
// and it has to be completely opaque
if (!window->isOpaque() && !topMost->opaque().contains(QRect(0, 0, window->width(), window->height()))) {
break;
}
fullscreenSurface = topMost;
break;
}
}
renderLoop->setFullscreenSurface(fullscreenSurface);
bool directScanout = false;
if (m_backend->directScanoutAllowed(output) && !static_cast<EffectsHandlerImpl*>(effects)->blocksDirectScanout()) {
directScanout = m_backend->scanout(output, fullscreenSurface);
}
if (directScanout) {
renderLoop->endFrame();
} else {
// prepare rendering makescontext current on the output
repaint = m_backend->beginFrame(output);
GLVertexBuffer::streamingBuffer()->beginFrame();
paintScreen(damage, repaint, &update, &valid);
paintCursor(output, valid);
renderLoop->endFrame();
GLVertexBuffer::streamingBuffer()->endOfFrame();
m_backend->endFrame(output, valid, update);
}
// do cleanup
clearStackingOrder();
GLVertexBuffer::streamingBuffer()->beginFrame();
paintScreen(damage, repaint, &update, &valid);
paintCursor(painted_screen, valid);
GLVertexBuffer::streamingBuffer()->endOfFrame();
}
QMatrix4x4 SceneOpenGL::transformation(int mask, const ScreenPaintData &data) const

View file

@ -33,8 +33,7 @@ public:
explicit SceneOpenGL(OpenGLBackend *backend, QObject *parent = nullptr);
~SceneOpenGL() override;
bool initFailed() const override;
void paint(AbstractOutput *output, const QRegion &damage, const QList<Toplevel *> &windows,
RenderLoop *renderLoop) override;
void paint(const QRegion &damage, const QRegion &repaint, QRegion &update, QRegion &valid) override;
Scene::EffectFrame *createEffectFrame(EffectFrameImpl *frame) override;
Shadow *createShadow(Toplevel *toplevel) override;
bool makeOpenGLContextCurrent() override;

View file

@ -68,34 +68,22 @@ void SceneQPainter::paintGenericScreen(int mask, const ScreenPaintData &data)
m_painter->restore();
}
void SceneQPainter::paint(AbstractOutput *output, const QRegion &damage, const QList<Toplevel *> &toplevels,
RenderLoop *renderLoop)
void SceneQPainter::paint(const QRegion &damage, const QRegion &repaint, QRegion &update, QRegion &valid)
{
Q_ASSERT(kwinApp()->platform()->isPerScreenRenderingEnabled());
painted_screen = output;
createStackingOrder(toplevels);
const QRegion repaint = m_backend->beginFrame(output);
const QRect geometry = output->geometry();
QImage *buffer = m_backend->bufferForScreen(output);
QImage *buffer = m_backend->bufferForScreen(painted_screen);
if (buffer && !buffer->isNull()) {
renderLoop->beginFrame();
const QRect geometry = painted_screen->geometry();
m_painter->begin(buffer);
m_painter->setWindow(geometry);
QRegion updateRegion, validRegion;
paintScreen(damage, repaint, &updateRegion, &validRegion);
paintCursor(output, updateRegion);
paintCursor(painted_screen, updateRegion);
m_painter->end();
renderLoop->endFrame();
m_backend->endFrame(output, validRegion, updateRegion);
}
// do cleanup
clearStackingOrder();
}
void SceneQPainter::paintBackground(const QRegion &region)

View file

@ -23,8 +23,7 @@ class KWIN_EXPORT SceneQPainter : public Scene
public:
~SceneQPainter() override;
void paint(AbstractOutput *output, const QRegion &damage, const QList<Toplevel *> &windows,
RenderLoop *renderLoop) override;
void paint(const QRegion &damage, const QRegion &repaint, QRegion &update, QRegion &valid) override;
void paintGenericScreen(int mask, const ScreenPaintData &data) override;
bool initFailed() const override;
EffectFrame *createEffectFrame(EffectFrameImpl *frame) override;