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