scene: Expand surface damage if the surface is scaled

If the surface item's contents is scaled, i.e. its scale factor doesn't
match the output's scale, GL_LINEAR will be applied to smooth the
contents. The unfortunate thing is that it's possible some of the
changed pixels will bleed to the neighbor ones.

In order to handle that scenario better, this change makes the
SurfaceItem expand the damage if there's scale factor mismatch.

bufferSourceBox and bufferTransform properties were introduced to detect
if the surface contents is going to be scaled. bufferSourceBox covers
both crop transform from wp_viewport and scale factor from wl_surface.
bufferTransform is same as wl_surface's buffer transform property.
This commit is contained in:
Vlad Zahorodnii 2023-07-04 09:19:08 +03:00
parent b98ffaf785
commit 9e898c0e68
11 changed files with 138 additions and 18 deletions

View file

@ -99,36 +99,36 @@ QRectF applyOutputTransform(const QRectF &rect, const QSizeF &bounds, Output::Tr
switch (transform) {
case Output::Transform::Normal:
dest.setX(rect.x());
dest.setY(rect.y());
dest.moveLeft(rect.x());
dest.moveTop(rect.y());
break;
case Output::Transform::Rotated90:
dest.setX(bounds.height() - (rect.y() + rect.height()));
dest.setY(rect.x());
dest.moveLeft(bounds.height() - (rect.y() + rect.height()));
dest.moveTop(rect.x());
break;
case Output::Transform::Rotated180:
dest.setX(bounds.width() - (rect.x() + rect.width()));
dest.setY(bounds.height() - (rect.y() + rect.height()));
dest.moveLeft(bounds.width() - (rect.x() + rect.width()));
dest.moveTop(bounds.height() - (rect.y() + rect.height()));
break;
case Output::Transform::Rotated270:
dest.setX(rect.y());
dest.setY(bounds.width() - (rect.x() + rect.width()));
dest.moveLeft(rect.y());
dest.moveTop(bounds.width() - (rect.x() + rect.width()));
break;
case Output::Transform::Flipped:
dest.setX(bounds.width() - (rect.x() + rect.width()));
dest.setY(rect.y());
dest.moveLeft(bounds.width() - (rect.x() + rect.width()));
dest.moveTop(rect.y());
break;
case Output::Transform::Flipped90:
dest.setX(rect.y());
dest.setY(rect.x());
dest.moveLeft(rect.y());
dest.moveTop(rect.x());
break;
case Output::Transform::Flipped180:
dest.setX(rect.x());
dest.setY(bounds.height() - (rect.y() + rect.height()));
dest.moveLeft(rect.x());
dest.moveTop(bounds.height() - (rect.y() + rect.height()));
break;
case Output::Transform::Flipped270:
dest.setX(bounds.height() - (rect.y() + rect.height()));
dest.setY(bounds.width() - (rect.x() + rect.width()));
dest.moveLeft(bounds.height() - (rect.y() + rect.height()));
dest.moveTop(bounds.width() - (rect.x() + rect.width()));
break;
}

View file

@ -295,6 +295,13 @@ void Item::scheduleRepaint(const QRegion &region)
}
}
void Item::scheduleRepaint(SceneDelegate *delegate, const QRegion &region)
{
if (isVisible()) {
scheduleRepaintInternal(delegate, region);
}
}
void Item::scheduleRepaintInternal(const QRegion &region)
{
const QRegion globalRegion = mapToGlobal(region);
@ -308,6 +315,16 @@ void Item::scheduleRepaintInternal(const QRegion &region)
}
}
void Item::scheduleRepaintInternal(SceneDelegate *delegate, const QRegion &region)
{
const QRegion globalRegion = mapToGlobal(region);
const QRegion dirtyRegion = globalRegion & delegate->viewport();
if (!dirtyRegion.isEmpty()) {
m_repaints[delegate] += dirtyRegion;
delegate->layer()->loop()->scheduleRepaint(this);
}
}
void Item::scheduleFrame()
{
if (!isVisible()) {

View file

@ -104,6 +104,7 @@ public:
void scheduleRepaint(const QRectF &region);
void scheduleRepaint(const QRegion &region);
void scheduleRepaint(SceneDelegate *delegate, const QRegion &region);
void scheduleFrame();
QRegion repaints(SceneDelegate *delegate) const;
void resetRepaints(SceneDelegate *delegate);
@ -137,6 +138,7 @@ private:
void removeChild(Item *item);
void updateBoundingRect();
void scheduleRepaintInternal(const QRegion &region);
void scheduleRepaintInternal(SceneDelegate *delegate, const QRegion &region);
void markSortedChildItemsDirty();
bool computeEffectiveVisibility() const;

View file

@ -54,6 +54,11 @@ Output *SceneDelegate::output() const
return m_output;
}
qreal SceneDelegate::scale() const
{
return m_output ? m_output->scale() : 1.0;
}
QRect SceneDelegate::viewport() const
{
return m_output ? m_output->geometry() : m_scene->geometry();

View file

@ -26,6 +26,7 @@ public:
~SceneDelegate() override;
Output *output() const;
qreal scale() const;
QRect viewport() const;
QRegion repaints() const override;

View file

@ -5,6 +5,7 @@
*/
#include "scene/surfaceitem.h"
#include "scene/scene.h"
namespace KWin
{
@ -25,6 +26,36 @@ void SurfaceItem::setSurfaceToBufferMatrix(const QMatrix4x4 &matrix)
m_bufferToSurfaceMatrix = matrix.inverted();
}
QRectF SurfaceItem::bufferSourceBox() const
{
return m_bufferSourceBox;
}
void SurfaceItem::setBufferSourceBox(const QRectF &box)
{
m_bufferSourceBox = box;
}
Output::Transform SurfaceItem::bufferTransform() const
{
return m_bufferTransform;
}
void SurfaceItem::setBufferTransform(Output::Transform transform)
{
m_bufferTransform = transform;
}
QSize SurfaceItem::bufferSize() const
{
return m_bufferSize;
}
void SurfaceItem::setBufferSize(const QSize &size)
{
m_bufferSize = size;
}
QRegion SurfaceItem::mapFromBuffer(const QRegion &region) const
{
QRegion result;
@ -34,10 +65,42 @@ QRegion SurfaceItem::mapFromBuffer(const QRegion &region) const
return result;
}
static QRegion expandRegion(const QRegion &region, const QMargins &padding)
{
if (region.isEmpty()) {
return QRegion();
}
QRegion ret;
for (const QRect &rect : region) {
ret += rect.marginsAdded(padding);
}
return ret;
}
void SurfaceItem::addDamage(const QRegion &region)
{
m_damage += region;
scheduleRepaint(mapFromBuffer(region));
const QRectF sourceBox = applyOutputTransform(m_bufferSourceBox, m_bufferSize, m_bufferTransform);
const qreal xScale = sourceBox.width() / size().width();
const qreal yScale = sourceBox.height() / size().height();
const QRegion logicalDamage = mapFromBuffer(region);
const auto delegates = scene()->delegates();
for (SceneDelegate *delegate : delegates) {
QRegion delegateDamage = logicalDamage;
const qreal delegateScale = delegate->scale();
if (xScale != delegateScale || yScale != delegateScale) {
// Simplified version of ceil(ceil(0.5 * output_scale / surface_scale) / output_scale)
const int xPadding = std::ceil(0.5 / xScale);
const int yPadding = std::ceil(0.5 / yScale);
delegateDamage = expandRegion(delegateDamage, QMargins(xPadding, yPadding, xPadding, yPadding));
}
scheduleRepaint(delegate, delegateDamage);
}
Q_EMIT damaged();
}

View file

@ -26,6 +26,15 @@ public:
QMatrix4x4 surfaceToBufferMatrix() const;
void setSurfaceToBufferMatrix(const QMatrix4x4 &matrix);
QRectF bufferSourceBox() const;
void setBufferSourceBox(const QRectF &box);
Output::Transform bufferTransform() const;
void setBufferTransform(Output::Transform transform);
QSize bufferSize() const;
void setBufferSize(const QSize &size);
QRegion mapFromBuffer(const QRegion &region) const;
void addDamage(const QRegion &region);
@ -55,6 +64,9 @@ protected:
WindowQuadList buildQuads() const override;
QRegion m_damage;
Output::Transform m_bufferTransform;
QRectF m_bufferSourceBox;
QSize m_bufferSize;
std::unique_ptr<SurfacePixmap> m_pixmap;
std::unique_ptr<SurfacePixmap> m_previousPixmap;
QMatrix4x4 m_surfaceToBufferMatrix;

View file

@ -22,6 +22,8 @@ SurfaceItemInternal::SurfaceItemInternal(InternalWindow *window, Scene *scene, I
this, &SurfaceItemInternal::handleBufferGeometryChanged);
setSize(window->bufferGeometry().size());
setBufferSourceBox(QRectF(QPointF(0, 0), window->bufferGeometry().size()));
setBufferSize((window->bufferGeometry().size() * window->bufferScale()).toSize());
// The device pixel ratio of the internal window is static.
QMatrix4x4 surfaceToBufferMatrix;
@ -50,6 +52,8 @@ void SurfaceItemInternal::handleBufferGeometryChanged(const QRectF &old)
discardPixmap();
}
setSize(m_window->bufferGeometry().size());
setBufferSourceBox(QRectF(QPointF(0, 0), m_window->bufferGeometry().size()));
setBufferSize((m_window->bufferGeometry().size() * m_window->bufferScale()).toSize());
}
SurfacePixmapInternal::SurfacePixmapInternal(SurfaceItemInternal *item, QObject *parent)

