Fix decoration rendering with scale factor < 1

QPainter won't let paint with a device pixel ratio less than 1. There
are used to be workarounds to force a device pixel ratio of 1, but they
were removed with fractional scaling corner fix.

This change makes sure that the decoration renderer forces a device
pixel ratio of 1 if the output's scale factor is less than 1 so
calculated texture coordinates match where window borders are rendered
in the texture atlas.

BUG: 449681
This commit is contained in:
Vlad Zahorodnii 2022-02-07 12:36:25 +02:00
parent a428630ea4
commit bb935aa5de
7 changed files with 28 additions and 33 deletions

View file

@ -68,6 +68,12 @@ void DecorationRenderer::resetDamage()
m_damage = QRegion(); m_damage = QRegion();
} }
qreal DecorationRenderer::effectiveDevicePixelRatio() const
{
// QPainter won't let paint with a device pixel ratio less than 1.
return std::max(qreal(1.0), devicePixelRatio());
}
qreal DecorationRenderer::devicePixelRatio() const qreal DecorationRenderer::devicePixelRatio() const
{ {
return m_devicePixelRatio; return m_devicePixelRatio;
@ -107,7 +113,7 @@ QImage DecorationRenderer::renderToImage(const QRect &geo)
image.fill(Qt::transparent); image.fill(Qt::transparent);
QPainter p(&image); QPainter p(&image);
p.setRenderHint(QPainter::Antialiasing); p.setRenderHint(QPainter::Antialiasing);
p.setWindow(QRect(geo.topLeft(), geo.size() * qPainterEffectiveDevicePixelRatio(&p))); p.setWindow(QRect(geo.topLeft(), geo.size() * effectiveDevicePixelRatio()));
p.setClipRect(geo); p.setClipRect(geo);
renderToPainter(&p, geo); renderToPainter(&p, geo);
return image; return image;
@ -230,7 +236,7 @@ WindowQuadList DecorationItem::buildQuads() const
} }
QRect left, top, right, bottom; QRect left, top, right, bottom;
const qreal devicePixelRatio = m_renderer->devicePixelRatio(); const qreal devicePixelRatio = m_renderer->effectiveDevicePixelRatio();
const int texturePad = DecorationRenderer::TexturePad; const int texturePad = DecorationRenderer::TexturePad;
if (const AbstractClient *client = qobject_cast<const AbstractClient *>(m_window)) { if (const AbstractClient *client = qobject_cast<const AbstractClient *>(m_window)) {

View file

@ -38,6 +38,7 @@ public:
void addDamage(const QRegion &region); void addDamage(const QRegion &region);
void resetDamage(); void resetDamage();
qreal effectiveDevicePixelRatio() const;
qreal devicePixelRatio() const; qreal devicePixelRatio() const;
void setDevicePixelRatio(qreal dpr); void setDevicePixelRatio(qreal dpr);

View file

@ -1536,9 +1536,10 @@ void SceneOpenGLDecorationRenderer::render(const QRegion &region)
QRect left, top, right, bottom; QRect left, top, right, bottom;
client()->client()->layoutDecorationRects(left, top, right, bottom); client()->client()->layoutDecorationRects(left, top, right, bottom);
const int topHeight = std::ceil(top.height() * devicePixelRatio()); const qreal devicePixelRatio = effectiveDevicePixelRatio();
const int bottomHeight = std::ceil(bottom.height() * devicePixelRatio()); const int topHeight = std::ceil(top.height() * devicePixelRatio);
const int leftWidth = std::ceil(left.width() * devicePixelRatio()); const int bottomHeight = std::ceil(bottom.height() * devicePixelRatio);
const int leftWidth = std::ceil(left.width() * devicePixelRatio);
const QPoint topPosition(0, 0); const QPoint topPosition(0, 0);
const QPoint bottomPosition(0, topPosition.y() + topHeight + (2 * TexturePad)); const QPoint bottomPosition(0, topPosition.y() + topHeight + (2 * TexturePad));
@ -1547,14 +1548,15 @@ void SceneOpenGLDecorationRenderer::render(const QRegion &region)
const QRect dirtyRect = region.boundingRect(); const QRect dirtyRect = region.boundingRect();
renderPart(top.intersected(dirtyRect), top, topPosition); renderPart(top.intersected(dirtyRect), top, topPosition, devicePixelRatio);
renderPart(bottom.intersected(dirtyRect), bottom, bottomPosition); renderPart(bottom.intersected(dirtyRect), bottom, bottomPosition, devicePixelRatio);
renderPart(left.intersected(dirtyRect), left, leftPosition, true); renderPart(left.intersected(dirtyRect), left, leftPosition, devicePixelRatio, true);
renderPart(right.intersected(dirtyRect), right, rightPosition, true); renderPart(right.intersected(dirtyRect), right, rightPosition, devicePixelRatio, true);
} }
void SceneOpenGLDecorationRenderer::renderPart(const QRect &rect, const QRect &partRect, void SceneOpenGLDecorationRenderer::renderPart(const QRect &rect, const QRect &partRect,
const QPoint &textureOffset, bool rotated) const QPoint &textureOffset,
qreal devicePixelRatio, bool rotated)
{ {
if (!rect.isValid()) { if (!rect.isValid()) {
return; return;
@ -1567,7 +1569,7 @@ void SceneOpenGLDecorationRenderer::renderPart(const QRect &rect, const QRect &p
int verticalPadding = padding.top() + padding.bottom(); int verticalPadding = padding.top() + padding.bottom();
int horizontalPadding = padding.left() + padding.right(); int horizontalPadding = padding.left() + padding.right();
QSize imageSize = rect.size() * devicePixelRatio(); QSize imageSize = rect.size() * devicePixelRatio;
if (rotated) { if (rotated) {
imageSize = QSize(imageSize.height(), imageSize.width()); imageSize = QSize(imageSize.height(), imageSize.width());
} }
@ -1575,12 +1577,12 @@ void SceneOpenGLDecorationRenderer::renderPart(const QRect &rect, const QRect &p
paddedImageSize.rheight() += verticalPadding; paddedImageSize.rheight() += verticalPadding;
paddedImageSize.rwidth() += horizontalPadding; paddedImageSize.rwidth() += horizontalPadding;
QImage image(paddedImageSize, QImage::Format_ARGB32_Premultiplied); QImage image(paddedImageSize, 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()); QRect padClip = QRect(padding.left(), padding.top(), imageSize.width(), imageSize.height());
QPainter painter(&image); QPainter painter(&image);
const qreal inverseScale = 1.0 / devicePixelRatio(); const qreal inverseScale = 1.0 / devicePixelRatio;
painter.scale(inverseScale, inverseScale); painter.scale(inverseScale, inverseScale);
painter.setRenderHint(QPainter::Antialiasing); painter.setRenderHint(QPainter::Antialiasing);
painter.setClipRect(padClip); painter.setClipRect(padClip);
@ -1589,7 +1591,7 @@ void SceneOpenGLDecorationRenderer::renderPart(const QRect &rect, const QRect &p
painter.translate(0, imageSize.height()); painter.translate(0, imageSize.height());
painter.rotate(-90); painter.rotate(-90);
} }
painter.scale(devicePixelRatio(), devicePixelRatio()); painter.scale(devicePixelRatio, devicePixelRatio);
painter.translate(-rect.topLeft()); painter.translate(-rect.topLeft());
renderToPainter(&painter, rect); renderToPainter(&painter, rect);
painter.end(); painter.end();
@ -1597,7 +1599,7 @@ void SceneOpenGLDecorationRenderer::renderPart(const QRect &rect, const QRect &p
// fill padding pixels by copying from the neighbour row // fill padding pixels by copying from the neighbour row
clamp(image, padClip); clamp(image, padClip);
QPoint dirtyOffset = (rect.topLeft() - partRect.topLeft()) * devicePixelRatio(); QPoint dirtyOffset = (rect.topLeft() - partRect.topLeft()) * devicePixelRatio;
if (padding.top() == 0) { if (padding.top() == 0) {
dirtyOffset.ry() += TexturePad; dirtyOffset.ry() += TexturePad;
} }
@ -1641,7 +1643,7 @@ 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(); size *= effectiveDevicePixelRatio();
size.rheight() += 4 * (2 * TexturePad); size.rheight() += 4 * (2 * TexturePad);
size.rwidth() += 2 * TexturePad; size.rwidth() += 2 * TexturePad;

View file

@ -213,7 +213,7 @@ public:
} }
private: private:
void renderPart(const QRect &rect, const QRect &partRect, const QPoint &textureOffset, bool rotated = false); void renderPart(const QRect &rect, const QRect &partRect, const QPoint &textureOffset, qreal devicePixelRatio, bool rotated = false);
static const QMargins texturePadForPart(const QRect &rect, const QRect &partRect); static const QMargins texturePadForPart(const QRect &rect, const QRect &partRect);
void resizeTexture(); void resizeTexture();
QScopedPointer<GLTexture> m_texture; QScopedPointer<GLTexture> m_texture;

View file

@ -444,7 +444,7 @@ void SceneQPainterDecorationRenderer::render(const QRegion &region)
} }
QPainter painter(&m_images[index]); QPainter painter(&m_images[index]);
painter.setRenderHint(QPainter::Antialiasing); painter.setRenderHint(QPainter::Antialiasing);
painter.setWindow(QRect(partRect.topLeft(), partRect.size() * qPainterEffectiveDevicePixelRatio(&painter))); painter.setWindow(QRect(partRect.topLeft(), partRect.size() * effectiveDevicePixelRatio()));
painter.setClipRect(rect); painter.setClipRect(rect);
painter.save(); painter.save();
// clear existing part // clear existing part
@ -466,7 +466,7 @@ void SceneQPainterDecorationRenderer::resizeImages()
client()->client()->layoutDecorationRects(left, top, right, bottom); client()->client()->layoutDecorationRects(left, top, right, bottom);
auto checkAndCreate = [this](int index, const QSize &size) { auto checkAndCreate = [this](int index, const QSize &size) {
auto dpr = devicePixelRatio(); auto dpr = effectiveDevicePixelRatio();
if (m_images[index].size() != size * dpr || if (m_images[index].size() != size * dpr ||
m_images[index].devicePixelRatio() != dpr) m_images[index].devicePixelRatio() != dpr)
{ {

View file

@ -181,11 +181,6 @@ Qt::KeyboardModifiers x11ToQtKeyboardModifiers(int state)
return ret; return ret;
} }
qreal qPainterEffectiveDevicePixelRatio(const QPainter *painter)
{
return std::max(qreal(1), painter->device()->devicePixelRatioF());
}
QPoint popupOffset(const QRect &anchorRect, const Qt::Edges anchorEdge, const Qt::Edges gravity, const QSize popupSize) QPoint popupOffset(const QRect &anchorRect, const Qt::Edges anchorEdge, const Qt::Edges gravity, const QSize popupSize)
{ {
QPoint anchorPoint; QPoint anchorPoint;

View file

@ -123,15 +123,6 @@ void KWIN_EXPORT ungrabXServer();
bool KWIN_EXPORT grabXKeyboard(xcb_window_t w = XCB_WINDOW_NONE); bool KWIN_EXPORT grabXKeyboard(xcb_window_t w = XCB_WINDOW_NONE);
void KWIN_EXPORT ungrabXKeyboard(); void KWIN_EXPORT ungrabXKeyboard();
/**
* QPainter::setWindow() doesn't work as expected when the device pixel ratio of the paint
* device is less than 1.
*
* QPainter simply doesn't allow the effective scale factor to be less than 1. Use this function
* to determine the effective device pixel ratio by which the window rect has to be scaled.
*/
qreal KWIN_EXPORT qPainterEffectiveDevicePixelRatio(const QPainter *painter);
static inline QRegion mapRegion(const QMatrix4x4 &matrix, const QRegion &region) static inline QRegion mapRegion(const QMatrix4x4 &matrix, const QRegion &region)
{ {
QRegion result; QRegion result;