diff --git a/abstract_egl_backend.cpp b/abstract_egl_backend.cpp --- a/abstract_egl_backend.cpp +++ b/abstract_egl_backend.cpp @@ -22,6 +22,7 @@ #include "wayland_server.h" #include #include +#include // kwin libs #include // Qt @@ -289,6 +290,9 @@ return loadTexture(pixmap->pixmap(), pixmap->toplevel()->size()); } // try Wayland loading + if (auto s = pixmap->surface()) { + s->resetTrackedDamage(); + } if (buffer->shmBuffer()) { return loadShmTexture(buffer); } else { @@ -339,24 +343,29 @@ } return; } + auto s = pixmap->surface(); if (!buffer->shmBuffer()) { q->bind(); EGLImageKHR image = attach(buffer); q->unbind(); if (image != EGL_NO_IMAGE_KHR) { eglDestroyImageKHR(m_backend->eglDisplay(), m_image); m_image = image; } + if (s) { + s->resetTrackedDamage(); + } return; } // shm fallback const QImage &image = buffer->data(); - if (image.isNull()) { + if (image.isNull() || !s) { return; } Q_ASSERT(image.size() == m_size); q->bind(); - const QRegion &damage = pixmap->toplevel()->damage(); + const QRegion damage = s->trackedDamage(); + s->resetTrackedDamage(); // TODO: this should be shared with GLTexture::update if (GLPlatform::instance()->isGLES()) { diff --git a/scene.h b/scene.h --- a/scene.h +++ b/scene.h @@ -35,6 +35,7 @@ namespace Server { class BufferInterface; +class SubSurfaceInterface; } } @@ -239,7 +240,7 @@ QRect rect() const; // access to the internal window class // TODO eventually get rid of this - Toplevel* window(); + Toplevel* window() const; // should the window be painted bool isPaintingEnabled() const; void resetPaintingEnabled(); @@ -390,10 +391,38 @@ * @brief Returns the Toplevel this WindowPixmap belongs to. * Note: the Toplevel can change over the lifetime of the WindowPixmap in case the Toplevel is copied to Deleted. */ - Toplevel *toplevel(); + Toplevel *toplevel() const; + + /** + * @returns the parent WindowPixmap in the sub-surface tree + **/ + WindowPixmap *parent() const { + return m_parent; + } + + /** + * @returns the current sub-surface tree + **/ + QVector children() const { + return m_children; + } + + /** + * @returns the subsurface this WindowPixmap is for if it is not for a root window + **/ + QPointer subSurface() const { + return m_subSurface; + } + + /** + * @returns the surface this WindowPixmap references, might be @c null. + **/ + KWayland::Server::SurfaceInterface *surface() const; protected: explicit WindowPixmap(Scene::Window *window); + explicit WindowPixmap(const QPointer &subSurface, WindowPixmap *parent); + virtual WindowPixmap *createChild(const QPointer &subSurface); /** * @return The Window this WindowPixmap belongs to */ @@ -403,15 +432,26 @@ * Should be called by the implementing subclasses when the Wayland Buffer changed and needs * updating. **/ - void updateBuffer(); + virtual void updateBuffer(); + + /** + * Sets the sub-surface tree to @p children. + **/ + void setChildren(const QVector &children) { + m_children = children; + } + private: Scene::Window *m_window; xcb_pixmap_t m_pixmap; QSize m_pixmapSize; bool m_discarded; QRect m_contentsRect; QPointer m_buffer; QSharedPointer m_fbo; + WindowPixmap *m_parent = nullptr; + QVector m_children; + QPointer m_subSurface; }; class Scene::EffectFrame @@ -480,7 +520,7 @@ } inline -Toplevel* Scene::Window::window() +Toplevel* Scene::Window::window() const { return toplevel; } @@ -553,7 +593,7 @@ } inline -Toplevel* WindowPixmap::toplevel() +Toplevel* WindowPixmap::toplevel() const { return m_window->window(); } diff --git a/scene.cpp b/scene.cpp --- a/scene.cpp +++ b/scene.cpp @@ -82,6 +82,7 @@ #include "thumbnailitem.h" #include +#include #include namespace KWin @@ -937,6 +938,15 @@ { } +WindowPixmap::WindowPixmap(const QPointer &subSurface, WindowPixmap *parent) + : m_window(parent->m_window) + , m_pixmap(XCB_PIXMAP_NONE) + , m_discarded(false) + , m_parent(parent) + , m_subSurface(subSurface) +{ +} + WindowPixmap::~WindowPixmap() { if (isValid() && !kwinApp()->shouldUseWaylandForCompositing()) { @@ -957,7 +967,7 @@ if (kwinApp()->shouldUseWaylandForCompositing()) { // use Buffer updateBuffer(); - if (m_buffer || !m_fbo.isNull()) { + if ((m_buffer || !m_fbo.isNull()) && m_subSurface.isNull()) { m_window->unreferencePreviousPixmap(); } return; @@ -991,6 +1001,12 @@ m_window->unreferencePreviousPixmap(); } +WindowPixmap *WindowPixmap::createChild(const QPointer &subSurface) +{ + Q_UNUSED(subSurface) + return nullptr; +} + bool WindowPixmap::isValid() const { if (kwinApp()->shouldUseWaylandForCompositing()) { @@ -1001,23 +1017,71 @@ void WindowPixmap::updateBuffer() { - if (auto s = toplevel()->surface()) { + using namespace KWayland::Server; + if (SurfaceInterface *s = surface()) { + QVector oldTree = m_children; + QVector children; using namespace KWayland::Server; + const auto subSurfaces = s->childSubSurfaces(); + for (const auto &subSurface : subSurfaces) { + if (subSurface.isNull()) { + continue; + } + auto it = std::find_if(oldTree.begin(), oldTree.end(), [subSurface] (WindowPixmap *p) { return p->m_subSurface == subSurface; }); + if (it != oldTree.end()) { + children << *it; + (*it)->updateBuffer(); + oldTree.erase(it); + } else { + WindowPixmap *p = createChild(subSurface); + if (p) { + p->create(); + children << p; + } + } + } + setChildren(children); + qDeleteAll(oldTree); if (auto b = s->buffer()) { + if (b == m_buffer) { + // no change + return; + } if (m_buffer) { QObject::disconnect(m_buffer.data(), &BufferInterface::aboutToBeDestroyed, m_buffer.data(), &BufferInterface::unref); m_buffer->unref(); } m_buffer = b; m_buffer->ref(); QObject::connect(m_buffer.data(), &BufferInterface::aboutToBeDestroyed, m_buffer.data(), &BufferInterface::unref); + } else if (m_subSurface) { + if (m_buffer) { + QObject::disconnect(m_buffer.data(), &BufferInterface::aboutToBeDestroyed, m_buffer.data(), &BufferInterface::unref); + m_buffer->unref(); + m_buffer.clear(); + } } else { // might be an internal window const auto &fbo = toplevel()->internalFramebufferObject(); if (!fbo.isNull()) { m_fbo = fbo; } } + } else { + if (m_buffer) { + QObject::disconnect(m_buffer.data(), &BufferInterface::aboutToBeDestroyed, m_buffer.data(), &BufferInterface::unref); + m_buffer->unref(); + m_buffer.clear(); + } + } +} + +KWayland::Server::SurfaceInterface *WindowPixmap::surface() const +{ + if (!m_subSurface.isNull()) { + return m_subSurface->surface().data(); + } else { + return toplevel()->surface(); } } diff --git a/scene_opengl.h b/scene_opengl.h --- a/scene_opengl.h +++ b/scene_opengl.h @@ -274,8 +274,12 @@ virtual ~OpenGLWindowPixmap(); SceneOpenGL::Texture *texture() const; bool bind(); +protected: + WindowPixmap *createChild(const QPointer &subSurface) override; private: + explicit OpenGLWindowPixmap(const QPointer &subSurface, WindowPixmap *parent, SceneOpenGL *scene); QScopedPointer m_texture; + SceneOpenGL *m_scene; }; class SceneOpenGL::EffectFrame diff --git a/scene_opengl.cpp b/scene_opengl.cpp --- a/scene_opengl.cpp +++ b/scene_opengl.cpp @@ -49,6 +49,9 @@ #include "screens.h" #include "decorations/decoratedclient.h" +#include +#include + #include #include #include @@ -1505,15 +1508,39 @@ return scene->projectionMatrix() * mvMatrix; } +static void renderSubSurface(GLShader *shader, const QMatrix4x4 &mvp, const QMatrix4x4 &windowMatrix, OpenGLWindowPixmap *pixmap) +{ + QMatrix4x4 newWindowMatrix = windowMatrix; + newWindowMatrix.translate(pixmap->subSurface()->position().x(), pixmap->subSurface()->position().y()); + + if (!pixmap->texture()->isNull()) { + // render this texture + shader->setUniform(GLShader::ModelViewProjectionMatrix, mvp * newWindowMatrix); + auto texture = pixmap->texture(); + texture->bind(); + texture->render(QRegion(), QRect(0, 0, texture->width(), texture->height())); + texture->unbind(); + } + + const auto &children = pixmap->children(); + for (auto pixmap : children) { + if (pixmap->subSurface().isNull() || pixmap->subSurface()->surface().isNull() || !pixmap->subSurface()->surface()->isMapped()) { + continue; + } + renderSubSurface(shader, mvp, newWindowMatrix, static_cast(pixmap)); + } +} + void SceneOpenGL2Window::performPaint(int mask, QRegion region, WindowPaintData data) { if (!beginRenderWindow(mask, region, data)) return; SceneOpenGL2 *scene = static_cast(m_scene); - const QMatrix4x4 windowMatrix = transformation(mask, data); - const QMatrix4x4 mvpMatrix = modelViewProjectionMatrix(mask, data) * windowMatrix; + QMatrix4x4 windowMatrix = transformation(mask, data); + const QMatrix4x4 modelViewProjection = modelViewProjectionMatrix(mask, data); + const QMatrix4x4 mvpMatrix = modelViewProjection * windowMatrix; GLShader *shader = data.shader; if (!shader) { @@ -1641,6 +1668,16 @@ setBlendEnabled(false); + // render sub-surfaces + const auto &children = windowPixmap()->children(); + windowMatrix.translate(toplevel->clientPos().x(), toplevel->clientPos().y()); + for (auto pixmap : children) { + if (pixmap->subSurface().isNull() || pixmap->subSurface()->surface().isNull() || !pixmap->subSurface()->surface()->isMapped()) { + continue; + } + renderSubSurface(shader, modelViewProjection, windowMatrix, static_cast(pixmap)); + } + if (!data.shader) ShaderManager::instance()->popShader(); @@ -1655,6 +1692,14 @@ OpenGLWindowPixmap::OpenGLWindowPixmap(Scene::Window *window, SceneOpenGL* scene) : WindowPixmap(window) , m_texture(scene->createTexture()) + , m_scene(scene) +{ +} + +OpenGLWindowPixmap::OpenGLWindowPixmap(const QPointer &subSurface, WindowPixmap *parent, SceneOpenGL *scene) + : WindowPixmap(subSurface, parent) + , m_texture(scene->createTexture()) + , m_scene(scene) { } @@ -1665,28 +1710,53 @@ bool OpenGLWindowPixmap::bind() { if (!m_texture->isNull()) { - if (!toplevel()->damage().isEmpty()) { + // always call updateBuffer to get the sub-surface tree updated + if (subSurface().isNull() && !toplevel()->damage().isEmpty()) { updateBuffer(); + } + auto s = surface(); + if (s && !s->trackedDamage().isEmpty()) { m_texture->updateFromPixmap(this); // mipmaps need to be updated m_texture->setDirty(); + } + if (subSurface().isNull()) { toplevel()->resetDamage(); } + // also bind all children + for (auto it = children().constBegin(); it != children().constEnd(); ++it) { + static_cast(*it)->bind(); + } return true; } + // also bind all children, needs to be done before checking isValid + // as there might be valid children to render, see https://bugreports.qt.io/browse/QTBUG-52192 + if (subSurface().isNull()) { + updateBuffer(); + } + for (auto it = children().constBegin(); it != children().constEnd(); ++it) { + static_cast(*it)->bind(); + } if (!isValid()) { return false; } bool success = m_texture->load(this); - if (success) - toplevel()->resetDamage(); - else + if (success) { + if (subSurface().isNull()) { + toplevel()->resetDamage(); + } + } else qCDebug(KWIN_CORE) << "Failed to bind window"; return success; } +WindowPixmap *OpenGLWindowPixmap::createChild(const QPointer &subSurface) +{ + return new OpenGLWindowPixmap(subSurface, this, m_scene); +} + //**************************************** // SceneOpenGL::EffectFrame //**************************************** diff --git a/scene_qpainter.h b/scene_qpainter.h --- a/scene_qpainter.h +++ b/scene_qpainter.h @@ -156,9 +156,13 @@ virtual ~QPainterWindowPixmap(); virtual void create() override; - bool update(const QRegion &damage); + void updateBuffer() override; const QImage &image(); + +protected: + WindowPixmap *createChild(const QPointer &subSurface) override; private: + explicit QPainterWindowPixmap(const QPointer &subSurface, WindowPixmap *parent); QImage m_image; }; diff --git a/scene_qpainter.cpp b/scene_qpainter.cpp --- a/scene_qpainter.cpp +++ b/scene_qpainter.cpp @@ -30,6 +30,7 @@ #include "abstract_backend.h" #include "wayland_server.h" #include +#include #include #include "decorations/decoratedclient.h" // Qt @@ -235,6 +236,23 @@ discardShape(); } +static void paintSubSurface(QPainter *painter, const QPoint &pos, QPainterWindowPixmap *pixmap) +{ + QPoint p = pos; + if (!pixmap->subSurface().isNull()) { + p += pixmap->subSurface()->position(); + } + painter->drawImage(p, pixmap->image()); + const auto &children = pixmap->children(); + for (auto it = children.begin(); it != children.end(); ++it) { + auto pixmap = static_cast(*it); + if (pixmap->subSurface().isNull() || pixmap->subSurface()->surface().isNull() || !pixmap->subSurface()->surface()->isMapped()) { + continue; + } + paintSubSurface(painter, p, pixmap); + } +} + void SceneQPainter::Window::performPaint(int mask, QRegion region, WindowPaintData data) { if (!(mask & (PAINT_WINDOW_TRANSFORMED | PAINT_SCREEN_TRANSFORMED))) @@ -247,7 +265,7 @@ return; } if (!toplevel->damage().isEmpty()) { - pixmap->update(toplevel->damage()); + pixmap->updateBuffer(); toplevel->resetDamage(); } @@ -282,6 +300,15 @@ const QRect src = QRect(toplevel->clientPos() + toplevel->clientContentPos(), toplevel->clientSize()); painter->drawImage(toplevel->clientPos(), pixmap->image(), src); + // render subsurfaces + const auto &children = pixmap->children(); + for (auto pixmap : children) { + if (pixmap->subSurface().isNull() || pixmap->subSurface()->surface().isNull() || !pixmap->subSurface()->surface()->isMapped()) { + continue; + } + paintSubSurface(painter, toplevel->clientPos(), static_cast(pixmap)); + } + if (!opaque) { tempPainter.restore(); tempPainter.setCompositionMode(QPainter::CompositionMode_DestinationIn); @@ -406,6 +433,11 @@ { } +QPainterWindowPixmap::QPainterWindowPixmap(const QPointer &subSurface, WindowPixmap *parent) + : WindowPixmap(subSurface, parent) +{ +} + QPainterWindowPixmap::~QPainterWindowPixmap() { } @@ -421,23 +453,33 @@ } // performing deep copy, this could probably be improved m_image = buffer()->data().copy(); + if (auto s = surface()) { + s->resetTrackedDamage(); + } +} + +WindowPixmap *QPainterWindowPixmap::createChild(const QPointer &subSurface) +{ + return new QPainterWindowPixmap(subSurface, this); } -bool QPainterWindowPixmap::update(const QRegion &damage) +void QPainterWindowPixmap::updateBuffer() { const auto oldBuffer = buffer(); - updateBuffer(); + WindowPixmap::updateBuffer(); const auto &b = buffer(); - if (b == oldBuffer || b.isNull()) { - return false; + if (b.isNull()) { + m_image = QImage(); + return; } - QPainter p(&m_image); - const QImage &data = b->data(); - p.setCompositionMode(QPainter::CompositionMode_Source); - for (const QRect &rect : damage.rects()) { - p.drawImage(rect, data, rect); + if (b == oldBuffer) { + return; + } + // perform deep copy + m_image = b->data().copy(); + if (auto s = surface()) { + s->resetTrackedDamage(); } - return true; } QPainterEffectFrame::QPainterEffectFrame(EffectFrameImpl *frame, SceneQPainter *scene) diff --git a/toplevel.cpp b/toplevel.cpp --- a/toplevel.cpp +++ b/toplevel.cpp @@ -451,6 +451,15 @@ } m_surface = surface; connect(m_surface, &SurfaceInterface::damaged, this, &Toplevel::addDamage); + connect(m_surface, &SurfaceInterface::subSurfaceTreeChanged, this, + [this] { + // TODO improve to only update actual visual area + if (ready_for_painting) { + addDamageFull(); + m_isDamaged = true; + } + } + ); connect(m_surface, &SurfaceInterface::destroyed, this, [this] { m_surface = nullptr; diff --git a/wayland_server.cpp b/wayland_server.cpp --- a/wayland_server.cpp +++ b/wayland_server.cpp @@ -44,6 +44,7 @@ #include #include #include +#include #include #include #include @@ -239,6 +240,8 @@ this, [this](KWayland::Server::OutputConfigurationInterface *config) { m_backend->configurationChangeRequested(config); }); + + m_display->createSubCompositor(m_display)->create(); } void WaylandServer::initWorkspace()