Commit 221f114d authored by Vlad Zahorodnii's avatar Vlad Zahorodnii Committed by Merge Service
Browse files

effects/zoom: Render scene in an offscreen texture

This fixes integration of the zoom effect with the blur effect and qtquick
effects such as overview or window view.

The main con of the proposed solution is that offscreen rendering can be
a bit overkill. But on the other hand, it's the most robust way to
ensure that fullscreen effects integrate correctly with the zoom effect.
For example, without it, the quick scene effect would need to figure out
what screen views to paint for the given screen. There are also more
finicky cases, e.g. the screen transform effect.

BUG: 447002
BUG: 447670
BUG: 453467
Tested-by: Merge Service <!2451>
Part-of: <!2451>
parent cc487d42
Pipeline #180355 passed with stage
in 35 minutes and 59 seconds
......@@ -117,6 +117,7 @@ ZoomEffect::ZoomEffect()
connect(&timeline, &QTimeLine::frameChanged, this, &ZoomEffect::timelineFrameChanged);
connect(effects, &EffectsHandler::mouseChanged, this, &ZoomEffect::slotMouseChanged);
connect(effects, &EffectsHandler::windowDamaged, this, &ZoomEffect::slotWindowDamaged);
connect(effects, &EffectsHandler::screenRemoved, this, &ZoomEffect::slotScreenRemoved);
#if HAVE_ACCESSIBILITY
m_accessibilityIntegration = new ZoomAccessibilityIntegration(this);
......@@ -131,6 +132,7 @@ ZoomEffect::~ZoomEffect()
{
// switch off and free resources
showCursor();
qDeleteAll(m_offscreenData);
// Save the zoom value.
ZoomConfig::setInitialZoom(target_zoom);
ZoomConfig::self()->save();
......@@ -261,8 +263,59 @@ void ZoomEffect::prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseco
effects->prePaintScreen(data, presentTime);
}
ZoomEffect::OffscreenData *ZoomEffect::ensureOffscreenData(EffectScreen *screen)
{
const QRect rect = effects->renderTargetRect();
const qreal devicePixelRatio = effects->renderTargetScale();
const QSize nativeSize = rect.size() * devicePixelRatio;
OffscreenData *&data = m_offscreenData[effects->waylandDisplay() ? screen : nullptr];
if (!data) {
data = new OffscreenData;
}
if (!data->texture || data->texture->size() != nativeSize) {
data->texture.reset(new GLTexture(GL_RGBA8, nativeSize));
data->texture->setFilter(GL_NEAREST);
data->texture->setWrapMode(GL_CLAMP_TO_EDGE);
data->framebuffer.reset(new GLFramebuffer(data->texture.data()));
}
if (!data->vbo || data->viewport != rect) {
data->vbo.reset(new GLVertexBuffer(GLVertexBuffer::Static));
data->viewport = rect;
QVector<float> verts;
QVector<float> texcoords;
// The v-coordinate is flipped because projection matrix is "flipped."
texcoords << 1.0 << 1.0;
verts << rect.x() + rect.width() << rect.y();
texcoords << 0.0 << 1.0;
verts << rect.x() << rect.y();
texcoords << 0.0 << 0.0;
verts << rect.x() << rect.y() + rect.height();
texcoords << 1.0 << 0.0;
verts << rect.x() + rect.width() << rect.y() + rect.height();
texcoords << 1.0 << 1.0;
verts << rect.x() + rect.width() << rect.y();
texcoords << 0.0 << 0.0;
verts << rect.x() << rect.y() + rect.height();
data->vbo->setData(6, 2, verts.constData(), texcoords.constData());
}
return data;
}
void ZoomEffect::paintScreen(int mask, const QRegion &region, ScreenPaintData &data)
{
OffscreenData *offscreenData = ensureOffscreenData(data.screen());
// Render the scene in an offscreen texture and then upscale it.
GLFramebuffer::pushFramebuffer(offscreenData->framebuffer.data());
effects->paintScreen(mask, region, data);
GLFramebuffer::popFramebuffer();
if (zoom != 1.0) {
data *= QVector2D(zoom, zoom);
const QSize screenSize = effects->virtualScreenSize();
......@@ -324,37 +377,52 @@ void ZoomEffect::paintScreen(int mask, const QRegion &region, ScreenPaintData &d
prevPoint = focusPoint;
}
}
}
effects->paintScreen(mask, region, data);
// Render transformed offscreen texture.
glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT);
if (zoom != 1.0 && mousePointer != MousePointerHide) {
GLTexture *cursorTexture = ensureCursorTexture();
if (cursorTexture) {
const auto cursor = effects->cursorImage();
QMatrix4x4 matrix;
matrix.translate(data.translation());
matrix.scale(data.scale());
auto shader = ShaderManager::instance()->pushShader(ShaderTrait::MapTexture);
shader->setUniform(GLShader::ModelViewProjectionMatrix, data.projectionMatrix() * matrix);
for (OffscreenData *data : std::as_const(m_offscreenData)) {
data->texture->bind();
data->vbo->render(GL_TRIANGLES);
data->texture->unbind();
}
ShaderManager::instance()->popShader();
if (mousePointer != MousePointerHide) {
// Draw the mouse-texture at the position matching to zoomed-in image of the desktop. Hiding the
// previous mouse-cursor and drawing our own fake mouse-cursor is needed to be able to scale the
// mouse-cursor up and to re-position those mouse-cursor to match to the chosen zoom-level.
QSize cursorSize = cursor.image().size() / cursor.image().devicePixelRatio();
if (mousePointer == MousePointerScale) {
cursorSize *= zoom;
}
const QPoint p = effects->cursorPos() - cursor.hotSpot();
QRect rect(p * zoom + QPoint(data.xTranslation(), data.yTranslation()), cursorSize);
cursorTexture->bind();
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
auto s = ShaderManager::instance()->pushShader(ShaderTrait::MapTexture);
QMatrix4x4 mvp = data.projectionMatrix();
mvp.translate(rect.x(), rect.y());
s->setUniform(GLShader::ModelViewProjectionMatrix, mvp);
cursorTexture->render(rect);
ShaderManager::instance()->popShader();
cursorTexture->unbind();
glDisable(GL_BLEND);
GLTexture *cursorTexture = ensureCursorTexture();
if (cursorTexture) {
const auto cursor = effects->cursorImage();
QSize cursorSize = cursor.image().size() / cursor.image().devicePixelRatio();
if (mousePointer == MousePointerScale) {
cursorSize *= zoom;
}
const QPoint p = effects->cursorPos() - cursor.hotSpot();
QRect rect(p * zoom + QPoint(data.xTranslation(), data.yTranslation()), cursorSize);
cursorTexture->bind();
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
auto s = ShaderManager::instance()->pushShader(ShaderTrait::MapTexture);
QMatrix4x4 mvp = data.projectionMatrix();
mvp.translate(rect.x(), rect.y());
s->setUniform(GLShader::ModelViewProjectionMatrix, mvp);
cursorTexture->render(rect);
ShaderManager::instance()->popShader();
cursorTexture->unbind();
glDisable(GL_BLEND);
}
}
}
}
......@@ -502,6 +570,14 @@ void ZoomEffect::slotWindowDamaged()
}
}
void ZoomEffect::slotScreenRemoved(EffectScreen *screen)
{
if (OffscreenData *offscreenData = m_offscreenData.take(screen)) {
effects->makeOpenGLContextCurrent();
delete offscreenData;
}
}
void ZoomEffect::moveFocus(const QPoint &point)
{
if (zoom == 1.0) {
......
......@@ -24,7 +24,9 @@ namespace KWin
class ZoomAccessibilityIntegration;
#endif
class GLFramebuffer;
class GLTexture;
class GLVertexBuffer;
class ZoomEffect
: public Effect
......@@ -94,6 +96,7 @@ private Q_SLOTS:
Qt::MouseButtons buttons, Qt::MouseButtons oldbuttons,
Qt::KeyboardModifiers modifiers, Qt::KeyboardModifiers oldmodifiers);
void slotWindowDamaged();
void slotScreenRemoved(EffectScreen *screen);
private:
void showCursor();
......@@ -101,7 +104,16 @@ private:
void moveZoom(int x, int y);
private:
struct OffscreenData
{
QScopedPointer<GLTexture> texture;
QScopedPointer<GLFramebuffer> framebuffer;
QScopedPointer<GLVertexBuffer> vbo;
QRect viewport;
};
GLTexture *ensureCursorTexture();
OffscreenData *ensureOffscreenData(EffectScreen *screen);
void markCursorTextureDirty();
#if HAVE_ACCESSIBILITY
......@@ -138,6 +150,7 @@ private:
int xMove, yMove;
double moveFactor;
std::chrono::milliseconds lastPresentTime;
QHash<EffectScreen *, OffscreenData *> m_offscreenData;
};
} // namespace
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment