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:
parent
d4ade78aac
commit
5255ebf8d0
3 changed files with 315 additions and 0 deletions
|
@ -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
|
||||
|
|
241
src/libkwineffects/kwindeformeffect.cpp
Normal file
241
src/libkwineffects/kwindeformeffect.cpp
Normal 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 ®ion,
|
||||
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 ®ion,
|
||||
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
|
72
src/libkwineffects/kwindeformeffect.h
Normal file
72
src/libkwineffects/kwindeformeffect.h
Normal 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
|
Loading…
Reference in a new issue