From 5255ebf8d037e2d188dd7d573e78126df03bdc5a Mon Sep 17 00:00:00 2001 From: Vlad Zahorodnii Date: Wed, 26 May 2021 17:40:47 +0300 Subject: [PATCH] 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. --- src/libkwineffects/CMakeLists.txt | 2 + src/libkwineffects/kwindeformeffect.cpp | 241 ++++++++++++++++++++++++ src/libkwineffects/kwindeformeffect.h | 72 +++++++ 3 files changed, 315 insertions(+) create mode 100644 src/libkwineffects/kwindeformeffect.cpp create mode 100644 src/libkwineffects/kwindeformeffect.h diff --git a/src/libkwineffects/CMakeLists.txt b/src/libkwineffects/CMakeLists.txt index f849119ee9..436551ef0c 100644 --- a/src/libkwineffects/CMakeLists.txt +++ b/src/libkwineffects/CMakeLists.txt @@ -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 diff --git a/src/libkwineffects/kwindeformeffect.cpp b/src/libkwineffects/kwindeformeffect.cpp new file mode 100644 index 0000000000..dcd46408e9 --- /dev/null +++ b/src/libkwineffects/kwindeformeffect.cpp @@ -0,0 +1,241 @@ +/* + SPDX-FileCopyrightText: 2021 Vlad Zahorodnii + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "kwindeformeffect.h" +#include "kwingltexture.h" +#include "kwinglutils.h" + +namespace KWin +{ + +struct DeformOffscreenData +{ + QScopedPointer texture; + QScopedPointer renderTarget; + bool isDirty = true; +}; + +class DeformEffectPrivate +{ +public: + QHash 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(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 diff --git a/src/libkwineffects/kwindeformeffect.h b/src/libkwineffects/kwindeformeffect.h new file mode 100644 index 0000000000..54333c589c --- /dev/null +++ b/src/libkwineffects/kwindeformeffect.h @@ -0,0 +1,72 @@ +/* + SPDX-FileCopyrightText: 2021 Vlad Zahorodnii + + 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 d; +}; + +} // namespace KWin