diff --git a/src/decorationitem.cpp b/src/decorationitem.cpp index 73aa4313d9..ec1fb00ffc 100644 --- a/src/decorationitem.cpp +++ b/src/decorationitem.cpp @@ -14,6 +14,8 @@ #include "scene.h" #include "utils.h" +#include + #include #include @@ -190,78 +192,75 @@ DecorationRenderer *DecorationItem::renderer() const return m_renderer.data(); } +WindowQuad buildQuad(const QRect &partRect, const QPoint &textureOffset, + const qreal devicePixelRatio, bool rotated) +{ + const QRect &r = partRect; + const int p = DecorationRenderer::TexturePad; + + const int x0 = r.x(); + const int y0 = r.y(); + const int x1 = r.x() + r.width(); + const int y1 = r.y() + r.height(); + + int u0 = textureOffset.x() + p; + int v0 = textureOffset.y() + p; + int u1 = textureOffset.x() + p + (r.width() * devicePixelRatio); + int v1 = textureOffset.y() + p + (r.height() * devicePixelRatio); + + if (rotated) { + u0 = textureOffset.x() + p; + v0 = textureOffset.y() + p + (r.width() * devicePixelRatio); + u1 = textureOffset.x() + p + (r.height() * devicePixelRatio); + v1 = textureOffset.y() + p; + } + + WindowQuad quad; + quad[0] = WindowVertex(x0, y0, u0, v0); // Top-left + quad[1] = WindowVertex(x1, y0, u1, v0); // Top-right + quad[2] = WindowVertex(x1, y1, u1, v1); // Bottom-right + quad[3] = WindowVertex(x0, y1, u0, v1); // Bottom-left + return quad; +} + WindowQuadList DecorationItem::buildQuads() const { if (m_window->frameMargins().isNull()) { return WindowQuadList(); } - QRect rects[4]; + QRect left, top, right, bottom; + const qreal devicePixelRatio = m_renderer->devicePixelRatio(); + const int texturePad = DecorationRenderer::TexturePad; if (const AbstractClient *client = qobject_cast(m_window)) { - client->layoutDecorationRects(rects[0], rects[1], rects[2], rects[3]); + client->layoutDecorationRects(left, top, right, bottom); } else if (const Deleted *deleted = qobject_cast(m_window)) { - deleted->layoutDecorationRects(rects[0], rects[1], rects[2], rects[3]); + deleted->layoutDecorationRects(left, top, right, bottom); } - const qreal textureScale = m_renderer->devicePixelRatio(); - const int padding = 1; + const int topHeight = std::ceil(top.height() * devicePixelRatio); + const int bottomHeight = std::ceil(bottom.height() * devicePixelRatio); + const int leftWidth = std::ceil(left.width() * devicePixelRatio); - const QPoint topSpritePosition(padding, padding); - const QPoint bottomSpritePosition(padding, topSpritePosition.y() + rects[1].height() + 2 * padding); - const QPoint leftSpritePosition(bottomSpritePosition.y() + rects[3].height() + 2 * padding, padding); - const QPoint rightSpritePosition(leftSpritePosition.x() + rects[0].width() + 2 * padding, padding); - - const QPoint offsets[4] = { - QPoint(-rects[0].x(), -rects[0].y()) + leftSpritePosition, - QPoint(-rects[1].x(), -rects[1].y()) + topSpritePosition, - QPoint(-rects[2].x(), -rects[2].y()) + rightSpritePosition, - QPoint(-rects[3].x(), -rects[3].y()) + bottomSpritePosition, - }; - - const Qt::Orientation orientations[4] = { - Qt::Vertical, // Left - Qt::Horizontal, // Top - Qt::Vertical, // Right - Qt::Horizontal, // Bottom - }; + const QPoint topPosition(0, 0); + const QPoint bottomPosition(0, topPosition.y() + topHeight + (2 * texturePad)); + const QPoint leftPosition(0, bottomPosition.y() + bottomHeight + (2 * texturePad)); + const QPoint rightPosition(0, leftPosition.y() + leftWidth + (2 * texturePad)); WindowQuadList list; - list.reserve(4); - - for (int i = 0; i < 4; ++i) { - const QRect &r = rects[i]; - if (!r.isValid()) { - continue; - } - - const int x0 = r.x(); - const int y0 = r.y(); - const int x1 = r.x() + r.width(); - const int y1 = r.y() + r.height(); - - const int u0 = (x0 + offsets[i].x()) * textureScale; - const int v0 = (y0 + offsets[i].y()) * textureScale; - const int u1 = (x1 + offsets[i].x()) * textureScale; - const int v1 = (y1 + offsets[i].y()) * textureScale; - - WindowQuad quad; - - if (orientations[i] == Qt::Vertical) { - quad[0] = WindowVertex(x0, y0, v0, u0); // Top-left - quad[1] = WindowVertex(x1, y0, v0, u1); // Top-right - quad[2] = WindowVertex(x1, y1, v1, u1); // Bottom-right - quad[3] = WindowVertex(x0, y1, v1, u0); // Bottom-left - } else { - quad[0] = WindowVertex(x0, y0, u0, v0); // Top-left - quad[1] = WindowVertex(x1, y0, u1, v0); // Top-right - quad[2] = WindowVertex(x1, y1, u1, v1); // Bottom-right - quad[3] = WindowVertex(x0, y1, u0, v1); // Bottom-left - } - - list.append(quad); + if (left.isValid()) { + list.append(buildQuad(left, leftPosition, devicePixelRatio, true)); + } + if (top.isValid()) { + list.append(buildQuad(top, topPosition, devicePixelRatio, false)); + } + if (right.isValid()) { + list.append(buildQuad(right, rightPosition, devicePixelRatio, true)); + } + if (bottom.isValid()) { + list.append(buildQuad(bottom, bottomPosition, devicePixelRatio, false)); } - return list; } diff --git a/src/decorationitem.h b/src/decorationitem.h index 24ed389734..21d4da01cb 100644 --- a/src/decorationitem.h +++ b/src/decorationitem.h @@ -41,6 +41,9 @@ public: qreal devicePixelRatio() const; void setDevicePixelRatio(qreal dpr); + // Reserve some space for padding. We pad decoration parts to avoid texture bleeding. + static const int TexturePad = 1; + Q_SIGNALS: void damaged(const QRegion ®ion); diff --git a/src/scenes/opengl/scene_opengl.cpp b/src/scenes/opengl/scene_opengl.cpp index 764778b352..cc476e87eb 100644 --- a/src/scenes/opengl/scene_opengl.cpp +++ b/src/scenes/opengl/scene_opengl.cpp @@ -1530,31 +1530,6 @@ SceneOpenGLDecorationRenderer::~SceneOpenGLDecorationRenderer() } } -// Rotates the given source rect 90° counter-clockwise, -// and flips it vertically -static QImage rotate(const QImage &srcImage, const QRect &srcRect) -{ - auto dpr = srcImage.devicePixelRatio(); - QImage image(srcRect.height() * dpr, srcRect.width() * dpr, srcImage.format()); - image.setDevicePixelRatio(dpr); - const QPoint srcPoint(srcRect.x() * dpr, srcRect.y() * dpr); - - const uint32_t *src = reinterpret_cast(srcImage.bits()); - uint32_t *dst = reinterpret_cast(image.bits()); - - for (int x = 0; x < image.width(); x++) { - const uint32_t *s = src + (srcPoint.y() + x) * srcImage.width() + srcPoint.x(); - uint32_t *d = dst + x; - - for (int y = 0; y < image.height(); y++) { - *d = s[y]; - d += image.width(); - } - } - - return image; -} - static void clamp_row(int left, int width, int right, const uint32_t *src, uint32_t *dest) { std::fill_n(dest, left, *src); @@ -1620,71 +1595,94 @@ void SceneOpenGLDecorationRenderer::render(const QRegion ®ion) QRect left, top, right, bottom; client()->client()->layoutDecorationRects(left, top, right, bottom); - // We pad each part in the decoration atlas in order to avoid texture bleeding. - const int padding = 1; + const int topHeight = std::ceil(top.height() * devicePixelRatio()); + const int bottomHeight = std::ceil(bottom.height() * devicePixelRatio()); + const int leftWidth = std::ceil(left.width() * devicePixelRatio()); - auto renderPart = [=](const QRect &geo, const QRect &partRect, const QPoint &position, bool rotated = false) { - if (!geo.isValid()) { - return; - } + const QPoint topPosition(0, 0); + const QPoint bottomPosition(0, topPosition.y() + topHeight + (2 * TexturePad)); + const QPoint leftPosition(0, bottomPosition.y() + bottomHeight + (2 * TexturePad)); + const QPoint rightPosition(0, leftPosition.y() + leftWidth + (2 * TexturePad)); - QRect rect = geo; + const QRect dirtyRect = region.boundingRect(); - // We allow partial decoration updates and it might just so happen that the dirty region - // is completely contained inside the decoration part, i.e. the dirty region doesn't touch - // any of the decoration's edges. In that case, we should **not** pad the dirty region. - if (rect.left() == partRect.left()) { - rect.setLeft(rect.left() - padding); - } - if (rect.top() == partRect.top()) { - rect.setTop(rect.top() - padding); - } - if (rect.right() == partRect.right()) { - rect.setRight(rect.right() + padding); - } - if (rect.bottom() == partRect.bottom()) { - rect.setBottom(rect.bottom() + padding); - } + renderPart(top.intersected(dirtyRect), top, topPosition); + renderPart(bottom.intersected(dirtyRect), bottom, bottomPosition); + renderPart(left.intersected(dirtyRect), left, leftPosition, true); + renderPart(right.intersected(dirtyRect), right, rightPosition, true); +} - QRect viewport = geo.translated(-rect.x(), -rect.y()); +void SceneOpenGLDecorationRenderer::renderPart(const QRect &rect, const QRect &partRect, + const QPoint &textureOffset, bool rotated) +{ + if (!rect.isValid()) { + return; + } + // We allow partial decoration updates and it might just so happen that the + // dirty region is completely contained inside the decoration part, i.e. + // the dirty region doesn't touch any of the decoration's edges. In that + // case, we should **not** pad the dirty region. + const QMargins padding = texturePadForPart(rect, partRect); + int verticalPadding = padding.top() + padding.bottom(); + int horizontalPadding = padding.left() + padding.right(); - QImage image(rect.size() * devicePixelRatio(), QImage::Format_ARGB32_Premultiplied); - image.setDevicePixelRatio(devicePixelRatio()); - image.fill(Qt::transparent); + QSize imageSize = rect.size() * devicePixelRatio(); + if (rotated) { + imageSize = QSize(imageSize.height(), imageSize.width()); + } + QSize paddedImageSize = imageSize; + paddedImageSize.rheight() += verticalPadding; + paddedImageSize.rwidth() += horizontalPadding; + QImage image(paddedImageSize, QImage::Format_ARGB32_Premultiplied); + image.setDevicePixelRatio(devicePixelRatio()); + image.fill(Qt::transparent); - QPainter painter(&image); - painter.setRenderHint(QPainter::Antialiasing); - painter.setViewport(QRect(viewport.topLeft(), viewport.size() * devicePixelRatio())); - painter.setWindow(QRect(geo.topLeft(), geo.size() * qPainterEffectiveDevicePixelRatio(&painter))); - painter.setClipRect(geo); - renderToPainter(&painter, geo); - painter.end(); + QRect padClip = QRect(padding.left(), padding.top(), imageSize.width(), imageSize.height()); + QPainter painter(&image); + const qreal inverseScale = 1.0 / devicePixelRatio(); + painter.scale(inverseScale, inverseScale); + painter.setRenderHint(QPainter::Antialiasing); + painter.setClipRect(padClip); + painter.translate(padding.left(), padding.top()); + if (rotated) { + painter.translate(0, imageSize.height()); + painter.rotate(-90); + } + painter.scale(devicePixelRatio(), devicePixelRatio()); + painter.translate(-rect.topLeft()); + renderToPainter(&painter, rect); + painter.end(); - const QRect viewportScaled(viewport.topLeft() * devicePixelRatio(), viewport.size() * devicePixelRatio()); - const bool isIntegerScaling = qFuzzyCompare(devicePixelRatio(), std::ceil(devicePixelRatio())); - clamp(image, isIntegerScaling ? viewportScaled : viewportScaled.marginsRemoved({1, 1, 1, 1})); + // fill padding pixels by copying from the neighbour row + clamp(image, padClip); - if (rotated) { - // TODO: get this done directly when rendering to the image - image = rotate(image, QRect(QPoint(), rect.size())); - viewport = QRect(viewport.y(), viewport.x(), viewport.height(), viewport.width()); - } + QPoint dirtyOffset = (rect.topLeft() - partRect.topLeft()) * devicePixelRatio(); + if (padding.top() == 0) { + dirtyOffset.ry() += TexturePad; + } + if (padding.left() == 0) { + dirtyOffset.rx() += TexturePad; + } + m_texture->update(image, textureOffset + dirtyOffset); +} - const QPoint dirtyOffset = geo.topLeft() - partRect.topLeft(); - m_texture->update(image, (position + dirtyOffset - viewport.topLeft()) * image.devicePixelRatio()); - }; - - const QRect geometry = region.boundingRect(); - - const QPoint topPosition(padding, padding); - const QPoint bottomPosition(padding, topPosition.y() + top.height() + 2 * padding); - const QPoint leftPosition(padding, bottomPosition.y() + bottom.height() + 2 * padding); - const QPoint rightPosition(padding, leftPosition.y() + left.width() + 2 * padding); - - renderPart(left.intersected(geometry), left, leftPosition, true); - renderPart(top.intersected(geometry), top, topPosition); - renderPart(right.intersected(geometry), right, rightPosition, true); - renderPart(bottom.intersected(geometry), bottom, bottomPosition); +const QMargins SceneOpenGLDecorationRenderer::texturePadForPart( + const QRect &rect, const QRect &partRect) +{ + QMargins result = QMargins(0, 0, 0, 0); + if (rect.top() == partRect.top()) { + result.setTop(TexturePad); + } + if (rect.bottom() == partRect.bottom()) { + result.setBottom(TexturePad); + } + if (rect.left() == partRect.left()) { + result.setLeft(TexturePad); + } + if (rect.right() == partRect.right()) { + result.setRight(TexturePad); + } + return result; } static int align(int value, int align) @@ -1702,15 +1700,12 @@ void SceneOpenGLDecorationRenderer::resizeTexture() qMax(left.height(), right.height())); size.rheight() = top.height() + bottom.height() + left.width() + right.width(); + size *= devicePixelRatio(); - // Reserve some space for padding. We pad decoration parts to avoid texture bleeding. - const int padding = 1; - size.rwidth() += 2 * padding; - size.rheight() += 4 * 2 * padding; - + size.rheight() += 4 * (2 * TexturePad); + size.rwidth() += 2 * TexturePad; size.rwidth() = align(size.width(), 128); - size *= devicePixelRatio(); if (m_texture && m_texture->size() == size) return; diff --git a/src/scenes/opengl/scene_opengl.h b/src/scenes/opengl/scene_opengl.h index 161558f492..79418cc528 100644 --- a/src/scenes/opengl/scene_opengl.h +++ b/src/scenes/opengl/scene_opengl.h @@ -215,6 +215,8 @@ public: } private: + void renderPart(const QRect &rect, const QRect &partRect, const QPoint &textureOffset, bool rotated = false); + static const QMargins texturePadForPart(const QRect &rect, const QRect &partRect); void resizeTexture(); QScopedPointer m_texture; };