diff --git a/abstract_output.h b/abstract_output.h --- a/abstract_output.h +++ b/abstract_output.h @@ -154,6 +154,8 @@ */ virtual qreal scale() const; + virtual QPoint globalPos() const; + /** * Returns the physical size of this output, in millimeters. * diff --git a/abstract_output.cpp b/abstract_output.cpp --- a/abstract_output.cpp +++ b/abstract_output.cpp @@ -98,6 +98,10 @@ return 1; } +QPoint AbstractOutput::globalPos() const { + return QPoint(); +} + QSize AbstractOutput::physicalSize() const { return QSize(); diff --git a/abstract_wayland_output.h b/abstract_wayland_output.h --- a/abstract_wayland_output.h +++ b/abstract_wayland_output.h @@ -132,7 +132,7 @@ const QByteArray &uuid, const QSize &physicalSize, const QVector &modes); - QPoint globalPos() const; + QPoint globalPos() const override; bool internal() const { return m_internal; diff --git a/autotests/mock_screens.h b/autotests/mock_screens.h --- a/autotests/mock_screens.h +++ b/autotests/mock_screens.h @@ -32,6 +32,7 @@ explicit MockScreens(QObject *parent = nullptr); ~MockScreens() override; QRect geometry(int screen) const override; + QPoint globalPos(int screen) const override; int number(const QPoint &pos) const override; QString name(int screen) const override; float refreshRate(int screen) const override; diff --git a/autotests/mock_screens.cpp b/autotests/mock_screens.cpp --- a/autotests/mock_screens.cpp +++ b/autotests/mock_screens.cpp @@ -37,6 +37,17 @@ return m_geometries.at(screen); } +QPoint MockScreens::globalPos(int screen) const +{ + if (screen >= m_geometries.count()) { + return QPoint(); + } + int x1, y1, x2, y2; + m_geometries.at(screen).getCoords(&x1, &y1, &x2, &y2); + + return QPoint(x1, y1); +} + QString MockScreens::name(int screen) const { Q_UNUSED(screen); diff --git a/effects/screenshot/screenshot.h b/effects/screenshot/screenshot.h --- a/effects/screenshot/screenshot.h +++ b/effects/screenshot/screenshot.h @@ -141,7 +141,7 @@ private: void grabPointerImage(QImage& snapshot, int offsetx, int offsety); - QImage blitScreenshot(const QRect &geometry); + QImage blitScreenshot(const QRect &geometry, qreal scale = 1.0); QString saveTempImage(const QImage &img); void sendReplyImage(const QImage &img); enum class InfoMessageMode { @@ -156,7 +156,7 @@ QRect m_scheduledGeometry; QDBusMessage m_replyMessage; QRect m_cachedOutputGeometry; - QImage m_multipleOutputsImage; + QVector> m_cacheOutputsImages; QRegion m_multipleOutputsRendered; bool m_captureCursor = false; enum class WindowMode { @@ -167,6 +167,8 @@ }; WindowMode m_windowMode = WindowMode::NoCapture; int m_fd = -1; + QPoint m_globalPos; + qreal m_cachedScale; }; } // namespace diff --git a/effects/screenshot/screenshot.cpp b/effects/screenshot/screenshot.cpp --- a/effects/screenshot/screenshot.cpp +++ b/effects/screenshot/screenshot.cpp @@ -159,6 +159,15 @@ void ScreenShotEffect::paintScreen(int mask, const QRegion ®ion, ScreenPaintData &data) { m_cachedOutputGeometry = data.outputGeometry(); + m_cachedScale = data.screenScale(); + m_globalPos = data.globalPos(); + if (m_globalPos.isNull()) { + // Xorg case + int x1, y1, x2, y2; + m_cachedOutputGeometry.getCoords(&x1, &y1, &x2, &y2); + m_globalPos = QPoint(x1, y1); + } + effects->paintScreen(mask, region, data); } @@ -283,31 +292,56 @@ if (!m_cachedOutputGeometry.isNull()) { // special handling for per-output geometry rendering const QRect intersection = m_scheduledGeometry.intersected(m_cachedOutputGeometry); + if (intersection.isEmpty()) { // doesn't intersect, not going onto this screenshot return; } - const QImage img = blitScreenshot(intersection); - if (img.size() == m_scheduledGeometry.size()) { + + const QImage img = blitScreenshot(intersection, m_cachedScale); + + if (img.size() == QSize(m_scheduledGeometry.width() * m_cachedScale, + m_scheduledGeometry.height() * m_cachedScale)) { // we are done sendReplyImage(img); return; } - if (m_multipleOutputsImage.isNull()) { - m_multipleOutputsImage = QImage(m_scheduledGeometry.size(), QImage::Format_ARGB32); - m_multipleOutputsImage.fill(Qt::transparent); - } - QPainter p; - p.begin(&m_multipleOutputsImage); - p.drawImage(intersection.topLeft() - m_scheduledGeometry.topLeft(), img); - p.end(); + + m_cacheOutputsImages.append(QPair(m_globalPos, img)); m_multipleOutputsRendered = m_multipleOutputsRendered.united(intersection); + if (m_multipleOutputsRendered.boundingRect() == m_scheduledGeometry) { + + // find the output image size + int width = 0; + int height = 0; + for (const QPair &pair: m_cacheOutputsImages) { + const auto pos = pair.first; + const auto img = pair.second; + + width = qMax(width, pos.x() + img.width()); + height = qMax(height, pos.y() + img.height()); + } + + QSize outputSize(width, height); + QImage m_multipleOutputsImage = QImage(outputSize, QImage::Format_ARGB32); + + QPainter p; + p.begin(&m_multipleOutputsImage); + + // reassemble images together + for (const QPair &pair: qAsConst(m_cacheOutputsImages)) { + const auto pos = pair.first; + const auto img = pair.second; + p.drawImage(pos, img); + } + p.end(); + sendReplyImage(m_multipleOutputsImage); } } else { - const QImage img = blitScreenshot(m_scheduledGeometry); + const QImage img = blitScreenshot(m_scheduledGeometry, m_cachedScale); sendReplyImage(img); } } @@ -332,10 +366,10 @@ QDBusConnection::sessionBus().send(m_replyMessage.createReply(saveTempImage(img))); } m_scheduledGeometry = QRect(); - m_multipleOutputsImage = QImage(); m_multipleOutputsRendered = QRegion(); m_captureCursor = false; m_windowMode = WindowMode::NoCapture; + m_cacheOutputsImages = QVector>(); } QString ScreenShotEffect::saveTempImage(const QImage &img) @@ -604,38 +638,46 @@ return QString(); } -QImage ScreenShotEffect::blitScreenshot(const QRect &geometry) +QImage ScreenShotEffect::blitScreenshot(const QRect &geometry, qreal scale) { QImage img; if (effects->isOpenGLCompositing()) { - img = QImage(geometry.size(), QImage::Format_ARGB32); + int width = geometry.width(); + int height = geometry.height(); if (GLRenderTarget::blitSupported() && !GLPlatform::instance()->isGLES()) { - GLTexture tex(GL_RGBA8, geometry.width(), geometry.height()); + + width = static_cast(width * scale); + height = static_cast(height * scale); + + img = QImage(width, height, QImage::Format_ARGB32); + + GLTexture tex(GL_RGBA8, width, height); GLRenderTarget target(tex); - target.blitFromFramebuffer(geometry); // copy content from framebuffer into image + target.blitFromFramebuffer(geometry); tex.bind(); - glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, (GLvoid*)img.bits()); + glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, static_cast(img.bits())); tex.unbind(); } else { - glReadPixels(0, 0, img.width(), img.height(), GL_RGBA, GL_UNSIGNED_BYTE, (GLvoid*)img.bits()); + img = QImage(width, height, QImage::Format_ARGB32); + glReadPixels(0, 0, img.width(), img.height(), GL_RGBA, GL_UNSIGNED_BYTE, static_cast(img.bits())); } - ScreenShotEffect::convertFromGLImage(img, geometry.width(), geometry.height()); + ScreenShotEffect::convertFromGLImage(img, width, height); } #ifdef KWIN_HAVE_XRENDER_COMPOSITING if (effects->compositingType() == XRenderCompositing) { - xcb_image_t *xImage = nullptr; + xcb_image_t *xImage = nullptr; img = xPictureToImage(effects->xrenderBufferPicture(), geometry, &xImage); if (xImage) { xcb_image_destroy(xImage); } } #endif if (m_captureCursor) { - grabPointerImage(img, geometry.x(), geometry.y()); + grabPointerImage(img, static_cast(geometry.x() * scale), static_cast(geometry.y() * scale)); } return img; diff --git a/libkwineffects/kwineffects.h b/libkwineffects/kwineffects.h --- a/libkwineffects/kwineffects.h +++ b/libkwineffects/kwineffects.h @@ -2991,7 +2991,7 @@ { public: ScreenPaintData(); - ScreenPaintData(const QMatrix4x4 &projectionMatrix, const QRect &outputGeometry = QRect()); + ScreenPaintData(const QMatrix4x4 &projectionMatrix, const QRect &outputGeometry = QRect(), const QPoint &globalPos = QPoint(), const qreal screenScale = 1.0); ScreenPaintData(const ScreenPaintData &other); ~ScreenPaintData() override; /** @@ -3053,6 +3053,15 @@ * @since 5.9 */ QRect outputGeometry() const; + + /** + * @returns the ratio between the virtual geometry space and the rendering for the painted screen + * @since 5.19 + */ + qreal screenScale() const; + + QPoint globalPos() const; + private: class Private; QScopedPointer d; diff --git a/libkwineffects/kwineffects.cpp b/libkwineffects/kwineffects.cpp --- a/libkwineffects/kwineffects.cpp +++ b/libkwineffects/kwineffects.cpp @@ -423,20 +423,24 @@ public: QMatrix4x4 projectionMatrix; QRect outputGeometry; + qreal screenScale; + QPoint globalPos; }; ScreenPaintData::ScreenPaintData() : PaintData() , d(new Private()) { } -ScreenPaintData::ScreenPaintData(const QMatrix4x4 &projectionMatrix, const QRect &outputGeometry) +ScreenPaintData::ScreenPaintData(const QMatrix4x4 &projectionMatrix, const QRect &outputGeometry, const QPoint &globalPos, qreal screenScale) : PaintData() , d(new Private()) { d->projectionMatrix = projectionMatrix; d->outputGeometry = outputGeometry; + d->globalPos = globalPos; + d->screenScale = screenScale; } ScreenPaintData::~ScreenPaintData() = default; @@ -454,6 +458,8 @@ setRotationAngle(other.rotationAngle()); d->projectionMatrix = other.d->projectionMatrix; d->outputGeometry = other.d->outputGeometry; + d->globalPos = other.d->globalPos; + d->screenScale = other.d->screenScale; } ScreenPaintData &ScreenPaintData::operator=(const ScreenPaintData &rhs) @@ -469,6 +475,8 @@ setRotationAngle(rhs.rotationAngle()); d->projectionMatrix = rhs.d->projectionMatrix; d->outputGeometry = rhs.d->outputGeometry; + d->globalPos = rhs.d->globalPos; + d->screenScale = rhs.d->screenScale; return *this; } @@ -526,6 +534,16 @@ return d->outputGeometry; } +qreal ScreenPaintData::screenScale() const +{ + return d->screenScale; +} + +QPoint ScreenPaintData::globalPos() const +{ + return d->globalPos; +} + //**************************************** // Effect //**************************************** diff --git a/outputscreens.h b/outputscreens.h --- a/outputscreens.h +++ b/outputscreens.h @@ -42,6 +42,7 @@ bool isInternal(int screen) const override; QSizeF physicalSize(int screen) const override; QRect geometry(int screen) const override; + QPoint globalPos(int screen) const override; QSize size(int screen) const override; qreal scale(int screen) const override; float refreshRate(int screen) const override; diff --git a/outputscreens.cpp b/outputscreens.cpp --- a/outputscreens.cpp +++ b/outputscreens.cpp @@ -71,6 +71,14 @@ return QSize(); } +QPoint OutputScreens::globalPos(int screen) const +{ + if (AbstractOutput *output = findOutput(screen)) { + return output->globalPos(); + } + return QPoint(); +} + qreal OutputScreens::scale(int screen) const { if (AbstractOutput *output = findOutput(screen)) { 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 @@ -649,7 +649,7 @@ int mask = 0; updateProjectionMatrix(); - paintScreen(&mask, damage.intersected(geo), repaint, &update, &valid, projectionMatrix(), geo); // call generic implementation + paintScreen(&mask, damage.intersected(geo), repaint, &update, &valid, projectionMatrix(), geo, screens()->globalPos(i), screens()->scale(i)); // call generic implementation paintCursor(); GLVertexBuffer::streamingBuffer()->endOfFrame(); diff --git a/scene.h b/scene.h --- a/scene.h +++ b/scene.h @@ -213,7 +213,7 @@ void clearStackingOrder(); // shared implementation, starts painting the screen void paintScreen(int *mask, const QRegion &damage, const QRegion &repaint, - QRegion *updateRegion, QRegion *validRegion, const QMatrix4x4 &projection = QMatrix4x4(), const QRect &outputGeometry = QRect()); + QRegion *updateRegion, QRegion *validRegion, const QMatrix4x4 &projection = QMatrix4x4(), const QRect &outputGeometry = QRect(), const QPoint &globalPos = QPoint(), qreal scale = 1); // Render cursor texture in case hardware cursor is disabled/non-applicable virtual void paintCursor() = 0; friend class EffectsHandlerImpl; diff --git a/scene.cpp b/scene.cpp --- a/scene.cpp +++ b/scene.cpp @@ -105,7 +105,7 @@ // returns mask and possibly modified region void Scene::paintScreen(int* mask, const QRegion &damage, const QRegion &repaint, - QRegion *updateRegion, QRegion *validRegion, const QMatrix4x4 &projection, const QRect &outputGeometry) + QRegion *updateRegion, QRegion *validRegion, const QMatrix4x4 &projection, const QRect &outputGeometry, const QPoint &globalPos, qreal scale) { const QSize &screenSize = screens()->size(); const QRegion displayRegion(0, 0, screenSize.width(), screenSize.height()); @@ -145,7 +145,7 @@ paintBackground(region); } - ScreenPaintData data(projection, outputGeometry); + ScreenPaintData data(projection, outputGeometry, globalPos, scale); effects->paintScreen(*mask, region, data); foreach (Window *w, stacking_order) { diff --git a/screens.h b/screens.h --- a/screens.h +++ b/screens.h @@ -86,6 +86,11 @@ * @see size() */ virtual QSize size(int screen) const = 0; + /** + * @returns position of the @p screen in the global coordinates + * + */ + virtual QPoint globalPos(int screen) const = 0; /** * The highest scale() of all connected screens