Render GL Window decorations at the correct scale

Summary:
Under wayland we support high DPI putting by putting a separation
between the logical co-ordinate system and the resolution of rendered
assets.

When a window is on a high DPI screen, we should render at the higher
resolution.

Like the window scaling this handles any combination of a 2x scaled
decoration being rendered on a 1x screen or vice versa.

---
This patch is a bit different from the other scaling stuff. We have to
generate the quads *before* we have an updated texture with the new
scale. This means the scale isn't attached to the buffer like elsewhere.

That's why I added a property in TopLevel so there's still one canonical
source and things can't get out of sync.

BUG: 384765

Test Plan:
Crystal clear breeze and oxygen decos on my @2x display
Drag windows to attached @1x display, things still look OK when across 2
screens
Changing the scale of a screen updated the decos instantly

Reviewers: #plasma, graesslin

Reviewed By: #plasma, graesslin

Subscribers: graesslin, plasma-devel, kwin, #kwin

Tags: #kwin

Differential Revision: https://phabricator.kde.org/D8600
This commit is contained in:
David Edmundson 2017-11-01 17:54:21 +00:00
parent 449c93362b
commit fc887ab907
8 changed files with 60 additions and 26 deletions

View file

@ -21,6 +21,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "decoratedclient.h"
#include "deleted.h"
#include "abstract_client.h"
#include "screens.h"
#include <KDecoration2/Decoration>
#include <KDecoration2/DecoratedClient>
@ -38,10 +39,10 @@ Renderer::Renderer(DecoratedClientImpl *client)
, m_client(client)
, m_imageSizesDirty(true)
{
auto markImageSizesDirty = [this]{ m_imageSizesDirty = true; };
if (kwinApp()->operationMode() != Application::OperationModeX11) {
connect(client->client(), &AbstractClient::screenChanged, this, markImageSizesDirty);
}
auto markImageSizesDirty = [this]{
m_imageSizesDirty = true;
};
connect(client->client(), &AbstractClient::screenScaleChanged, this, markImageSizesDirty);
connect(client->decoration(), &KDecoration2::Decoration::bordersChanged, this, markImageSizesDirty);
connect(client->decoratedClient(), &KDecoration2::DecoratedClient::widthChanged, this, markImageSizesDirty);
connect(client->decoratedClient(), &KDecoration2::DecoratedClient::heightChanged, this, markImageSizesDirty);
@ -65,11 +66,13 @@ QRegion Renderer::getScheduled()
QImage Renderer::renderToImage(const QRect &geo)
{
Q_ASSERT(m_client);
QImage image(geo.width(), geo.height(), QImage::Format_ARGB32_Premultiplied);
auto dpr = client()->client()->screenScale();
QImage image(geo.width() * dpr, geo.height() * dpr, QImage::Format_ARGB32_Premultiplied);
image.setDevicePixelRatio(dpr);
image.fill(Qt::transparent);
QPainter p(&image);
p.setRenderHint(QPainter::Antialiasing);
p.setWindow(geo);
p.setWindow(QRect(geo.topLeft(), geo.size() * dpr));
p.setClipRect(geo);
client()->decoration()->paint(&p, geo);
return image;

View file

@ -2336,13 +2336,16 @@ SceneOpenGLDecorationRenderer::~SceneOpenGLDecorationRenderer() = default;
// and flips it vertically
static QImage rotate(const QImage &srcImage, const QRect &srcRect)
{
QImage image(srcRect.height(), srcRect.width(), srcImage.format());
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 + (srcRect.y() + x) * srcImage.width() + srcRect.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++) {
@ -2357,10 +2360,10 @@ static QImage rotate(const QImage &srcImage, const QRect &srcRect)
void SceneOpenGLDecorationRenderer::render()
{
const QRegion scheduled = getScheduled();
if (scheduled.isEmpty()) {
const bool dirty = areImageSizesDirty();
if (scheduled.isEmpty() && !dirty) {
return;
}
const bool dirty = areImageSizesDirty();
if (dirty) {
resizeTexture();
resetImageSizesDirty();
@ -2385,7 +2388,7 @@ void SceneOpenGLDecorationRenderer::render()
// TODO: get this done directly when rendering to the image
image = rotate(image, QRect(geo.topLeft() - partRect.topLeft(), geo.size()));
}
m_texture->update(image, geo.topLeft() - partRect.topLeft() + offset);
m_texture->update(image, (geo.topLeft() - partRect.topLeft() + offset) * image.devicePixelRatio());
};
renderPart(left.intersected(geometry), left, QPoint(0, top.height() + bottom.height() + 2), true);
renderPart(top.intersected(geometry), top, QPoint(0, 0));
@ -2411,6 +2414,7 @@ void SceneOpenGLDecorationRenderer::resizeTexture()
size.rwidth() = align(size.width(), 128);
size *= client()->client()->screenScale();
if (m_texture && m_texture->size() == size)
return;

View file

@ -632,7 +632,7 @@ void SceneQPainterDecorationRenderer::resizeImages()
client()->client()->layoutDecorationRects(left, top, right, bottom);
auto checkAndCreate = [this](int index, const QSize &size) {
auto dpr = screens()->scale(client()->client()->screen());
auto dpr = client()->client()->screenScale();
if (m_images[index].size() != size * dpr ||
m_images[index].devicePixelRatio() != dpr)
{

View file

@ -1230,6 +1230,7 @@ void SceneXRenderDecorationRenderer::render()
return;
}
QImage image = renderToImage(geo);
Q_ASSERT(image.devicePixelRatio() == 1);
xcb_put_image(c, XCB_IMAGE_FORMAT_Z_PIXMAP, m_pixmaps[index], m_gc,
image.width(), image.height(), geo.x() - offset.x(), geo.y() - offset.y(), 0, 32,
image.byteCount(), image.constBits());

View file

@ -403,6 +403,7 @@ void Scene::windowAdded(Toplevel *c)
if (c->surface()) {
connect(c->surface(), &KWayland::Server::SurfaceInterface::scaleChanged, this, std::bind(&Scene::windowGeometryShapeChanged, this, c));
}
connect(c, &Toplevel::screenScaleChanged, std::bind(&Scene::windowGeometryShapeChanged, this, c));
c->effectWindow()->setSceneWindow(w);
c->getShadow();
w->updateShadow(c->shadow());
@ -851,22 +852,23 @@ WindowQuadList Scene::Window::buildQuads(bool force) const
QRegion contents = clientShape();
QRegion center = toplevel->transparentRect();
QRegion decoration = (client ? QRegion(client->decorationRect()) : shape()) - center;
qreal decorationScale = 1.0;
ret = makeQuads(WindowQuadContents, contents, toplevel->clientContentPos(), scale);
QRect rects[4];
bool isShadedClient = false;
if (client) {
client->layoutDecorationRects(rects[0], rects[1], rects[2], rects[3]);
decorationScale = client->screenScale();
isShadedClient = client->isShade() || center.isEmpty();
}
if (isShadedClient) {
const QRect bounding = rects[0] | rects[1] | rects[2] | rects[3];
ret += makeDecorationQuads(rects, bounding);
ret += makeDecorationQuads(rects, bounding, decorationScale);
} else {
ret += makeDecorationQuads(rects, decoration);
ret += makeDecorationQuads(rects, decoration, decorationScale);
}
}
@ -878,7 +880,7 @@ WindowQuadList Scene::Window::buildQuads(bool force) const
return ret;
}
WindowQuadList Scene::Window::makeDecorationQuads(const QRect *rects, const QRegion &region) const
WindowQuadList Scene::Window::makeDecorationQuads(const QRect *rects, const QRegion &region, qreal textureScale) const
{
WindowQuadList list;
@ -908,10 +910,10 @@ WindowQuadList Scene::Window::makeDecorationQuads(const QRect *rects, const QReg
const int x1 = r.x() + r.width();
const int y1 = r.y() + r.height();
const int u0 = x0 + offsets[i].x();
const int v0 = y0 + offsets[i].y();
const int u1 = x1 + offsets[i].x();
const int v1 = y1 + offsets[i].y();
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(WindowQuadDecoration);
quad.setUVAxisSwapped(swap);

View file

@ -338,7 +338,7 @@ public:
void unreferencePreviousPixmap();
protected:
WindowQuadList makeQuads(WindowQuadType type, const QRegion& reg, const QPoint &textureOffset = QPoint(0, 0), qreal textureScale = 1.0) const;
WindowQuadList makeDecorationQuads(const QRect *rects, const QRegion &region) const;
WindowQuadList makeDecorationQuads(const QRect *rects, const QRegion &region, qreal textureScale = 1.0) const;
/**
* @brief Returns the WindowPixmap for this Window.
*

View file

@ -281,12 +281,17 @@ void Toplevel::checkScreen()
m_screen = 0;
emit screenChanged();
}
return;
} else {
const int s = screens()->number(geometry().center());
if (s != m_screen) {
m_screen = s;
emit screenChanged();
}
}
const int s = screens()->number(geometry().center());
if (s != m_screen) {
m_screen = s;
emit screenChanged();
qreal newScale = screens()->scale(m_screen);
if (newScale != m_screenScale) {
m_screenScale = newScale;
emit screenScaleChanged();
}
}
@ -308,6 +313,11 @@ int Toplevel::screen() const
return m_screen;
}
qreal Toplevel::screenScale() const
{
return m_screenScale;
}
bool Toplevel::isOnScreen(int screen) const
{
return screens()->geometry(screen).intersects(geometry());

View file

@ -236,6 +236,12 @@ public:
bool isOnScreen(int screen) const; // true if it's at least partially there
bool isOnActiveScreen() const;
int screen() const; // the screen where the center is
/**
* The scale of the screen this window is currently on
* @Note: The buffer scale can be different.
* @since 5.12
*/
qreal screenScale() const; //
virtual QPoint clientPos() const = 0; // inside of geometry()
/**
* Describes how the client's content maps to the window geometry including the frame.
@ -490,6 +496,13 @@ Q_SIGNALS:
**/
void surfaceChanged();
/*
* Emitted when the client's screen changes onto a screen of a different scale
* or the screen we're on changes
* @since 5.12
*/
void screenScaleChanged();
protected Q_SLOTS:
/**
* Checks whether the screen number for this Toplevel changed and updates if needed.
@ -570,6 +583,7 @@ private:
**/
QSharedPointer<QOpenGLFramebufferObject> m_internalFBO;
// when adding new data members, check also copyToDeleted()
qreal m_screenScale = 1.0;
};
inline xcb_window_t Toplevel::window() const