View file

@ -26,7 +26,7 @@ SurfaceItemWayland::SurfaceItemWayland(KWaylandServer::SurfaceInterface *surface
connect(surface, &KWaylandServer::SurfaceInterface::sizeChanged,
this, &SurfaceItemWayland::handleSurfaceSizeChanged);
connect(surface, &KWaylandServer::SurfaceInterface::bufferSizeChanged,
this, &SurfaceItemWayland::discardPixmap);
this, &SurfaceItemWayland::handleBufferSizeChanged);
connect(surface, &KWaylandServer::SurfaceInterface::childSubSurfacesChanged,
this, &SurfaceItemWayland::handleChildSubSurfacesChanged);
@ -51,6 +51,9 @@ SurfaceItemWayland::SurfaceItemWayland(KWaylandServer::SurfaceInterface *surface
handleChildSubSurfacesChanged();
setSize(surface->size());
setBufferTransform(surface->bufferTransform());
setBufferSourceBox(surface->bufferSourceBox());
setBufferSize(surface->bufferSize());
setSurfaceToBufferMatrix(surface->surfaceToBufferMatrix());
}
@ -74,6 +77,8 @@ KWaylandServer::SurfaceInterface *SurfaceItemWayland::surface() const
void SurfaceItemWayland::handleSurfaceToBufferMatrixChanged()
{
setBufferTransform(m_surface->bufferTransform());
setBufferSourceBox(m_surface->bufferSourceBox());
setSurfaceToBufferMatrix(m_surface->surfaceToBufferMatrix());
discardQuads();
discardPixmap();
@ -84,6 +89,12 @@ void SurfaceItemWayland::handleSurfaceSizeChanged()
setSize(m_surface->size());
}
void SurfaceItemWayland::handleBufferSizeChanged()
{
setBufferSize(m_surface->bufferSize());
discardPixmap();
}
void SurfaceItemWayland::handleSurfaceCommitted()
{
if (m_surface->hasFrameCallbacks()) {

View file

@ -40,6 +40,7 @@ private Q_SLOTS:
void handleSurfaceToBufferMatrixChanged();
void handleSurfaceCommitted();
void handleSurfaceSizeChanged();
void handleBufferSizeChanged();
void handleChildSubSurfaceRemoved(KWaylandServer::SubSurfaceInterface *child);
void handleChildSubSurfacesChanged();

View file

@ -35,6 +35,8 @@ SurfaceItemX11::SurfaceItemX11(X11Window *window, Scene *scene, Item *parent)
}
setSize(window->bufferGeometry().size());
setBufferSourceBox(QRectF(QPointF(0, 0), window->bufferGeometry().size()));
setBufferSize(window->bufferGeometry().size().toSize());
}
SurfaceItemX11::~SurfaceItemX11()
@ -144,6 +146,8 @@ void SurfaceItemX11::handleBufferGeometryChanged(const QRectF &old)
discardPixmap();
}
setSize(m_window->bufferGeometry().size());
setBufferSourceBox(QRectF(QPointF(0, 0), m_window->bufferGeometry().size()));
setBufferSize(m_window->bufferGeometry().size().toSize());
}
void SurfaceItemX11::handleShapeChanged()