kwineffects: Introduce DeformEffect

DeformEffect is the base class for effects that transform the window
quad grid, e.g. wobbly windows or magic lamp. The main difference
between normal effects and offscreen effects is that the latter renders
animated windows into offscreen textures which are later deformed.

The offscreen texture approach is superior to the half-pixel correction
approach as it produces more visually appealing results. Even with
half-pixel correction, you're still going to see jagged lines between
the main surface and the server-side decoration or drop-shadow.

It is also needed to reduce the number of usages of WindowPaintData::quads
which is needed to move forward with the scene redesign goal.
This commit is contained in:
Vlad Zahorodnii 2021-05-26 17:40:47 +03:00
parent d4ade78aac
commit 5255ebf8d0
3 changed files with 315 additions and 0 deletions

View file

@ -38,6 +38,7 @@ install(TARGETS kwinxrenderutils EXPORT KWinEffectsTargets ${KDE_INSTALL_TARGETS
set(kwin_EFFECTSLIB_SRCS
anidata.cpp
kwinanimationeffect.cpp
kwindeformeffect.cpp
kwineffectquickview.cpp
kwineffects.cpp
logging.cpp
@ -95,6 +96,7 @@ install(FILES
${CMAKE_CURRENT_BINARY_DIR}/kwinglutils_export.h
${CMAKE_CURRENT_BINARY_DIR}/kwinxrenderutils_export.h
kwinanimationeffect.h
kwindeformeffect.h
kwineffectquickview.h
kwineffects.h
kwinglobals.h

View file

@ -0,0 +1,241 @@
/*
SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "kwindeformeffect.h"
#include "kwingltexture.h"
#include "kwinglutils.h"
namespace KWin
{
struct DeformOffscreenData
{
QScopedPointer<GLTexture> texture;
QScopedPointer<GLRenderTarget> renderTarget;
bool isDirty = true;
};
class DeformEffectPrivate
{
public:
QHash<EffectWindow *, DeformOffscreenData *> windows;
QMetaObject::Connection windowExpandedGeometryChangedConnection;
QMetaObject::Connection windowDamagedConnection;
QMetaObject::Connection windowDeletedConnection;
void paint(EffectWindow *window, GLTexture *texture, const QRegion &region,
const WindowPaintData &data, const WindowQuadList &quads);
GLTexture *maybeRender(EffectWindow *window, DeformOffscreenData *offscreenData);
};
DeformEffect::DeformEffect(QObject *parent)
: Effect(parent)
, d(new DeformEffectPrivate)
{
}
DeformEffect::~DeformEffect()
{
qDeleteAll(d->windows);
}
bool DeformEffect::supported()
{
return effects->isOpenGLCompositing();
}
static void allocateOffscreenData(EffectWindow *window, DeformOffscreenData *offscreenData)
{
const QRect geometry = window->expandedGeometry();
offscreenData->texture.reset(new GLTexture(GL_RGBA8, geometry.size()));
offscreenData->texture->setFilter(GL_LINEAR);
offscreenData->texture->setWrapMode(GL_CLAMP_TO_EDGE);
offscreenData->renderTarget.reset(new GLRenderTarget(*offscreenData->texture));
offscreenData->isDirty = true;
}
void DeformEffect::redirect(EffectWindow *window)
{
DeformOffscreenData *&offscreenData = d->windows[window];
if (offscreenData) {
return;
}
effects->makeOpenGLContextCurrent();
offscreenData = new DeformOffscreenData;
allocateOffscreenData(window, offscreenData);
if (d->windows.count() == 1) {
setupConnections();
}
}
void DeformEffect::unredirect(EffectWindow *window)
{
delete d->windows.take(window);
if (d->windows.isEmpty()) {
destroyConnections();
}
}
void DeformEffect::deform(EffectWindow *window, int mask, WindowPaintData &data, WindowQuadList &quads)
{
Q_UNUSED(window)
Q_UNUSED(mask)
Q_UNUSED(data)
Q_UNUSED(quads)
}
GLTexture *DeformEffectPrivate::maybeRender(EffectWindow *window, DeformOffscreenData *offscreenData)
{
if (offscreenData->isDirty) {
GLRenderTarget::pushRenderTarget(offscreenData->renderTarget.data());
glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT);
const QRect geometry = window->expandedGeometry();
QMatrix4x4 projectionMatrix;
projectionMatrix.ortho(QRect(0, 0, geometry.width(), geometry.height()));
WindowPaintData data(window);
data.setXTranslation(-geometry.x());
data.setYTranslation(-geometry.y());
data.setOpacity(1.0);
data.setProjectionMatrix(projectionMatrix);
const int mask = Effect::PAINT_WINDOW_TRANSFORMED | Effect::PAINT_WINDOW_TRANSLUCENT;
effects->drawWindow(window, mask, infiniteRegion(), data);
GLRenderTarget::popRenderTarget();
offscreenData->isDirty = false;
}
return offscreenData->texture.data();
}
void DeformEffectPrivate::paint(EffectWindow *window, GLTexture *texture, const QRegion &region,
const WindowPaintData &data, const WindowQuadList &quads)
{
ShaderBinder binder(ShaderTrait::MapTexture | ShaderTrait::Modulate | ShaderTrait::AdjustSaturation);
GLShader *shader = binder.shader();
const bool indexedQuads = GLVertexBuffer::supportsIndexedQuads();
const GLenum primitiveType = indexedQuads ? GL_QUADS : GL_TRIANGLES;
const int verticesPerQuad = indexedQuads ? 4 : 6;
const GLVertexAttrib attribs[] = {
{ VA_Position, 2, GL_FLOAT, offsetof(GLVertex2D, position) },
{ VA_TexCoord, 2, GL_FLOAT, offsetof(GLVertex2D, texcoord) },
};
GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer();
vbo->reset();
vbo->setAttribLayout(attribs, 2, sizeof(GLVertex2D));
const size_t size = verticesPerQuad * quads.count() * sizeof(GLVertex2D);
GLVertex2D *map = static_cast<GLVertex2D *>(vbo->map(size));
quads.makeInterleavedArrays(primitiveType, map, texture->matrix(NormalizedCoordinates));
vbo->unmap();
vbo->bindArrays();
glEnable(GL_SCISSOR_TEST);
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
const qreal rgb = data.brightness() * data.opacity();
const qreal a = data.opacity();
const QRect screenRect = effects->virtualScreenGeometry();
QMatrix4x4 mvp;
mvp.ortho(0, screenRect.width(), screenRect.height(), 0, 0, 65535);
mvp.translate(window->x(), window->y());
shader->setUniform(GLShader::ModelViewProjectionMatrix, mvp);
shader->setUniform(GLShader::ModulationConstant, QVector4D(rgb, rgb, rgb, a));
shader->setUniform(GLShader::Saturation, data.saturation());
texture->bind();
vbo->draw(region, primitiveType, 0, verticesPerQuad * quads.count(), true);
texture->unbind();
glDisable(GL_BLEND);
glDisable(GL_SCISSOR_TEST);
vbo->unbindArrays();
}
void DeformEffect::drawWindow(EffectWindow *window, int mask, const QRegion& region, WindowPaintData &data)
{
DeformOffscreenData *offscreenData = d->windows.value(window);
if (!offscreenData) {
effects->drawWindow(window, mask, region, data);
return;
}
const QRect expandedGeometry = window->expandedGeometry();
const QRect frameGeometry = window->frameGeometry();
QRectF visibleRect = expandedGeometry;
visibleRect.moveTopLeft(expandedGeometry.topLeft() - frameGeometry.topLeft());
WindowQuad quad(WindowQuadContents);
quad[0] = WindowVertex(visibleRect.topLeft(), QPointF(0, 0));
quad[1] = WindowVertex(visibleRect.topRight(), QPointF(1, 0));
quad[2] = WindowVertex(visibleRect.bottomRight(), QPointF(1, 1));
quad[3] = WindowVertex(visibleRect.bottomLeft(), QPointF(0, 1));
WindowQuadList quads;
quads.append(quad);
deform(window, mask, data, quads);
GLTexture *texture = d->maybeRender(window, offscreenData);
d->paint(window, texture, region, data, quads);
}
void DeformEffect::handleWindowGeometryChanged(EffectWindow *window)
{
DeformOffscreenData *offscreenData = d->windows.value(window);
if (offscreenData) {
const QRect geometry = window->expandedGeometry();
if (offscreenData->texture->size() != geometry.size()) {
effects->makeOpenGLContextCurrent();
allocateOffscreenData(window, offscreenData);
}
}
}
void DeformEffect::handleWindowDamaged(EffectWindow *window)
{
DeformOffscreenData *offscreenData = d->windows.value(window);
if (offscreenData) {
offscreenData->isDirty = true;
}
}
void DeformEffect::handleWindowDeleted(EffectWindow *window)
{
unredirect(window);
}
void DeformEffect::setupConnections()
{
d->windowExpandedGeometryChangedConnection =
connect(effects, &EffectsHandler::windowExpandedGeometryChanged, this, &DeformEffect::handleWindowGeometryChanged);
d->windowDamagedConnection =
connect(effects, &EffectsHandler::windowDamaged, this, &DeformEffect::handleWindowDamaged);
d->windowDeletedConnection =
connect(effects, &EffectsHandler::windowDeleted, this, &DeformEffect::handleWindowDeleted);
}
void DeformEffect::destroyConnections()
{
disconnect(d->windowExpandedGeometryChangedConnection);
disconnect(d->windowDamagedConnection);
disconnect(d->windowDeletedConnection);
d->windowExpandedGeometryChangedConnection = {};
d->windowDamagedConnection = {};
d->windowDeletedConnection = {};
}
} // namespace KWin

View file

@ -0,0 +1,72 @@
/*
SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "kwineffects.h"
namespace KWin
{
class DeformEffectPrivate;
/**
* The DeformEffect class is the base class for effects that paint deformed windows.
*
* Under the hood, the DeformEffect will paint the window into an offscreen texture,
* which will be mapped onto transformed window quad grid later on.
*
* The redirect() function must be called when the effect wants to transform a window.
* Once the effect is no longer interested in the window, the unredirect() function
* must be called.
*
* If a window is redirected into offscreen texture, the deform() function will be
* called with the window quads that can be mutated by the effect. The effect can
* sub-divide, remove, or transform the window quads.
*/
class KWINEFFECTS_EXPORT DeformEffect : public Effect
{
Q_OBJECT
public:
explicit DeformEffect(QObject *parent = nullptr);
~DeformEffect() override;
static bool supported();
private:
void drawWindow(EffectWindow *window, int mask, const QRegion& region, WindowPaintData &data) override;
protected:
/**
* This function must be called when the effect wants to animate the specified
* @a window.
*/
void redirect(EffectWindow *window);
/**
* This function must be called when the effect is done animating the specified
* @a window. The window will be automatically unredirected if it's deleted.
*/
void unredirect(EffectWindow *window);
/**
* Override this function to transform the window quad grid of the given window.
*/
virtual void deform(EffectWindow *window, int mask, WindowPaintData &data, WindowQuadList &quads);
private Q_SLOTS:
void handleWindowGeometryChanged(EffectWindow *window);
void handleWindowDamaged(EffectWindow *window);
void handleWindowDeleted(EffectWindow *window);
private:
void setupConnections();
void destroyConnections();
QScopedPointer<DeformEffectPrivate> d;
};
} // namespace KWin