diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -485,6 +485,7 @@ scripting/workspace_wrapper.cpp shadow.cpp sm.cpp + subsurfacemonitor.cpp thumbnailitem.cpp toplevel.cpp touch_hide_cursor_spy.cpp diff --git a/platformsupport/scenes/opengl/abstract_egl_backend.cpp b/platformsupport/scenes/opengl/abstract_egl_backend.cpp --- a/platformsupport/scenes/opengl/abstract_egl_backend.cpp +++ b/platformsupport/scenes/opengl/abstract_egl_backend.cpp @@ -427,12 +427,6 @@ if (image.isNull() || !s) { return; } - if (image.size() != m_size) { - // buffer size has changed, reload shm texture - if (!loadTexture(pixmap)) { - return; - } - } Q_ASSERT(image.size() == m_size); const QRegion damage = s->trackedDamage(); s->resetTrackedDamage(); diff --git a/plugins/scenes/opengl/scene_opengl.h b/plugins/scenes/opengl/scene_opengl.h --- a/plugins/scenes/opengl/scene_opengl.h +++ b/plugins/scenes/opengl/scene_opengl.h @@ -148,26 +148,38 @@ class OpenGLWindow final : public Scene::Window { public: - enum Leaf { ShadowLeaf = 0, DecorationLeaf, ContentLeaf, PreviousContentLeaf, LeafCount }; + enum Leaf { ShadowLeaf, DecorationLeaf, ContentLeaf, PreviousContentLeaf }; - struct LeafNode + struct RenderNode { - LeafNode() - : texture(nullptr), - firstVertex(0), - vertexCount(0), - opacity(1.0), - hasAlpha(false), - coordinateType(UnnormalizedCoordinates) + RenderNode() + : texture(nullptr) + , firstVertex(0) + , vertexCount(0) + , opacity(1.0) + , hasAlpha(false) + , coordinateType(UnnormalizedCoordinates) { } GLTexture *texture; + WindowQuadList quads; int firstVertex; int vertexCount; float opacity; bool hasAlpha; TextureCoordinateType coordinateType; + Leaf leafType; + }; + + struct RenderContext + { + QVector renderNodes; + int shadowOffset = 0; + int decorationOffset = 0; + int contentOffset = 0; + int previousContentOffset = 0; + int quadCount = 0; }; OpenGLWindow(Toplevel *toplevel, SceneOpenGL *scene); @@ -182,9 +194,7 @@ QMatrix4x4 modelViewProjectionMatrix(int mask, const WindowPaintData &data) const; QVector4D modulate(float opacity, float brightness) const; void setBlendEnabled(bool enabled); - void setupLeafNodes(LeafNode *nodes, const WindowQuadList *quads, const WindowPaintData &data); - void renderSubSurface(GLShader *shader, const QMatrix4x4 &mvp, const QMatrix4x4 &windowMatrix, - OpenGLWindowPixmap *pixmap, const QRegion ®ion, bool hardwareClipping); + void initializeRenderContext(RenderContext &context, const WindowPaintData &data); bool beginRenderWindow(int mask, const QRegion ®ion, WindowPaintData &data); void endRenderWindow(); bool bindTexture(); diff --git a/plugins/scenes/opengl/scene_opengl.cpp b/plugins/scenes/opengl/scene_opengl.cpp --- a/plugins/scenes/opengl/scene_opengl.cpp +++ b/plugins/scenes/opengl/scene_opengl.cpp @@ -1077,16 +1077,13 @@ { } -static SceneOpenGLTexture *s_frameTexture = nullptr; // Bind the window pixmap to an OpenGL texture. bool OpenGLWindow::bindTexture() { - s_frameTexture = nullptr; OpenGLWindowPixmap *pixmap = windowPixmap(); if (!pixmap) { return false; } - s_frameTexture = pixmap->texture(); if (pixmap->isDiscarded()) { return !pixmap->texture()->isNull(); } @@ -1155,28 +1152,14 @@ if (data.quads.isEmpty()) return false; - if (!bindTexture() || !s_frameTexture) { + if (!bindTexture()) { return false; } if (m_hardwareClipping) { glEnable(GL_SCISSOR_TEST); } - // Update the texture filter - if (waylandServer()) { - filter = Scene::ImageFilterGood; - s_frameTexture->setFilter(GL_LINEAR); - } else { - if (options->glSmoothScale() != 0 && - (mask & (Scene::PAINT_WINDOW_TRANSFORMED | Scene::PAINT_SCREEN_TRANSFORMED))) - filter = Scene::ImageFilterGood; - else - filter = Scene::ImageFilterFast; - - s_frameTexture->setFilter(filter == Scene::ImageFilterGood ? GL_LINEAR : GL_NEAREST); - } - const GLVertexAttrib attribs[] = { { VA_Position, 2, GL_FLOAT, offsetof(GLVertex2D, position) }, { VA_TexCoord, 2, GL_FLOAT, offsetof(GLVertex2D, texcoord) }, @@ -1245,40 +1228,137 @@ m_blendingEnabled = enabled; } -void OpenGLWindow::setupLeafNodes(LeafNode *nodes, const WindowQuadList *quads, const WindowPaintData &data) +void OpenGLWindow::initializeRenderContext(RenderContext &context, const WindowPaintData &data) { - if (!quads[ShadowLeaf].isEmpty()) { - nodes[ShadowLeaf].texture = static_cast(m_shadow)->shadowTexture(); - nodes[ShadowLeaf].opacity = data.opacity(); - nodes[ShadowLeaf].hasAlpha = true; - nodes[ShadowLeaf].coordinateType = NormalizedCoordinates; + QVector &renderNodes = context.renderNodes; + + int contentsNodeCount = 0; + for (const WindowQuad &quad : data.quads) { + if (quad.type() != WindowQuadContents) + continue; + contentsNodeCount = std::max(contentsNodeCount, quad.id() + 1); // Cheating. + } + + context.shadowOffset = 0; + context.decorationOffset = 1; + context.contentOffset = 2; + context.previousContentOffset = contentsNodeCount + 2; + context.quadCount = data.quads.count(); + + const int nodeCount = context.previousContentOffset + 1; + renderNodes.resize(nodeCount); + + for (const WindowQuad &quad : data.quads) { + switch (quad.type()) { + case WindowQuadShadow: + renderNodes[context.shadowOffset].quads << quad; + break; + + case WindowQuadDecoration: + renderNodes[context.decorationOffset].quads << quad; + break; + + case WindowQuadContents: + renderNodes[context.contentOffset + quad.id()].quads << quad; + break; + + default: + // Ignore window quad generated by effects. + break; + } + } + + if (!renderNodes[context.shadowOffset].quads.isEmpty()) { + SceneOpenGLShadow *shadow = static_cast(m_shadow); + renderNodes[context.shadowOffset].texture = shadow->shadowTexture(); + renderNodes[context.shadowOffset].opacity = data.opacity(); + renderNodes[context.shadowOffset].hasAlpha = true; + renderNodes[context.shadowOffset].coordinateType = NormalizedCoordinates; + renderNodes[context.shadowOffset].leafType = ShadowLeaf; } - if (!quads[DecorationLeaf].isEmpty()) { - nodes[DecorationLeaf].texture = getDecorationTexture(); - nodes[DecorationLeaf].opacity = data.opacity(); - nodes[DecorationLeaf].hasAlpha = true; - nodes[DecorationLeaf].coordinateType = UnnormalizedCoordinates; + if (!renderNodes[context.decorationOffset].quads.isEmpty()) { + renderNodes[context.decorationOffset].texture = getDecorationTexture(); + renderNodes[context.decorationOffset].opacity = data.opacity(); + renderNodes[context.decorationOffset].hasAlpha = true; + renderNodes[context.decorationOffset].coordinateType = UnnormalizedCoordinates; + renderNodes[context.decorationOffset].leafType = DecorationLeaf; } - nodes[ContentLeaf].texture = s_frameTexture; - nodes[ContentLeaf].hasAlpha = !isOpaque(); - // TODO: ARGB crsoofading is atm. a hack, playing on opacities for two dumb SrcOver operations - // Should be a shader + // FIXME: Cross-fading must be implemented in a shader. + float contentOpacity = data.opacity(); if (data.crossFadeProgress() != 1.0 && (data.opacity() < 0.95 || toplevel->hasAlpha())) { const float opacity = 1.0 - data.crossFadeProgress(); - nodes[ContentLeaf].opacity = data.opacity() * (1 - pow(opacity, 1.0f + 2.0f * data.opacity())); - } else { - nodes[ContentLeaf].opacity = data.opacity(); + contentOpacity *= 1 - pow(opacity, 1.0f + 2.0f * data.opacity()); + } + + // The main surface and all of its sub-surfaces form a tree. In order to initialize + // the render nodes for the window pixmaps we need to traverse the tree in the + // depth-first search manner. The id of content window quads corresponds to the time + // when we visited the corresponding window pixmap. The DFS traversal probably doesn't + // have a significant impact on performance. However, if that's the case, we could + // keep a cache of window pixmaps in the order in which they'll be rendered. + QStack stack; + stack.push(windowPixmap()); + + int i = 0; + + while (!stack.isEmpty()) { + OpenGLWindowPixmap *windowPixmap = static_cast(stack.pop()); + + // If it's an unmapped sub-surface, don't render it and all of its children. + if (!windowPixmap->isValid()) + continue; + + renderNodes[context.contentOffset + i].texture = windowPixmap->texture(); + renderNodes[context.contentOffset + i].hasAlpha = windowPixmap->hasAlphaChannel(); + renderNodes[context.contentOffset + i].opacity = contentOpacity; + renderNodes[context.contentOffset + i].coordinateType = UnnormalizedCoordinates; + renderNodes[context.contentOffset + i].leafType = ContentLeaf; + i++; + + const QVector children = windowPixmap->children(); + for (WindowPixmap *child : children) + stack.push(child); } - nodes[ContentLeaf].coordinateType = UnnormalizedCoordinates; + // Note that cross-fading is currently working properly only on X11. In order to make it + // work on Wayland, we have to render the current and the previous window pixmap trees in + // offscreen render targets, then use a cross-fading shader to blend those two layers. if (data.crossFadeProgress() != 1.0) { OpenGLWindowPixmap *previous = previousWindowPixmap(); - nodes[PreviousContentLeaf].texture = previous ? previous->texture() : nullptr; - nodes[PreviousContentLeaf].hasAlpha = !isOpaque(); - nodes[PreviousContentLeaf].opacity = data.opacity() * (1.0 - data.crossFadeProgress()); - nodes[PreviousContentLeaf].coordinateType = NormalizedCoordinates; + if (previous) { // TODO(vlad): Should cross-fading be disabled on Wayland? + const QRect &oldGeometry = previous->contentsRect(); + for (const WindowQuad &quad : qAsConst(renderNodes[context.contentOffset].quads)) { + // We need to create new window quads with normalized texture coordinates. + // Normal quads divide the x/y position by width/height. This would not work + // as the texture is larger than the visible content in case of a decorated + // Client resulting in garbage being shown. So we calculate the normalized + // texture coordinate in the Client's new content space and map it to the + // previous Client's content space. + WindowQuad newQuad(WindowQuadContents); + for (int i = 0; i < 4; ++i) { + const qreal xFactor = (quad[i].textureX() - toplevel->clientPos().x()) + / qreal(toplevel->clientSize().width()); + const qreal yFactor = (quad[i].textureY() - toplevel->clientPos().y()) + / qreal(toplevel->clientSize().height()); + const qreal u = (xFactor * oldGeometry.width() + oldGeometry.x()) + / qreal(previous->size().width()); + const qreal v = (yFactor * oldGeometry.height() + oldGeometry.y()) + / qreal(previous->size().height()); + newQuad[i] = WindowVertex(quad[i].x(), quad[i].y(), u, v); + } + renderNodes[context.previousContentOffset].quads.append(newQuad); + } + + renderNodes[context.previousContentOffset].texture = previous->texture(); + renderNodes[context.previousContentOffset].hasAlpha = previous->hasAlphaChannel(); + renderNodes[context.previousContentOffset].opacity = data.opacity() * (1.0 - data.crossFadeProgress()); + renderNodes[context.previousContentOffset].coordinateType = NormalizedCoordinates; + renderNodes[context.previousContentOffset].leafType = PreviousContentLeaf; + + context.quadCount += renderNodes[context.previousContentOffset].quads.count(); + } } } @@ -1306,35 +1386,6 @@ return scene->projectionMatrix() * mvMatrix; } -void OpenGLWindow::renderSubSurface(GLShader *shader, const QMatrix4x4 &mvp, const QMatrix4x4 &windowMatrix, OpenGLWindowPixmap *pixmap, const QRegion ®ion, bool hardwareClipping) -{ - QMatrix4x4 newWindowMatrix = windowMatrix; - newWindowMatrix.translate(pixmap->subSurface()->position().x(), pixmap->subSurface()->position().y()); - - qreal scale = 1.0; - if (pixmap->surface()) { - scale = pixmap->surface()->scale(); - } - - if (!pixmap->texture()->isNull()) { - setBlendEnabled(pixmap->buffer() && pixmap->buffer()->hasAlphaChannel()); - // render this texture - shader->setUniform(GLShader::ModelViewProjectionMatrix, mvp * newWindowMatrix); - auto texture = pixmap->texture(); - texture->bind(); - texture->render(region, QRect(0, 0, texture->width() / scale, texture->height() / scale), hardwareClipping); - 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), region, hardwareClipping); - } -} - void OpenGLWindow::performPaint(int mask, const QRegion ®ion, const WindowPaintData &_data) { WindowPaintData data = _data; @@ -1345,7 +1396,6 @@ const QMatrix4x4 modelViewProjection = modelViewProjectionMatrix(mask, data); const QMatrix4x4 mvpMatrix = modelViewProjection * windowMatrix; - bool useX11TextureClamp = false; GLShader *shader = data.shader; @@ -1382,76 +1432,30 @@ shader->setUniform(GLShader::Saturation, data.saturation()); - WindowQuadList quads[LeafCount]; - - // Split the quads into separate lists for each type - foreach (const WindowQuad &quad, data.quads) { - switch (quad.type()) { - case WindowQuadDecoration: - quads[DecorationLeaf].append(quad); - continue; - - case WindowQuadContents: - quads[ContentLeaf].append(quad); - continue; - - case WindowQuadShadow: - quads[ShadowLeaf].append(quad); - continue; - - default: - continue; - } - } - - if (data.crossFadeProgress() != 1.0) { - OpenGLWindowPixmap *previous = previousWindowPixmap(); - if (previous) { - const QRect &oldGeometry = previous->contentsRect(); - for (const WindowQuad &quad : quads[ContentLeaf]) { - // we need to create new window quads with normalize texture coordinates - // normal quads divide the x/y position by width/height. This would not work as the texture - // is larger than the visible content in case of a decorated Client resulting in garbage being shown. - // So we calculate the normalized texture coordinate in the Client's new content space and map it to - // the previous Client's content space. - WindowQuad newQuad(WindowQuadContents); - for (int i = 0; i < 4; ++i) { - const qreal xFactor = qreal(quad[i].textureX() - toplevel->clientPos().x())/qreal(toplevel->clientSize().width()); - const qreal yFactor = qreal(quad[i].textureY() - toplevel->clientPos().y())/qreal(toplevel->clientSize().height()); - WindowVertex vertex(quad[i].x(), quad[i].y(), - (xFactor * oldGeometry.width() + oldGeometry.x())/qreal(previous->size().width()), - (yFactor * oldGeometry.height() + oldGeometry.y())/qreal(previous->size().height())); - newQuad[i] = vertex; - } - quads[PreviousContentLeaf].append(newQuad); - } - } - } + RenderContext renderContext; + initializeRenderContext(renderContext, data); const bool indexedQuads = GLVertexBuffer::supportsIndexedQuads(); const GLenum primitiveType = indexedQuads ? GL_QUADS : GL_TRIANGLES; const int verticesPerQuad = indexedQuads ? 4 : 6; - const size_t size = verticesPerQuad * - (quads[0].count() + quads[1].count() + quads[2].count() + quads[3].count()) * sizeof(GLVertex2D); + const size_t size = verticesPerQuad * renderContext.quadCount * sizeof(GLVertex2D); GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer(); GLVertex2D *map = (GLVertex2D *) vbo->map(size); - LeafNode nodes[LeafCount]; - setupLeafNodes(nodes, quads, data); - - for (int i = 0, v = 0; i < LeafCount; i++) { - if (quads[i].isEmpty() || !nodes[i].texture) + for (int i = 0, v = 0; i < renderContext.renderNodes.count(); i++) { + RenderNode &renderNode = renderContext.renderNodes[i]; + if (renderNode.quads.isEmpty() || !renderNode.texture) continue; - nodes[i].firstVertex = v; - nodes[i].vertexCount = quads[i].count() * verticesPerQuad; + renderNode.firstVertex = v; + renderNode.vertexCount = renderNode.quads.count() * verticesPerQuad; - const QMatrix4x4 matrix = nodes[i].texture->matrix(nodes[i].coordinateType); + const QMatrix4x4 matrix = renderNode.texture->matrix(renderNode.coordinateType); - quads[i].makeInterleavedArrays(primitiveType, &map[v], matrix); - v += quads[i].count() * verticesPerQuad; + renderNode.quads.makeInterleavedArrays(primitiveType, &map[v], matrix); + v += renderNode.quads.count() * verticesPerQuad; } vbo->unmap(); @@ -1462,23 +1466,24 @@ float opacity = -1.0; - for (int i = 0; i < LeafCount; i++) { - if (nodes[i].vertexCount == 0) + for (int i = 0; i < renderContext.renderNodes.count(); i++) { + const RenderNode &renderNode = renderContext.renderNodes[i]; + if (renderNode.vertexCount == 0) continue; - setBlendEnabled(nodes[i].hasAlpha || nodes[i].opacity < 1.0); + setBlendEnabled(renderNode.hasAlpha || renderNode.opacity < 1.0); - if (opacity != nodes[i].opacity) { + if (opacity != renderNode.opacity) { shader->setUniform(GLShader::ModulationConstant, - modulate(nodes[i].opacity, data.brightness())); - opacity = nodes[i].opacity; + modulate(renderNode.opacity, data.brightness())); + opacity = renderNode.opacity; } - nodes[i].texture->setFilter(filter); - nodes[i].texture->setWrapMode(GL_CLAMP_TO_EDGE); - nodes[i].texture->bind(); + renderNode.texture->setFilter(filter); + renderNode.texture->setWrapMode(GL_CLAMP_TO_EDGE); + renderNode.texture->bind(); - if (i == ContentLeaf && useX11TextureClamp) { + if (renderNode.leafType == ContentLeaf && useX11TextureClamp) { // X11 windows are reparented to have their buffer in the middle of a larger texture // holding the frame window. // This code passes the texture geometry to the fragment shader @@ -1497,23 +1502,12 @@ shader->setUniform(GLShader::TextureClamp, QVector4D({0, 0, 1, 1})); } - vbo->draw(region, primitiveType, nodes[i].firstVertex, nodes[i].vertexCount, m_hardwareClipping); + vbo->draw(region, primitiveType, renderNode.firstVertex, + renderNode.vertexCount, m_hardwareClipping); } vbo->unbindArrays(); - // render sub-surfaces - auto wp = windowPixmap(); - const auto &children = wp ? wp->children() : QVector(); - const QPoint mainSurfaceOffset = bufferOffset(); - windowMatrix.translate(mainSurfaceOffset.x(), mainSurfaceOffset.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), region, m_hardwareClipping); - } - setBlendEnabled(false); if (!data.shader) @@ -1569,10 +1563,6 @@ bool OpenGLWindowPixmap::bind() { if (!m_texture->isNull()) { - // always call updateBuffer to get the sub-surface tree updated - if (subSurface().isNull() && !toplevel()->damage().isEmpty()) { - updateBuffer(); - } if (needsPixmapUpdate(this)) { m_texture->updateFromPixmap(this); // mipmaps need to be updated @@ -1587,11 +1577,6 @@ } 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(); } diff --git a/plugins/scenes/qpainter/scene_qpainter.h b/plugins/scenes/qpainter/scene_qpainter.h --- a/plugins/scenes/qpainter/scene_qpainter.h +++ b/plugins/scenes/qpainter/scene_qpainter.h @@ -91,9 +91,9 @@ explicit QPainterWindowPixmap(Scene::Window *window); ~QPainterWindowPixmap() override; void create() override; + void update() override; bool isValid() const override; - void updateBuffer() override; const QImage &image(); protected: diff --git a/plugins/scenes/qpainter/scene_qpainter.cpp b/plugins/scenes/qpainter/scene_qpainter.cpp --- a/plugins/scenes/qpainter/scene_qpainter.cpp +++ b/plugins/scenes/qpainter/scene_qpainter.cpp @@ -271,10 +271,7 @@ if (!pixmap || !pixmap->isValid()) { return; } - if (!toplevel->damage().isEmpty()) { - pixmap->updateBuffer(); - toplevel->resetDamage(); - } + toplevel->resetDamage(); QPainter *scenePainter = m_scene->scenePainter(); QPainter *painter = scenePainter; @@ -451,10 +448,10 @@ return new QPainterWindowPixmap(subSurface, this); } -void QPainterWindowPixmap::updateBuffer() +void QPainterWindowPixmap::update() { const auto oldBuffer = buffer(); - WindowPixmap::updateBuffer(); + WindowPixmap::update(); const auto &b = buffer(); if (!surface()) { // That's an internal client. diff --git a/scene.h b/scene.h --- a/scene.h +++ b/scene.h @@ -332,6 +332,8 @@ bool isVisible() const; // is the window fully opaque bool isOpaque() const; + // is the window shaded + bool isShaded() const; // shape of the window QRegion bufferShape() const; QRegion clientShape() const; @@ -347,6 +349,7 @@ void referencePreviousPixmap(); void unreferencePreviousPixmap(); void invalidateQuadsCache(); + void preprocess(); protected: WindowQuadList makeDecorationQuads(const QRect *rects, const QRegion ®ion, qreal textureScale = 1.0) const; WindowQuadList makeContentsQuads() const; @@ -365,8 +368,8 @@ * * @return The WindowPixmap casted to T* or @c NULL if there is no valid window pixmap. */ - template T *windowPixmap(); - template T *previousWindowPixmap(); + template T *windowPixmap() const; + template T *previousWindowPixmap() const; /** * @brief Factory method to create a WindowPixmap. * @@ -418,10 +421,18 @@ * native pixmap to the rendering format. */ virtual void create(); + /** + * @brief Recursively updates the mapping between the WindowPixmap and the buffer. + */ + virtual void update(); /** * @return @c true if the pixmap has been created and is valid, @c false otherwise */ virtual bool isValid() const; + /** + * Returns @c true if this is the root window pixmap; otherwise returns @c false. + */ + bool isRoot() const; /** * @return The native X11 pixmap handle */ @@ -447,10 +458,38 @@ * @see isDiscarded */ void markAsDiscarded(); + /** + * Returns the position of the WindowPixmap relative to the upper left corner of the parent. + * + * This method returns the position of the WindowPixmap relative to the upper left corner + * of the window pixmap if parent() is @c null. + * + * The upper left corner of the parent window pixmap corresponds to (0, 0). + */ + QPoint position() const; + /** + * Returns the position of the WindowPixmap relative to the upper left corner of the window + * frame. Note that position() returns the position relative to the parent WindowPixmap. + * + * The upper left corner of the window frame corresponds to (0, 0). + */ + QPoint framePosition() const; /** * The size of the pixmap. */ const QSize &size() const; + /** + * Returns the device pixel ratio for the attached buffer. This is the ratio between device + * pixels and logical pixels. + */ + qreal scale() const; + /** + * Returns the region that specifies the area inside the attached buffer with the actual + * client's contents. + * + * The upper left corner of the attached buffer corresponds to (0, 0). + */ + QRegion shape() const; /** * The geometry of the Client's content inside the pixmap. In case of a decorated Client the * pixmap also contains the decoration which is not rendered into this pixmap, though. This @@ -462,6 +501,10 @@ * Note: the Toplevel can change over the lifetime of the WindowPixmap in case the Toplevel is copied to Deleted. */ Toplevel *toplevel() const; + /** + * Returns @c true if the attached buffer has an alpha channel; otherwise returns @c false. + */ + bool hasAlphaChannel() const; /** * @returns the parent WindowPixmap in the sub-surface tree @@ -498,12 +541,6 @@ */ Scene::Window *window(); - /** - * Should be called by the implementing subclasses when the Wayland Buffer changed and needs - * updating. - */ - virtual void updateBuffer(); - /** * Sets the sub-surface tree to @p children. */ @@ -634,15 +671,8 @@ template inline -T* Scene::Window::windowPixmap() +T *Scene::Window::windowPixmap() const { - if (m_currentPixmap.isNull()) { - m_currentPixmap.reset(createWindowPixmap()); - } - if (m_currentPixmap->isValid()) { - return static_cast(m_currentPixmap.data()); - } - m_currentPixmap->create(); if (m_currentPixmap->isValid()) { return static_cast(m_currentPixmap.data()); } else { @@ -652,7 +682,7 @@ template inline -T* Scene::Window::previousWindowPixmap() +T *Scene::Window::previousWindowPixmap() const { return static_cast(m_previousPixmap.data()); } diff --git a/scene.cpp b/scene.cpp --- a/scene.cpp +++ b/scene.cpp @@ -77,6 +77,7 @@ #include "overlaywindow.h" #include "screens.h" #include "shadow.h" +#include "subsurfacemonitor.h" #include "wayland_server.h" #include "thumbnailitem.h" @@ -211,6 +212,9 @@ foreach (Window * w, stacking_order) { // bottom to top Toplevel* topw = w->window(); + // Let the scene window update the window pixmap tree. + w->preprocess(); + // Reset the repaint_region. // This has to be done here because many effects schedule a repaint for // the next frame within Effects::prePaintWindow. @@ -266,6 +270,9 @@ data.paint = region; data.paint |= toplevel->repaints(); + // Let the scene window update the window pixmap tree. + window->preprocess(); + // Reset the repaint_region. // This has to be done here because many effects schedule a repaint for // the next frame within Effects::prePaintWindow. @@ -389,25 +396,40 @@ Q_ASSERT(!m_windows.contains(c)); Scene::Window *w = createWindow(c); m_windows[ c ] = w; + + auto discardPixmap = [w]() { w->discardPixmap(); }; + auto discardQuads = [w]() { w->invalidateQuadsCache(); }; + connect(c, SIGNAL(geometryShapeChanged(KWin::Toplevel*,QRect)), SLOT(windowGeometryShapeChanged(KWin::Toplevel*))); connect(c, SIGNAL(windowClosed(KWin::Toplevel*,KWin::Deleted*)), SLOT(windowClosed(KWin::Toplevel*,KWin::Deleted*))); - //A change of scale won't affect the geometry in compositor co-ordinates, but will affect the window quads. if (c->surface()) { - connect(c->surface(), &KWayland::Server::SurfaceInterface::scaleChanged, this, std::bind(&Scene::windowGeometryShapeChanged, this, c)); + // We generate window quads for sub-surfaces so it's quite important to discard + // the pixmap tree and cached window quads when the sub-surface tree is changed. + SubSurfaceMonitor *monitor = new SubSurfaceMonitor(c->surface(), this); + + // TODO(vlad): Is there a more efficient way to manage window pixmap trees? + connect(monitor, &SubSurfaceMonitor::subSurfaceAdded, this, discardPixmap); + connect(monitor, &SubSurfaceMonitor::subSurfaceRemoved, this, discardPixmap); + connect(monitor, &SubSurfaceMonitor::subSurfaceResized, this, discardPixmap); + connect(monitor, &SubSurfaceMonitor::subSurfaceMapped, this, discardPixmap); + connect(monitor, &SubSurfaceMonitor::subSurfaceUnmapped, this, discardPixmap); + + connect(monitor, &SubSurfaceMonitor::subSurfaceAdded, this, discardQuads); + connect(monitor, &SubSurfaceMonitor::subSurfaceRemoved, this, discardQuads); + connect(monitor, &SubSurfaceMonitor::subSurfaceMoved, this, discardQuads); + connect(monitor, &SubSurfaceMonitor::subSurfaceResized, this, discardQuads); + connect(monitor, &SubSurfaceMonitor::subSurfaceMapped, this, discardQuads); + connect(monitor, &SubSurfaceMonitor::subSurfaceUnmapped, this, discardQuads); + + connect(c->surface(), &KWayland::Server::SurfaceInterface::scaleChanged, this, discardQuads); } - connect(c, &Toplevel::screenScaleChanged, this, - [this, c] { - windowGeometryShapeChanged(c); - } - ); + + connect(c, &Toplevel::screenScaleChanged, this, discardQuads); + connect(c, &Toplevel::shadowChanged, this, discardQuads); + c->effectWindow()->setSceneWindow(w); c->updateShadow(); w->updateShadow(c->shadow()); - connect(c, &Toplevel::shadowChanged, this, - [w] { - w->invalidateQuadsCache(); - } - ); } void Scene::removeToplevel(Toplevel *toplevel) @@ -732,7 +754,9 @@ if (m_currentPixmap.isNull()) { m_currentPixmap.reset(createWindowPixmap()); } - if (!m_currentPixmap->isValid()) { + if (m_currentPixmap->isValid()) { + m_currentPixmap->update(); + } else { m_currentPixmap->create(); } } @@ -779,11 +803,8 @@ QRegion Scene::Window::clientShape() const { - if (AbstractClient *client = qobject_cast(toplevel)) { - if (client->isShade()) { - return QRegion(); - } - } + if (isShaded()) + return QRegion(); const QRegion shape = bufferShape(); const QMargins bufferMargins = toplevel->bufferMargins(); @@ -825,6 +846,13 @@ return toplevel->opacity() == 1.0 && !toplevel->hasAlpha(); } +bool Scene::Window::isShaded() const +{ + if (AbstractClient *client = qobject_cast(toplevel)) + return client->isShade(); + return false; +} + bool Scene::Window::isPaintingEnabled() const { return !disable_painting; @@ -869,7 +897,11 @@ if (cached_quad_list != nullptr && !force) return *cached_quad_list; - WindowQuadList ret = makeContentsQuads(); + WindowQuadList ret; + + if (!isShaded()) { + ret += makeContentsQuads(); + } if (!toplevel->frameMargins().isNull()) { AbstractClient *client = dynamic_cast(toplevel); @@ -969,36 +1001,66 @@ WindowQuadList Scene::Window::makeContentsQuads() const { - const QRegion contentsRegion = clientShape(); - if (contentsRegion.isEmpty()) { - return WindowQuadList(); - } + // TODO(vlad): What about the case where we need to build window quads for a deleted + // window? Presumably, the current window will be invalid so no window quads will be + // generated. Is it okay? - const QPointF geometryOffset = bufferOffset(); - const qreal textureScale = toplevel->bufferScale(); + WindowPixmap *currentPixmap = windowPixmap(); + if (!currentPixmap) + return WindowQuadList(); WindowQuadList quads; - quads.reserve(contentsRegion.rectCount()); + int id = 0; - for (const QRectF &rect : contentsRegion) { - WindowQuad quad(WindowQuadContents); + // We need to assign an id to each generated window quad in order to be able to match + // a list of window quads against a particular window pixmap. We traverse the window + // pixmap tree in the depth-first search manner and assign an id to each window quad. + // The id is the time when we visited the window pixmap. - const qreal x0 = rect.left() + geometryOffset.x(); - const qreal y0 = rect.top() + geometryOffset.y(); - const qreal x1 = rect.right() + geometryOffset.x(); - const qreal y1 = rect.bottom() + geometryOffset.y(); + QStack stack; + stack.push(currentPixmap); - const qreal u0 = rect.left() * textureScale; - const qreal v0 = rect.top() * textureScale; - const qreal u1 = rect.right() * textureScale; - const qreal v1 = rect.bottom() * textureScale; + while (!stack.isEmpty()) { + WindowPixmap *windowPixmap = stack.pop(); - quad[0] = WindowVertex(QPointF(x0, y0), QPointF(u0, v0)); - quad[1] = WindowVertex(QPointF(x1, y0), QPointF(u1, v0)); - quad[2] = WindowVertex(QPointF(x1, y1), QPointF(u1, v1)); - quad[3] = WindowVertex(QPointF(x0, y1), QPointF(u0, v1)); + // If it's an unmapped sub-surface, don't generate window quads for it. + if (!windowPixmap->isValid()) + continue; - quads << quad; + const QRegion region = windowPixmap->shape(); + const QPoint position = windowPixmap->framePosition(); + const qreal scale = windowPixmap->scale(); + const int quadId = id++; + + for (const QRectF &rect : region) { + // Note that the window quad id is not unique if the window is shaped, i.e. the + // region contains more than just one rectangle. We assume that the "source" quad + // had been subdivided. + WindowQuad quad(WindowQuadContents, quadId); + + const qreal x0 = rect.left() + position.x(); + const qreal y0 = rect.top() + position.y(); + const qreal x1 = rect.right() + position.x(); + const qreal y1 = rect.bottom() + position.y(); + + const qreal u0 = rect.left() * scale; + const qreal v0 = rect.top() * scale; + const qreal u1 = rect.right() * scale; + const qreal v1 = rect.bottom() * scale; + + quad[0] = WindowVertex(QPointF(x0, y0), QPointF(u0, v0)); + quad[1] = WindowVertex(QPointF(x1, y0), QPointF(u1, v0)); + quad[2] = WindowVertex(QPointF(x1, y1), QPointF(u1, v1)); + quad[3] = WindowVertex(QPointF(x0, y1), QPointF(u0, v1)); + + quads << quad; + } + + // Push the child window pixmaps onto the stack, remember we're visiting the pixmaps + // in the depth-first search manner. + const auto children = windowPixmap->children(); + for (WindowPixmap *child : children) + stack.push(child); } return quads; @@ -1018,6 +1080,16 @@ m_shadow = shadow; } +void Scene::Window::preprocess() +{ + // The tracked damage will be reset after the scene is done with copying buffer's data. + // Note that we have to be prepared for the case where no damage has occurred since kwin + // core may discard the current window pixmap at any moment. + if (!m_currentPixmap || !window()->damage().isEmpty()) { + updatePixmap(); + } +} + //**************************************** // WindowPixmap //**************************************** @@ -1059,8 +1131,8 @@ // always update from Buffer on Wayland, don't try using XPixmap if (kwinApp()->shouldUseWaylandForCompositing()) { // use Buffer - updateBuffer(); - if ((m_buffer || !m_fbo.isNull()) && m_subSurface.isNull()) { + update(); + if (!isRoot() && isValid()) { m_window->unreferencePreviousPixmap(); } return; @@ -1094,21 +1166,7 @@ m_window->unreferencePreviousPixmap(); } -WindowPixmap *WindowPixmap::createChild(const QPointer &subSurface) -{ - Q_UNUSED(subSurface) - return nullptr; -} - -bool WindowPixmap::isValid() const -{ - if (!m_buffer.isNull() || !m_fbo.isNull() || !m_internalImage.isNull()) { - return true; - } - return m_pixmap != XCB_PIXMAP_NONE; -} - -void WindowPixmap::updateBuffer() +void WindowPixmap::update() { using namespace KWayland::Server; if (SurfaceInterface *s = surface()) { @@ -1123,7 +1181,7 @@ 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(); + (*it)->update(); oldTree.erase(it); } else { WindowPixmap *p = createChild(subSurface); @@ -1167,6 +1225,25 @@ } } +WindowPixmap *WindowPixmap::createChild(const QPointer &subSurface) +{ + Q_UNUSED(subSurface) + return nullptr; +} + +bool WindowPixmap::isValid() const +{ + if (!m_buffer.isNull() || !m_fbo.isNull() || !m_internalImage.isNull()) { + return true; + } + return m_pixmap != XCB_PIXMAP_NONE; +} + +bool WindowPixmap::isRoot() const +{ + return !m_parent; +} + KWayland::Server::SurfaceInterface *WindowPixmap::surface() const { if (!m_subSurface.isNull()) { @@ -1176,6 +1253,39 @@ } } +QPoint WindowPixmap::position() const +{ + if (subSurface()) + return subSurface()->position(); + return m_window->bufferOffset(); +} + +QPoint WindowPixmap::framePosition() const +{ + return position() + (m_parent ? m_parent->framePosition() : QPoint()); +} + +qreal WindowPixmap::scale() const +{ + if (surface()) + return surface()->scale(); + return toplevel()->bufferScale(); +} + +QRegion WindowPixmap::shape() const +{ + if (subSurface()) + return QRect(QPoint(), surface()->size()); + return m_window->clientShape(); +} + +bool WindowPixmap::hasAlphaChannel() const +{ + if (buffer()) + return buffer()->hasAlphaChannel(); + return toplevel()->hasAlpha(); +} + //**************************************** // Scene::EffectFrame //**************************************** diff --git a/subsurfacemonitor.h b/subsurfacemonitor.h new file mode 100644 --- /dev/null +++ b/subsurfacemonitor.h @@ -0,0 +1,84 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2020 Vlad Zahorodnii + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ + +#pragma once + +#include + +namespace KWayland +{ +namespace Server +{ +class SurfaceInterface; +class SubSurfaceInterface; +} +} + +namespace KWin +{ + +/** + * The SubSurfaceMonitor class provides a convenient way for monitoring changes in + * sub-surface trees, e.g. addition or removal of sub-surfaces, etc. + */ +class SubSurfaceMonitor : public QObject +{ + Q_OBJECT + +public: + /** + * Constructs a SubSurfaceTreeMonitor with the given @a surface and @a parent. + */ + SubSurfaceMonitor(KWayland::Server::SurfaceInterface *surface, QObject *parent); + +Q_SIGNALS: + /** + * This signal is emitted when a new sub-surface has been added to the tree. + */ + void subSurfaceAdded(); + /** + * This signal is emitted when a sub-surface has been removed from the tree. + */ + void subSurfaceRemoved(); + /** + * This signal is emitted when a sub-surface has been moved relative to its parent. + */ + void subSurfaceMoved(); + /** + * This signal is emitted when a sub-surface has been resized. + */ + void subSurfaceResized(); + /** + * This signal is emitted when a sub-surface is mapped. + */ + void subSurfaceMapped(); + /** + * This signal is emitted when a sub-surface is unmapped. + */ + void subSurfaceUnmapped(); + +private: + void registerSubSurface(KWayland::Server::SubSurfaceInterface *subSurface); + void unregisterSubSurface(KWayland::Server::SubSurfaceInterface *subSurface); + void registerSurface(KWayland::Server::SurfaceInterface *surface); + void unregisterSurface(KWayland::Server::SurfaceInterface *surface); +}; + +} // namespace KWin diff --git a/subsurfacemonitor.cpp b/subsurfacemonitor.cpp new file mode 100644 --- /dev/null +++ b/subsurfacemonitor.cpp @@ -0,0 +1,99 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2020 Vlad Zahorodnii + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ + +#include "subsurfacemonitor.h" + +#include +#include + +using namespace KWayland::Server; + +namespace KWin +{ + +SubSurfaceMonitor::SubSurfaceMonitor(SurfaceInterface *surface, QObject *parent) + : QObject(parent) +{ + registerSurface(surface); +} + +void SubSurfaceMonitor::registerSubSurface(SubSurfaceInterface *subSurface) +{ + SurfaceInterface *surface = subSurface->surface(); + + connect(subSurface, &SubSurfaceInterface::positionChanged, + this, &SubSurfaceMonitor::subSurfaceMoved); + connect(surface, &SurfaceInterface::sizeChanged, + this, &SubSurfaceMonitor::subSurfaceResized); +#if 0 // Add SurfaceInterface::mapped() + connect(surface, &SurfaceInterface::mapped, + this, &SubSurfaceMonitor::subSurfaceMapped); +#endif + connect(surface, &SurfaceInterface::unmapped, + this, &SubSurfaceMonitor::subSurfaceUnmapped); + + registerSurface(surface); +} + +void SubSurfaceMonitor::unregisterSubSurface(SubSurfaceInterface *subSurface) +{ + SurfaceInterface *surface = subSurface->surface(); + if (!surface) + return; + + disconnect(subSurface, &SubSurfaceInterface::positionChanged, + this, &SubSurfaceMonitor::subSurfaceMoved); + disconnect(surface, &SurfaceInterface::sizeChanged, + this, &SubSurfaceMonitor::subSurfaceResized); +#if 0 // Add SurfaceInterface::mapped() + disconnect(surface, &SurfaceInterface::mapped, + this, &SubSurfaceMonitor::subSurfaceMapped); +#endif + disconnect(surface, &SurfaceInterface::unmapped, + this, &SubSurfaceMonitor::subSurfaceUnmapped); + + unregisterSurface(surface); +} + +void SubSurfaceMonitor::registerSurface(SurfaceInterface *surface) +{ + connect(surface, &SurfaceInterface::childSubSurfaceAdded, + this, &SubSurfaceMonitor::subSurfaceAdded); + connect(surface, &SurfaceInterface::childSubSurfaceRemoved, + this, &SubSurfaceMonitor::subSurfaceRemoved); + connect(surface, &SurfaceInterface::childSubSurfaceAdded, + this, &SubSurfaceMonitor::registerSubSurface); + connect(surface, &SurfaceInterface::childSubSurfaceRemoved, + this, &SubSurfaceMonitor::unregisterSubSurface); +} + +void SubSurfaceMonitor::unregisterSurface(SurfaceInterface *surface) +{ + disconnect(surface, &SurfaceInterface::childSubSurfaceAdded, + this, &SubSurfaceMonitor::subSurfaceAdded); + disconnect(surface, &SurfaceInterface::childSubSurfaceRemoved, + this, &SubSurfaceMonitor::subSurfaceRemoved); + disconnect(surface, &SurfaceInterface::childSubSurfaceAdded, + this, &SubSurfaceMonitor::registerSubSurface); + disconnect(surface, &SurfaceInterface::childSubSurfaceRemoved, + this, &SubSurfaceMonitor::unregisterSubSurface); +} + +} // namespace KWin