Improve window decorations in OpenGL scene
When creating the texture containing the window decorations, the drawing code now directly handles the rotation for the left and right decoration, instead of rotating the image after it has been drawn. The padding, to prevent texture bleeding, is now a fixed value instead of being scaled. With this change, there are no longer visual artifacts for window decorations with rounded corners, when the scaling value is fractional.
This commit is contained in:
parent
e655dc7b42
commit
3b4d558371
4 changed files with 143 additions and 144 deletions
|
@ -14,6 +14,8 @@
|
||||||
#include "scene.h"
|
#include "scene.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
#include <KDecoration2/Decoration>
|
#include <KDecoration2/Decoration>
|
||||||
#include <KDecoration2/DecoratedClient>
|
#include <KDecoration2/DecoratedClient>
|
||||||
|
|
||||||
|
@ -190,78 +192,75 @@ DecorationRenderer *DecorationItem::renderer() const
|
||||||
return m_renderer.data();
|
return m_renderer.data();
|
||||||
}
|
}
|
||||||
|
|
||||||
WindowQuadList DecorationItem::buildQuads() const
|
WindowQuad buildQuad(const QRect &partRect, const QPoint &textureOffset,
|
||||||
|
const qreal devicePixelRatio, bool rotated)
|
||||||
{
|
{
|
||||||
if (m_window->frameMargins().isNull()) {
|
const QRect &r = partRect;
|
||||||
return WindowQuadList();
|
const int p = DecorationRenderer::TexturePad;
|
||||||
}
|
|
||||||
|
|
||||||
QRect rects[4];
|
|
||||||
|
|
||||||
if (const AbstractClient *client = qobject_cast<const AbstractClient *>(m_window)) {
|
|
||||||
client->layoutDecorationRects(rects[0], rects[1], rects[2], rects[3]);
|
|
||||||
} else if (const Deleted *deleted = qobject_cast<const Deleted *>(m_window)) {
|
|
||||||
deleted->layoutDecorationRects(rects[0], rects[1], rects[2], rects[3]);
|
|
||||||
}
|
|
||||||
|
|
||||||
const qreal textureScale = m_renderer->devicePixelRatio();
|
|
||||||
const int padding = 1;
|
|
||||||
|
|
||||||
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
|
|
||||||
};
|
|
||||||
|
|
||||||
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 x0 = r.x();
|
||||||
const int y0 = r.y();
|
const int y0 = r.y();
|
||||||
const int x1 = r.x() + r.width();
|
const int x1 = r.x() + r.width();
|
||||||
const int y1 = r.y() + r.height();
|
const int y1 = r.y() + r.height();
|
||||||
|
|
||||||
const int u0 = (x0 + offsets[i].x()) * textureScale;
|
int u0 = textureOffset.x() + p;
|
||||||
const int v0 = (y0 + offsets[i].y()) * textureScale;
|
int v0 = textureOffset.y() + p;
|
||||||
const int u1 = (x1 + offsets[i].x()) * textureScale;
|
int u1 = textureOffset.x() + p + (r.width() * devicePixelRatio);
|
||||||
const int v1 = (y1 + offsets[i].y()) * textureScale;
|
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;
|
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[0] = WindowVertex(x0, y0, u0, v0); // Top-left
|
||||||
quad[1] = WindowVertex(x1, y0, u1, v0); // Top-right
|
quad[1] = WindowVertex(x1, y0, u1, v0); // Top-right
|
||||||
quad[2] = WindowVertex(x1, y1, u1, v1); // Bottom-right
|
quad[2] = WindowVertex(x1, y1, u1, v1); // Bottom-right
|
||||||
quad[3] = WindowVertex(x0, y1, u0, v1); // Bottom-left
|
quad[3] = WindowVertex(x0, y1, u0, v1); // Bottom-left
|
||||||
|
return quad;
|
||||||
}
|
}
|
||||||
|
|
||||||
list.append(quad);
|
WindowQuadList DecorationItem::buildQuads() const
|
||||||
|
{
|
||||||
|
if (m_window->frameMargins().isNull()) {
|
||||||
|
return WindowQuadList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QRect left, top, right, bottom;
|
||||||
|
const qreal devicePixelRatio = m_renderer->devicePixelRatio();
|
||||||
|
const int texturePad = DecorationRenderer::TexturePad;
|
||||||
|
|
||||||
|
if (const AbstractClient *client = qobject_cast<const AbstractClient *>(m_window)) {
|
||||||
|
client->layoutDecorationRects(left, top, right, bottom);
|
||||||
|
} else if (const Deleted *deleted = qobject_cast<const Deleted *>(m_window)) {
|
||||||
|
deleted->layoutDecorationRects(left, top, right, bottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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;
|
||||||
|
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;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,9 @@ public:
|
||||||
qreal devicePixelRatio() const;
|
qreal devicePixelRatio() const;
|
||||||
void setDevicePixelRatio(qreal dpr);
|
void setDevicePixelRatio(qreal dpr);
|
||||||
|
|
||||||
|
// Reserve some space for padding. We pad decoration parts to avoid texture bleeding.
|
||||||
|
static const int TexturePad = 1;
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void damaged(const QRegion ®ion);
|
void damaged(const QRegion ®ion);
|
||||||
|
|
||||||
|
|
|
@ -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<const uint32_t *>(srcImage.bits());
|
|
||||||
uint32_t *dst = reinterpret_cast<uint32_t *>(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)
|
static void clamp_row(int left, int width, int right, const uint32_t *src, uint32_t *dest)
|
||||||
{
|
{
|
||||||
std::fill_n(dest, left, *src);
|
std::fill_n(dest, left, *src);
|
||||||
|
@ -1620,71 +1595,94 @@ void SceneOpenGLDecorationRenderer::render(const QRegion ®ion)
|
||||||
QRect left, top, right, bottom;
|
QRect left, top, right, bottom;
|
||||||
client()->client()->layoutDecorationRects(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 topHeight = std::ceil(top.height() * devicePixelRatio());
|
||||||
const int padding = 1;
|
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) {
|
const QPoint topPosition(0, 0);
|
||||||
if (!geo.isValid()) {
|
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));
|
||||||
|
|
||||||
|
const QRect dirtyRect = region.boundingRect();
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SceneOpenGLDecorationRenderer::renderPart(const QRect &rect, const QRect &partRect,
|
||||||
|
const QPoint &textureOffset, bool rotated)
|
||||||
|
{
|
||||||
|
if (!rect.isValid()) {
|
||||||
return;
|
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();
|
||||||
|
|
||||||
QRect rect = geo;
|
QSize imageSize = rect.size() * devicePixelRatio();
|
||||||
|
if (rotated) {
|
||||||
// We allow partial decoration updates and it might just so happen that the dirty region
|
imageSize = QSize(imageSize.height(), imageSize.width());
|
||||||
// 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()) {
|
QSize paddedImageSize = imageSize;
|
||||||
rect.setTop(rect.top() - padding);
|
paddedImageSize.rheight() += verticalPadding;
|
||||||
}
|
paddedImageSize.rwidth() += horizontalPadding;
|
||||||
if (rect.right() == partRect.right()) {
|
QImage image(paddedImageSize, QImage::Format_ARGB32_Premultiplied);
|
||||||
rect.setRight(rect.right() + padding);
|
|
||||||
}
|
|
||||||
if (rect.bottom() == partRect.bottom()) {
|
|
||||||
rect.setBottom(rect.bottom() + padding);
|
|
||||||
}
|
|
||||||
|
|
||||||
QRect viewport = geo.translated(-rect.x(), -rect.y());
|
|
||||||
|
|
||||||
QImage image(rect.size() * devicePixelRatio(), QImage::Format_ARGB32_Premultiplied);
|
|
||||||
image.setDevicePixelRatio(devicePixelRatio());
|
image.setDevicePixelRatio(devicePixelRatio());
|
||||||
image.fill(Qt::transparent);
|
image.fill(Qt::transparent);
|
||||||
|
|
||||||
|
QRect padClip = QRect(padding.left(), padding.top(), imageSize.width(), imageSize.height());
|
||||||
QPainter painter(&image);
|
QPainter painter(&image);
|
||||||
|
const qreal inverseScale = 1.0 / devicePixelRatio();
|
||||||
|
painter.scale(inverseScale, inverseScale);
|
||||||
painter.setRenderHint(QPainter::Antialiasing);
|
painter.setRenderHint(QPainter::Antialiasing);
|
||||||
painter.setViewport(QRect(viewport.topLeft(), viewport.size() * devicePixelRatio()));
|
painter.setClipRect(padClip);
|
||||||
painter.setWindow(QRect(geo.topLeft(), geo.size() * qPainterEffectiveDevicePixelRatio(&painter)));
|
painter.translate(padding.left(), padding.top());
|
||||||
painter.setClipRect(geo);
|
if (rotated) {
|
||||||
renderToPainter(&painter, geo);
|
painter.translate(0, imageSize.height());
|
||||||
|
painter.rotate(-90);
|
||||||
|
}
|
||||||
|
painter.scale(devicePixelRatio(), devicePixelRatio());
|
||||||
|
painter.translate(-rect.topLeft());
|
||||||
|
renderToPainter(&painter, rect);
|
||||||
painter.end();
|
painter.end();
|
||||||
|
|
||||||
const QRect viewportScaled(viewport.topLeft() * devicePixelRatio(), viewport.size() * devicePixelRatio());
|
// fill padding pixels by copying from the neighbour row
|
||||||
const bool isIntegerScaling = qFuzzyCompare(devicePixelRatio(), std::ceil(devicePixelRatio()));
|
clamp(image, padClip);
|
||||||
clamp(image, isIntegerScaling ? viewportScaled : viewportScaled.marginsRemoved({1, 1, 1, 1}));
|
|
||||||
|
|
||||||
if (rotated) {
|
QPoint dirtyOffset = (rect.topLeft() - partRect.topLeft()) * devicePixelRatio();
|
||||||
// TODO: get this done directly when rendering to the image
|
if (padding.top() == 0) {
|
||||||
image = rotate(image, QRect(QPoint(), rect.size()));
|
dirtyOffset.ry() += TexturePad;
|
||||||
viewport = QRect(viewport.y(), viewport.x(), viewport.height(), viewport.width());
|
}
|
||||||
|
if (padding.left() == 0) {
|
||||||
|
dirtyOffset.rx() += TexturePad;
|
||||||
|
}
|
||||||
|
m_texture->update(image, textureOffset + dirtyOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
const QPoint dirtyOffset = geo.topLeft() - partRect.topLeft();
|
const QMargins SceneOpenGLDecorationRenderer::texturePadForPart(
|
||||||
m_texture->update(image, (position + dirtyOffset - viewport.topLeft()) * image.devicePixelRatio());
|
const QRect &rect, const QRect &partRect)
|
||||||
};
|
{
|
||||||
|
QMargins result = QMargins(0, 0, 0, 0);
|
||||||
const QRect geometry = region.boundingRect();
|
if (rect.top() == partRect.top()) {
|
||||||
|
result.setTop(TexturePad);
|
||||||
const QPoint topPosition(padding, padding);
|
}
|
||||||
const QPoint bottomPosition(padding, topPosition.y() + top.height() + 2 * padding);
|
if (rect.bottom() == partRect.bottom()) {
|
||||||
const QPoint leftPosition(padding, bottomPosition.y() + bottom.height() + 2 * padding);
|
result.setBottom(TexturePad);
|
||||||
const QPoint rightPosition(padding, leftPosition.y() + left.width() + 2 * padding);
|
}
|
||||||
|
if (rect.left() == partRect.left()) {
|
||||||
renderPart(left.intersected(geometry), left, leftPosition, true);
|
result.setLeft(TexturePad);
|
||||||
renderPart(top.intersected(geometry), top, topPosition);
|
}
|
||||||
renderPart(right.intersected(geometry), right, rightPosition, true);
|
if (rect.right() == partRect.right()) {
|
||||||
renderPart(bottom.intersected(geometry), bottom, bottomPosition);
|
result.setRight(TexturePad);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int align(int value, int align)
|
static int align(int value, int align)
|
||||||
|
@ -1702,15 +1700,12 @@ void SceneOpenGLDecorationRenderer::resizeTexture()
|
||||||
qMax(left.height(), right.height()));
|
qMax(left.height(), right.height()));
|
||||||
size.rheight() = top.height() + bottom.height() +
|
size.rheight() = top.height() + bottom.height() +
|
||||||
left.width() + right.width();
|
left.width() + right.width();
|
||||||
|
size *= devicePixelRatio();
|
||||||
|
|
||||||
// Reserve some space for padding. We pad decoration parts to avoid texture bleeding.
|
size.rheight() += 4 * (2 * TexturePad);
|
||||||
const int padding = 1;
|
size.rwidth() += 2 * TexturePad;
|
||||||
size.rwidth() += 2 * padding;
|
|
||||||
size.rheight() += 4 * 2 * padding;
|
|
||||||
|
|
||||||
size.rwidth() = align(size.width(), 128);
|
size.rwidth() = align(size.width(), 128);
|
||||||
|
|
||||||
size *= devicePixelRatio();
|
|
||||||
if (m_texture && m_texture->size() == size)
|
if (m_texture && m_texture->size() == size)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
|
@ -215,6 +215,8 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
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();
|
void resizeTexture();
|
||||||
QScopedPointer<GLTexture> m_texture;
|
QScopedPointer<GLTexture> m_texture;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue