diff --git a/autotests/integration/CMakeLists.txt b/autotests/integration/CMakeLists.txt --- a/autotests/integration/CMakeLists.txt +++ b/autotests/integration/CMakeLists.txt @@ -71,6 +71,7 @@ integrationTest(NAME testQuickTiling SRCS quick_tiling_test.cpp LIBS XCB::ICCCM) integrationTest(NAME testGlobalShortcuts SRCS globalshortcuts_test.cpp LIBS XCB::ICCCM) integrationTest(NAME testSceneQPainter SRCS scene_qpainter_test.cpp LIBS XCB::ICCCM) + integrationTest(NAME testSceneQPainterShadow SRCS scene_qpainter_shadow_test.cpp LIBS XCB::ICCCM) if (KWIN_BUILD_ACTIVITIES) integrationTest(NAME testActivities SRCS activities_test.cpp LIBS XCB::ICCCM) diff --git a/autotests/integration/kwin_wayland_test.h b/autotests/integration/kwin_wayland_test.h --- a/autotests/integration/kwin_wayland_test.h +++ b/autotests/integration/kwin_wayland_test.h @@ -38,6 +38,7 @@ class PointerConstraints; class Seat; class ServerSideDecorationManager; +class ShadowManager; class Shell; class ShellSurface; class ShmPool; @@ -85,7 +86,8 @@ WindowManagement = 1 << 3, PointerConstraints = 1 << 4, IdleInhibition = 1 << 5, - AppMenu = 1 << 6 + AppMenu = 1 << 6, + ShadowManager = 1 << 7 }; Q_DECLARE_FLAGS(AdditionalWaylandInterfaces, AdditionalWaylandInterface) /** @@ -106,6 +108,7 @@ KWayland::Client::ConnectionThread *waylandConnection(); KWayland::Client::Compositor *waylandCompositor(); +KWayland::Client::ShadowManager *waylandShadowManager(); KWayland::Client::Shell *waylandShell(); KWayland::Client::ShmPool *waylandShmPool(); KWayland::Client::Seat *waylandSeat(); diff --git a/autotests/integration/scene_qpainter_shadow_test.cpp b/autotests/integration/scene_qpainter_shadow_test.cpp new file mode 100644 --- /dev/null +++ b/autotests/integration/scene_qpainter_shadow_test.cpp @@ -0,0 +1,817 @@ +/******************************************************************** +KWin - the KDE window manager +This file is part of the KDE project. + +Copyright (C) 2018 Vlad Zagorodniy + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "kwin_wayland_test.h" + +#include "composite.h" +#include "effect_builtins.h" +#include "effectloader.h" +#include "effects.h" +#include "platform.h" +#include "plugins/scenes/qpainter/scene_qpainter.h" +#include "shadow.h" +#include "shell_client.h" +#include "wayland_server.h" +#include "workspace.h" + +using namespace KWin; +using namespace KWayland::Client; + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_scene_qpainter_shadow-0"); + +class SceneQPainterShadowTest : public QObject +{ + Q_OBJECT +public: + SceneQPainterShadowTest() {} + +private Q_SLOTS: + void initTestCase(); + void cleanup(); + + void testShadowTileOverlaps_data(); + void testShadowTileOverlaps(); + void testShadowTextureReconstruction(); + +}; + + +/////////////////////////////////////////////////////////// +// Helpers. +/////////////////////////////////////////////////////////// + +inline bool compareDoubles(double a, double b, double eps = 1e-5) +{ + if (a == b) { + return true; + } + double diff = std::fabs(a - b); + if (a == 0 || b == 0) { + return diff < eps; + } + return diff / std::max(a, b) < eps; +} + +inline bool compareQuads(const WindowQuad &a, const WindowQuad &b) +{ + for (int i = 0; i < 4; i++) { + if (compareDoubles(a[i].x(), b[i].x()) + && compareDoubles(a[i].y(), b[i].y()) + && compareDoubles(a[i].textureX(), b[i].textureX()) + && compareDoubles(a[i].textureY(), b[i].textureY())) { + return true; + } + } + return false; +} + +inline WindowQuad makeShadowQuad(const QRectF &geo, qreal tx1, qreal ty1, qreal tx2, qreal ty2) +{ + WindowQuad quad(WindowQuadShadow); + quad[0] = WindowVertex(geo.left(), geo.top(), tx1, ty1); + quad[1] = WindowVertex(geo.right(), geo.top(), tx2, ty1); + quad[2] = WindowVertex(geo.right(), geo.bottom(), tx2, ty2); + quad[3] = WindowVertex(geo.left(), geo.bottom(), tx1, ty2); + return quad; +} + +// Need this one because WindowQuadList isn't registered with Q_DECLARE_METATYPE +class WindowQuadListWrapper +{ +public: + WindowQuadListWrapper() {} + WindowQuadListWrapper(const WindowQuadList &quadList) + : m_quadList(quadList) {} + WindowQuadListWrapper(const WindowQuadListWrapper &other) + : m_quadList(other.m_quadList) {} + ~WindowQuadListWrapper() {} + + WindowQuadList &quadList() { return m_quadList; } + +private: + WindowQuadList m_quadList; +}; +Q_DECLARE_METATYPE(WindowQuadListWrapper); + +/////////////////////////////////////////////////////////// +// End of helpers. +/////////////////////////////////////////////////////////// + +void SceneQPainterShadowTest::initTestCase() +{ + // Copied from scene_qpainter_test.cpp + + qRegisterMetaType(); + qRegisterMetaType(); + QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); + QVERIFY(workspaceCreatedSpy.isValid()); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); + + // disable all effects - we don't want to have it interact with the rendering + auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + KConfigGroup plugins(config, QStringLiteral("Plugins")); + ScriptedEffectLoader loader; + const auto builtinNames = BuiltInEffects::availableEffectNames() << loader.listOfKnownEffects(); + for (QString name : builtinNames) { + plugins.writeEntry(name + QStringLiteral("Enabled"), false); + } + + config->sync(); + kwinApp()->setConfig(config); + + if (!QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("icons/DMZ-White/index.theme")).isEmpty()) { + qputenv("XCURSOR_THEME", QByteArrayLiteral("DMZ-White")); + } else { + // might be vanilla-dmz (e.g. Arch, FreeBSD) + qputenv("XCURSOR_THEME", QByteArrayLiteral("Vanilla-DMZ")); + } + qputenv("XCURSOR_SIZE", QByteArrayLiteral("24")); + qputenv("KWIN_COMPOSE", QByteArrayLiteral("Q")); + + kwinApp()->start(); + QVERIFY(workspaceCreatedSpy.wait()); + QVERIFY(KWin::Compositor::self()); + + // Add directory with fake decorations to the plugin search path. + QCoreApplication::addLibraryPath( + QDir(QCoreApplication::applicationDirPath()).absoluteFilePath("fakes") + ); + + // Change decoration theme. + KConfigGroup group = kwinApp()->config()->group("org.kde.kdecoration2"); + group.writeEntry("library", "org.kde.test.fakedecowithshadows"); + group.sync(); + Workspace::self()->slotReconfigure(); +} + +void SceneQPainterShadowTest::cleanup() +{ + Test::destroyWaylandConnection(); +} + +namespace { + const int SHADOW_SIZE = 128; + + const int SHADOW_OFFSET_TOP = 64; + const int SHADOW_OFFSET_LEFT = 48; + + // NOTE: We assume deco shadows are generated with blur so that's + // why there is 4, 1 is the size of the inner shadow rect. + const int SHADOW_TEXTURE_WIDTH = 4 * SHADOW_SIZE + 1; + const int SHADOW_TEXTURE_HEIGHT = 4 * SHADOW_SIZE + 1; + + const int SHADOW_PADDING_TOP = SHADOW_SIZE - SHADOW_OFFSET_TOP; + const int SHADOW_PADDING_RIGHT = SHADOW_SIZE + SHADOW_OFFSET_LEFT; + const int SHADOW_PADDING_BOTTOM = SHADOW_SIZE + SHADOW_OFFSET_TOP; + const int SHADOW_PADDING_LEFT = SHADOW_SIZE - SHADOW_OFFSET_LEFT; + + const QRectF SHADOW_INNER_RECT(2 * SHADOW_SIZE, 2 * SHADOW_SIZE, 1, 1); +} + +void SceneQPainterShadowTest::testShadowTileOverlaps_data() +{ + QTest::addColumn("windowSize"); + QTest::addColumn("expectedQuadsWrapper"); + + // Precompute shadow tile geometries(in texture's space). + QRectF topLeftTile( + 0, + 0, + SHADOW_INNER_RECT.x(), + SHADOW_INNER_RECT.y()); + QRectF topRightTile( + SHADOW_INNER_RECT.right(), + 0, + SHADOW_TEXTURE_WIDTH - SHADOW_INNER_RECT.right(), + SHADOW_INNER_RECT.y()); + QRectF topTile(topLeftTile.topRight(), topRightTile.bottomLeft()); + + QRectF bottomLeftTile( + 0, + SHADOW_INNER_RECT.bottom(), + SHADOW_INNER_RECT.x(), + SHADOW_TEXTURE_HEIGHT - SHADOW_INNER_RECT.bottom()); + QRectF bottomRightTile( + SHADOW_INNER_RECT.right(), + SHADOW_INNER_RECT.bottom(), + SHADOW_TEXTURE_WIDTH - SHADOW_INNER_RECT.right(), + SHADOW_TEXTURE_HEIGHT - SHADOW_INNER_RECT.bottom()); + QRectF bottomTile(bottomLeftTile.topRight(), bottomRightTile.bottomLeft()); + + QRectF leftTile(topLeftTile.bottomLeft(), bottomLeftTile.topRight()); + QRectF rightTile(topRightTile.bottomLeft(), bottomRightTile.topRight()); + + qreal tx1 = 0; + qreal ty1 = 0; + qreal tx2 = 0; + qreal ty2 = 0; + + // Explanation behind numbers: (256+1 x 256+1) is the minimum window size + // which doesn't cause overlapping of shadow tiles. For example, if a window + // has (256 x 256+1) size, top-left and top-right or bottom-left and + // bottom-right shadow tiles overlap. + + // No overlaps: In this case corner tiles are rendered as they are, + // and top/right/bottom/left tiles are stretched. + { + QSize windowSize(256 + 1, 256 + 1); + WindowQuadList shadowQuads; + + QRectF outerRect( + -SHADOW_PADDING_LEFT, + -SHADOW_PADDING_TOP, + windowSize.width() + SHADOW_PADDING_LEFT + SHADOW_PADDING_RIGHT, + windowSize.height() + SHADOW_PADDING_TOP + SHADOW_PADDING_BOTTOM); + + QRectF topLeft( + outerRect.left(), + outerRect.top(), + topLeftTile.width(), + topLeftTile.height()); + tx1 = topLeftTile.left(); + ty1 = topLeftTile.top(); + tx2 = topLeftTile.right(); + ty2 = topLeftTile.bottom(); + shadowQuads << makeShadowQuad(topLeft, tx1, ty1, tx2, ty2); + + QRectF topRight( + outerRect.right() - topRightTile.width(), + outerRect.top(), + topRightTile.width(), + topRightTile.height()); + tx1 = topRightTile.left(); + ty1 = topRightTile.top(); + tx2 = topRightTile.right(); + ty2 = topRightTile.bottom(); + shadowQuads << makeShadowQuad(topRight, tx1, ty1, tx2, ty2); + + QRectF top(topLeft.topRight(), topRight.bottomLeft()); + tx1 = topTile.left(); + ty1 = topTile.top(); + tx2 = topTile.right(); + ty2 = topTile.bottom(); + shadowQuads << makeShadowQuad(top, tx1, ty1, tx2, ty2); + + QRectF bottomLeft( + outerRect.left(), + outerRect.bottom() - bottomLeftTile.height(), + bottomLeftTile.width(), + bottomLeftTile.height()); + tx1 = bottomLeftTile.left(); + ty1 = bottomLeftTile.top(); + tx2 = bottomLeftTile.right(); + ty2 = bottomLeftTile.bottom(); + shadowQuads << makeShadowQuad(bottomLeft, tx1, ty1, tx2, ty2); + + QRectF bottomRight( + outerRect.right() - bottomRightTile.width(), + outerRect.bottom() - bottomRightTile.height(), + bottomRightTile.width(), + bottomRightTile.height()); + tx1 = bottomRightTile.left(); + ty1 = bottomRightTile.top(); + tx2 = bottomRightTile.right(); + ty2 = bottomRightTile.bottom(); + shadowQuads << makeShadowQuad(bottomRight, tx1, ty1, tx2, ty2); + + QRectF bottom(bottomLeft.topRight(), bottomRight.bottomLeft()); + tx1 = bottomTile.left(); + ty1 = bottomTile.top(); + tx2 = bottomTile.right(); + ty2 = bottomTile.bottom(); + shadowQuads << makeShadowQuad(bottom, tx1, ty1, tx2, ty2); + + QRectF left(topLeft.bottomLeft(), bottomLeft.topRight()); + tx1 = leftTile.left(); + ty1 = leftTile.top(); + tx2 = leftTile.right(); + ty2 = leftTile.bottom(); + shadowQuads << makeShadowQuad(left, tx1, ty1, tx2, ty2); + + QRectF right(topRight.bottomLeft(), bottomRight.topRight()); + tx1 = rightTile.left(); + ty1 = rightTile.top(); + tx2 = rightTile.right(); + ty2 = rightTile.bottom(); + shadowQuads << makeShadowQuad(right, tx1, ty1, tx2, ty2); + + QTest::newRow("no overlaps") + << windowSize + << WindowQuadListWrapper(shadowQuads); + } + + // Top-Left & Bottom-Left/Top-Right & Bottom-Right overlap: + // In this case overlapping parts are clipped and left/right + // tiles aren't rendered. + const QVector> verticalOverlapTestTable { + QPair { + QByteArray("top-left & bottom-left/top-right & bottom-right overlap"), + QSize(256 + 1, 256) + }, + QPair { + QByteArray("top-left & bottom-left/top-right & bottom-right overlap :: pre"), + QSize(256 + 1, 256 - 1) + } + // No need to test the case when window size is QSize(256 + 1, 256 + 1). + // It has been tested already (no overlaps test case). + }; + + for (auto const &tt : verticalOverlapTestTable) { + const char *testName = tt.first.constData(); + const QSize windowSize = tt.second; + + WindowQuadList shadowQuads; + qreal halfOverlap = 0.0; + + QRectF outerRect( + -SHADOW_PADDING_LEFT, + -SHADOW_PADDING_TOP, + windowSize.width() + SHADOW_PADDING_LEFT + SHADOW_PADDING_RIGHT, + windowSize.height() + SHADOW_PADDING_TOP + SHADOW_PADDING_BOTTOM); + + QRectF topLeft( + outerRect.left(), + outerRect.top(), + topLeftTile.width(), + topLeftTile.height()); + + QRectF bottomLeft( + outerRect.left(), + outerRect.bottom() - bottomLeftTile.height(), + bottomLeftTile.width(), + bottomLeftTile.height()); + + halfOverlap = qAbs(topLeft.bottom() - bottomLeft.top()) / 2; + topLeft.setBottom(topLeft.bottom() - std::ceil(halfOverlap)); + bottomLeft.setTop(bottomLeft.top() + std::floor(halfOverlap)); + + tx1 = topLeftTile.left(); + ty1 = topLeftTile.top(); + tx2 = topLeftTile.right(); + ty2 = topLeft.bottom(); + shadowQuads << makeShadowQuad(topLeft, tx1, ty1, tx2, ty2); + + tx1 = bottomLeftTile.left(); + ty1 = bottomLeft.top(); + tx2 = bottomLeftTile.right(); + ty2 = bottomLeftTile.bottom(); + shadowQuads << makeShadowQuad(bottomLeft, tx1, ty1, tx2, ty2); + + QRectF topRight( + outerRect.right() - topRightTile.width(), + outerRect.top(), + topRightTile.width(), + topRightTile.height()); + + QRectF bottomRight( + outerRect.right() - bottomRightTile.width(), + outerRect.bottom() - bottomRightTile.height(), + bottomRightTile.width(), + bottomRightTile.height()); + + halfOverlap = qAbs(topRight.bottom() - bottomRight.top()) / 2; + topRight.setBottom(topRight.bottom() - std::ceil(halfOverlap)); + bottomRight.setTop(bottomRight.top() + std::floor(halfOverlap)); + + tx1 = topRightTile.left(); + ty1 = topRightTile.top(); + tx2 = topRightTile.right(); + ty2 = topRight.bottom(); + shadowQuads << makeShadowQuad(topRight, tx1, ty1, tx2, ty2); + + tx1 = bottomRightTile.left(); + ty1 = bottomRight.top(); + tx2 = bottomRightTile.right(); + ty2 = bottomRightTile.bottom(); + shadowQuads << makeShadowQuad(bottomRight, tx1, ty1, tx2, ty2); + + QRectF top(topLeft.topRight(), topRight.bottomLeft()); + tx1 = topTile.left(); + ty1 = topTile.top(); + tx2 = topTile.right(); + ty2 = top.height(); + shadowQuads << makeShadowQuad(top, tx1, ty1, tx2, ty2); + + QRectF bottom(bottomLeft.topRight(), bottomRight.bottomLeft()); + tx1 = bottomTile.left(); + ty1 = SHADOW_TEXTURE_HEIGHT - bottomTile.height(); + tx2 = bottomTile.right(); + ty2 = bottomTile.bottom(); + shadowQuads << makeShadowQuad(bottom, tx1, ty1, tx2, ty2); + + QTest::newRow(testName) + << windowSize + << WindowQuadListWrapper(shadowQuads); + } + + // Top-Left & Top-Right/Bottom-Left & Bottom-Right overlap: + // In this case overlapping parts are clipped and top/bottom + // tiles aren't rendered. + const QVector> horizontalOverlapTestTable { + QPair { + QByteArray("top-left & top-right/bottom-left & bottom-right overlap"), + QSize(256, 256 + 1) + }, + QPair { + QByteArray("top-left & top-right/bottom-left & bottom-right overlap :: pre"), + QSize(256 - 1, 256 + 1) + } + // No need to test the case when window size is QSize(256 + 1, 256 + 1). + // It has been tested already (no overlaps test case). + }; + + for (auto const &tt : horizontalOverlapTestTable) { + const char *testName = tt.first.constData(); + const QSize windowSize = tt.second; + + WindowQuadList shadowQuads; + qreal halfOverlap = 0.0; + + QRectF outerRect( + -SHADOW_PADDING_LEFT, + -SHADOW_PADDING_TOP, + windowSize.width() + SHADOW_PADDING_LEFT + SHADOW_PADDING_RIGHT, + windowSize.height() + SHADOW_PADDING_TOP + SHADOW_PADDING_BOTTOM); + + QRectF topLeft( + outerRect.left(), + outerRect.top(), + topLeftTile.width(), + topLeftTile.height()); + + QRectF topRight( + outerRect.right() - topRightTile.width(), + outerRect.top(), + topRightTile.width(), + topRightTile.height()); + + halfOverlap = qAbs(topLeft.right() - topRight.left()) / 2; + topLeft.setRight(topLeft.right() - std::ceil(halfOverlap)); + topRight.setLeft(topRight.left() + std::floor(halfOverlap)); + + tx1 = topLeftTile.left(); + ty1 = topLeftTile.top(); + tx2 = topLeft.right(); + ty2 = topLeftTile.bottom(); + shadowQuads << makeShadowQuad(topLeft, tx1, ty1, tx2, ty2); + + tx1 = topRight.left(); + ty1 = topRightTile.top(); + tx2 = topRightTile.right(); + ty2 = topRightTile.bottom(); + shadowQuads << makeShadowQuad(topRight, tx1, ty1, tx2, ty2); + + QRectF bottomLeft( + outerRect.left(), + outerRect.bottom() - bottomLeftTile.height(), + bottomLeftTile.width(), + bottomLeftTile.height()); + + QRectF bottomRight( + outerRect.right() - bottomRightTile.width(), + outerRect.bottom() - bottomRightTile.height(), + bottomRightTile.width(), + bottomRightTile.height()); + + halfOverlap = qAbs(bottomLeft.right() - bottomRight.left()) / 2; + bottomLeft.setRight(bottomLeft.right() - std::ceil(halfOverlap)); + bottomRight.setLeft(bottomRight.left() + std::floor(halfOverlap)); + + tx1 = bottomLeftTile.left(); + ty1 = bottomLeftTile.top(); + tx2 = bottomLeft.right(); + ty2 = bottomLeftTile.bottom(); + shadowQuads << makeShadowQuad(bottomLeft, tx1, ty1, tx2, ty2); + + tx1 = bottomRight.left(); + ty1 = bottomRightTile.top(); + tx2 = bottomRightTile.right(); + ty2 = bottomRightTile.bottom(); + shadowQuads << makeShadowQuad(bottomRight, tx1, ty1, tx2, ty2); + + QRectF left(topLeft.bottomLeft(), bottomLeft.topRight()); + tx1 = leftTile.left(); + ty1 = leftTile.top(); + tx2 = left.width(); + ty2 = leftTile.bottom(); + shadowQuads << makeShadowQuad(left, tx1, ty1, tx2, ty2); + + QRectF right(topRight.bottomLeft(), bottomRight.topRight()); + tx1 = SHADOW_TEXTURE_WIDTH - right.width(); + ty1 = rightTile.top(); + tx2 = rightTile.right(); + ty2 = rightTile.bottom(); + shadowQuads << makeShadowQuad(right, tx1, ty1, tx2, ty2); + + QTest::newRow(testName) + << windowSize + << WindowQuadListWrapper(shadowQuads); + } + + // All shadow tiles overlap: In this case all overlapping parts + // are clippend and top/right/bottom/left tiles aren't rendered. + const QVector> allOverlapTestTable { + QPair { + QByteArray("all corner tiles overlap"), + QSize(256, 256) + }, + QPair { + QByteArray("all corner tiles overlap :: pre"), + QSize(256 - 1, 256 - 1) + } + // No need to test the case when window size is QSize(256 + 1, 256 + 1). + // It has been tested already (no overlaps test case). + }; + + for (auto const &tt : allOverlapTestTable) { + const char *testName = tt.first.constData(); + const QSize windowSize = tt.second; + + WindowQuadList shadowQuads; + qreal halfOverlap = 0.0; + + QRectF outerRect( + -SHADOW_PADDING_LEFT, + -SHADOW_PADDING_TOP, + windowSize.width() + SHADOW_PADDING_LEFT + SHADOW_PADDING_RIGHT, + windowSize.height() + SHADOW_PADDING_TOP + SHADOW_PADDING_BOTTOM); + + QRectF topLeft( + outerRect.left(), + outerRect.top(), + topLeftTile.width(), + topLeftTile.height()); + + QRectF topRight( + outerRect.right() - topRightTile.width(), + outerRect.top(), + topRightTile.width(), + topRightTile.height()); + + QRectF bottomLeft( + outerRect.left(), + outerRect.bottom() - bottomLeftTile.height(), + bottomLeftTile.width(), + bottomLeftTile.height()); + + QRectF bottomRight( + outerRect.right() - bottomRightTile.width(), + outerRect.bottom() - bottomRightTile.height(), + bottomRightTile.width(), + bottomRightTile.height()); + + halfOverlap = qAbs(topLeft.right() - topRight.left()) / 2; + topLeft.setRight(topLeft.right() - std::ceil(halfOverlap)); + topRight.setLeft(topRight.left() + std::floor(halfOverlap)); + + halfOverlap = qAbs(bottomLeft.right() - bottomRight.left()) / 2; + bottomLeft.setRight(bottomLeft.right() - std::ceil(halfOverlap)); + bottomRight.setLeft(bottomRight.left() + std::floor(halfOverlap)); + + halfOverlap = qAbs(topLeft.bottom() - bottomLeft.top()) / 2; + topLeft.setBottom(topLeft.bottom() - std::ceil(halfOverlap)); + bottomLeft.setTop(bottomLeft.top() + std::floor(halfOverlap)); + + halfOverlap = qAbs(topRight.bottom() - bottomRight.top()) / 2; + topRight.setBottom(topRight.bottom() - std::ceil(halfOverlap)); + bottomRight.setTop(bottomRight.top() + std::floor(halfOverlap)); + + tx1 = topLeftTile.left(); + ty1 = topLeftTile.top(); + tx2 = topLeft.width(); + ty2 = topLeft.height(); + shadowQuads << makeShadowQuad(topLeft, tx1, ty1, tx2, ty2); + + tx1 = SHADOW_TEXTURE_WIDTH - topRight.width(); + ty1 = topRightTile.top(); + tx2 = topRightTile.right(); + ty2 = topRight.bottom(); + shadowQuads << makeShadowQuad(topRight, tx1, ty1, tx2, ty2); + + tx1 = bottomLeftTile.left(); + ty1 = SHADOW_TEXTURE_HEIGHT - bottomLeft.height(); + tx2 = bottomLeft.width(); + ty2 = bottomLeftTile.bottom(); + shadowQuads << makeShadowQuad(bottomLeft, tx1, ty1, tx2, ty2); + + tx1 = SHADOW_TEXTURE_WIDTH - bottomRight.width(); + ty1 = SHADOW_TEXTURE_HEIGHT - bottomRight.height(); + tx2 = bottomRightTile.right(); + ty2 = bottomRightTile.bottom(); + shadowQuads << makeShadowQuad(bottomRight, tx1, ty1, tx2, ty2); + + QTest::newRow(testName) + << windowSize + << WindowQuadListWrapper(shadowQuads); + } + + // Window is too small: do not render any shadow tiles. + { + QSize windowSize(1, 1); + WindowQuadList shadowQuads; + + QTest::newRow("window too small") + << windowSize + << WindowQuadListWrapper(shadowQuads); + } +} + +void SceneQPainterShadowTest::testShadowTileOverlaps() +{ + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Decoration)); + + QFETCH(QSize, windowSize); + QFETCH(WindowQuadListWrapper, expectedQuadsWrapper); + WindowQuadList expectedQuads = expectedQuadsWrapper.quadList(); + + // Create a decorated client. + QScopedPointer surface(Test::createSurface()); + QScopedPointer shellSurface(Test::createShellSurface(surface.data())); + QScopedPointer ssd(Test::waylandServerSideDecoration()->create(surface.data())); + + auto client = Test::renderAndWaitForShown(surface.data(), windowSize, Qt::blue); + + QSignalSpy sizeChangedSpy(shellSurface.data(), &ShellSurface::sizeChanged); + QVERIFY(sizeChangedSpy.isValid()); + + // Check the client is decorated. + QVERIFY(client); + QVERIFY(client->isDecorated()); + auto decoration = client->decoration(); + QVERIFY(decoration); + + // If speciefied decoration theme is not found, KWin loads a default one + // so we have to check whether a client has right decoration. + auto decoShadow = decoration->shadow(); + QCOMPARE(decoShadow->shadow().size(), QSize(SHADOW_TEXTURE_WIDTH, SHADOW_TEXTURE_HEIGHT)); + QCOMPARE(decoShadow->paddingTop(), SHADOW_PADDING_TOP); + QCOMPARE(decoShadow->paddingRight(), SHADOW_PADDING_RIGHT); + QCOMPARE(decoShadow->paddingBottom(), SHADOW_PADDING_BOTTOM); + QCOMPARE(decoShadow->paddingLeft(), SHADOW_PADDING_LEFT); + + // Get shadow. + QVERIFY(client->effectWindow()); + QVERIFY(client->effectWindow()->sceneWindow()); + QVERIFY(client->effectWindow()->sceneWindow()->shadow()); + auto shadow = client->effectWindow()->sceneWindow()->shadow(); + + // Validate shadow quads. + const WindowQuadList &quads = shadow->shadowQuads(); + QCOMPARE(quads.size(), expectedQuads.size()); + + QVector mask(expectedQuads.size(), false); + for (const auto &q : quads) { + for (int i = 0; i < expectedQuads.size(); i++) { + if (! compareQuads(q, expectedQuads[i])) { + continue; + } + if (! mask[i]) { + mask[i] = true; + break; + } else { + QFAIL("got a duplicate shadow quad"); + } + } + } + + for (const auto v : mask) { + if (! v) { + QFAIL("missed a shadow quad"); + } + } +} + +void SceneQPainterShadowTest::testShadowTextureReconstruction() +{ + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::ShadowManager)); + + // Create a surface. + QScopedPointer surface(Test::createSurface()); + QScopedPointer shellSurface(Test::createShellSurface(surface.data())); + auto client = Test::renderAndWaitForShown(surface.data(), QSize(512, 512), Qt::blue); + QVERIFY(client); + QVERIFY(!client->isDecorated()); + + // Render reference shadow texture with the following params: + // - shadow size: 128 + // - inner rect size: 1 + // - padding: 128 + QImage referenceShadowTexture(QSize(256 + 1, 256 + 1), QImage::Format_ARGB32_Premultiplied); + referenceShadowTexture.fill(Qt::transparent); + + QPainter painter(&referenceShadowTexture); + painter.fillRect(QRect(10, 10, 192, 200), QColor(255, 0, 0, 128)); + painter.fillRect(QRect(128, 30, 10, 180), QColor(0, 0, 0, 30)); + painter.fillRect(QRect(20, 140, 160, 10), QColor(0, 255, 0, 128)); + + painter.setCompositionMode(QPainter::CompositionMode_DestinationOut); + painter.fillRect(QRect(128, 128, 1, 1), Qt::black); + painter.end(); + + // Create shadow. + QScopedPointer clientShadow(Test::waylandShadowManager()->createShadow(surface.data())); + QVERIFY(clientShadow->isValid()); + + auto shmPool = Test::waylandShmPool(); + + Buffer::Ptr bufferTopLeft = shmPool->createBuffer( + referenceShadowTexture.copy(QRect(0, 0, 128, 128))); + clientShadow->attachTopLeft(bufferTopLeft); + + Buffer::Ptr bufferTop = shmPool->createBuffer( + referenceShadowTexture.copy(QRect(128, 0, 1, 128))); + clientShadow->attachTop(bufferTop); + + Buffer::Ptr bufferTopRight = shmPool->createBuffer( + referenceShadowTexture.copy(QRect(128 + 1, 0, 128, 128))); + clientShadow->attachTopRight(bufferTopRight); + + Buffer::Ptr bufferRight = shmPool->createBuffer( + referenceShadowTexture.copy(QRect(128 + 1, 128, 128, 1))); + clientShadow->attachRight(bufferRight); + + Buffer::Ptr bufferBottomRight = shmPool->createBuffer( + referenceShadowTexture.copy(QRect(128 + 1, 128 + 1, 128, 128))); + clientShadow->attachBottomRight(bufferBottomRight); + + Buffer::Ptr bufferBottom = shmPool->createBuffer( + referenceShadowTexture.copy(QRect(128, 128 + 1, 1, 128))); + clientShadow->attachBottom(bufferBottom); + + Buffer::Ptr bufferBottomLeft = shmPool->createBuffer( + referenceShadowTexture.copy(QRect(0, 128 + 1, 128, 128))); + clientShadow->attachBottomLeft(bufferBottomLeft); + + Buffer::Ptr bufferLeft = shmPool->createBuffer( + referenceShadowTexture.copy(QRect(0, 128, 128, 1))); + clientShadow->attachLeft(bufferLeft); + + clientShadow->setOffsets(QMarginsF(128, 128, 128, 128)); + + // Commit shadow. + QSignalSpy shadowChangedSpy(client->surface(), &KWayland::Server::SurfaceInterface::shadowChanged); + QVERIFY(shadowChangedSpy.isValid()); + clientShadow->commit(); + surface->commit(Surface::CommitFlag::None); + QVERIFY(shadowChangedSpy.wait()); + + // Check whether we've got right shadow. + auto shadowIface = client->surface()->shadow(); + QVERIFY(!shadowIface.isNull()); + QCOMPARE(shadowIface->offset().left(), 128); + QCOMPARE(shadowIface->offset().top(), 128); + QCOMPARE(shadowIface->offset().right(), 128); + QCOMPARE(shadowIface->offset().bottom(), 128); + + // Get SceneQPainterShadow's texture. + QVERIFY(client->effectWindow()); + QVERIFY(client->effectWindow()->sceneWindow()); + QVERIFY(client->effectWindow()->sceneWindow()->shadow()); + auto shadowTexture = static_cast(client->effectWindow()->sceneWindow()->shadow())->shadowTexture(); + + QCOMPARE(shadowTexture, referenceShadowTexture); +} + +WAYLANDTEST_MAIN(SceneQPainterShadowTest) +#include "scene_qpainter_shadow_test.moc" diff --git a/autotests/integration/test_helpers.cpp b/autotests/integration/test_helpers.cpp --- a/autotests/integration/test_helpers.cpp +++ b/autotests/integration/test_helpers.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -62,6 +63,7 @@ EventQueue *queue = nullptr; Compositor *compositor = nullptr; ServerSideDecorationManager *decoration = nullptr; + ShadowManager *shadowManager = nullptr; Shell *shell = nullptr; XdgShell *xdgShellV5 = nullptr; XdgShell *xdgShellV6 = nullptr; @@ -163,6 +165,13 @@ return false; } } + if (flags.testFlag(AdditionalWaylandInterface::ShadowManager)) { + s_waylandConnection.shadowManager = registry->createShadowManager(registry->interface(Registry::Interface::Shadow).name, + registry->interface(Registry::Interface::Shadow).version); + if (!s_waylandConnection.shadowManager->isValid()) { + return false; + } + } if (flags.testFlag(AdditionalWaylandInterface::Decoration)) { s_waylandConnection.decoration = registry->createServerSideDecorationManager(registry->interface(Registry::Interface::ServerSideDecorationManager).name, registry->interface(Registry::Interface::ServerSideDecorationManager).version); @@ -230,6 +239,8 @@ s_waylandConnection.xdgShellV6 = nullptr; delete s_waylandConnection.shell; s_waylandConnection.shell = nullptr; + delete s_waylandConnection.shadowManager; + s_waylandConnection.shadowManager = nullptr; delete s_waylandConnection.idleInhibit; s_waylandConnection.idleInhibit = nullptr; delete s_waylandConnection.shm; @@ -264,6 +275,11 @@ return s_waylandConnection.compositor; } +ShadowManager *waylandShadowManager() +{ + return s_waylandConnection.shadowManager; +} + Shell *waylandShell() { return s_waylandConnection.shell; diff --git a/plugins/scenes/qpainter/scene_qpainter.h b/plugins/scenes/qpainter/scene_qpainter.h --- a/plugins/scenes/qpainter/scene_qpainter.h +++ b/plugins/scenes/qpainter/scene_qpainter.h @@ -123,23 +123,17 @@ public: SceneQPainterShadow(Toplevel* toplevel); virtual ~SceneQPainterShadow(); - using Shadow::ShadowElements; - using Shadow::ShadowElementTop; - using Shadow::ShadowElementTopRight; - using Shadow::ShadowElementRight; - using Shadow::ShadowElementBottomRight; - using Shadow::ShadowElementBottom; - using Shadow::ShadowElementBottomLeft; - using Shadow::ShadowElementLeft; - using Shadow::ShadowElementTopLeft; - using Shadow::ShadowElementsCount; - using Shadow::shadowPixmap; - using Shadow::topOffset; - using Shadow::leftOffset; - using Shadow::rightOffset; - using Shadow::bottomOffset; + + QImage &shadowTexture() { + return m_texture; + } + protected: + virtual void buildQuads() override; virtual bool prepareBackend() override; + +private: + QImage m_texture; }; class SceneQPainterDecorationRenderer : public Decoration::Renderer diff --git a/plugins/scenes/qpainter/scene_qpainter.cpp b/plugins/scenes/qpainter/scene_qpainter.cpp --- a/plugins/scenes/qpainter/scene_qpainter.cpp +++ b/plugins/scenes/qpainter/scene_qpainter.cpp @@ -38,6 +38,8 @@ #include #include +#include + namespace KWin { @@ -309,51 +311,21 @@ return; } SceneQPainterShadow *shadow = static_cast(toplevel->shadow()); - const QPixmap &topLeft = shadow->shadowPixmap(SceneQPainterShadow::ShadowElementTopLeft); - const QPixmap &top = shadow->shadowPixmap(SceneQPainterShadow::ShadowElementTop); - const QPixmap &topRight = shadow->shadowPixmap(SceneQPainterShadow::ShadowElementTopRight); - const QPixmap &bottomLeft = shadow->shadowPixmap(SceneQPainterShadow::ShadowElementBottomLeft); - const QPixmap &bottom = shadow->shadowPixmap(SceneQPainterShadow::ShadowElementBottom); - const QPixmap &bottomRight = shadow->shadowPixmap(SceneQPainterShadow::ShadowElementBottomRight); - const QPixmap &left = shadow->shadowPixmap(SceneQPainterShadow::ShadowElementLeft); - const QPixmap &right = shadow->shadowPixmap(SceneQPainterShadow::ShadowElementRight); - - const int leftOffset = shadow->leftOffset(); - const int topOffset = shadow->topOffset(); - const int rightOffset = shadow->rightOffset(); - const int bottomOffset = shadow->bottomOffset(); - - // top left - painter->drawPixmap(-leftOffset, -topOffset, topLeft); - // top right - painter->drawPixmap(toplevel->width() - topRight.width() + rightOffset, -topOffset, topRight); - // bottom left - painter->drawPixmap(-leftOffset, toplevel->height() - bottomLeft.height() + bottomOffset, bottomLeft); - // bottom right - painter->drawPixmap(toplevel->width() - bottomRight.width() + rightOffset, - toplevel->height() - bottomRight.height() + bottomOffset, - bottomRight); - // top - painter->drawPixmap(topLeft.width() - leftOffset, -topOffset, - toplevel->width() - topLeft.width() - topRight.width() + leftOffset + rightOffset, - top.height(), - top); - // left - painter->drawPixmap(-leftOffset, topLeft.height() - topOffset, left.width(), - toplevel->height() - topLeft.height() - bottomLeft.height() + topOffset + bottomOffset, - left); - // right - painter->drawPixmap(toplevel->width() - right.width() + rightOffset, - topRight.height() - topOffset, - right.width(), - toplevel->height() - topRight.height() - bottomRight.height() + topOffset + bottomOffset, - right); - // bottom - painter->drawPixmap(bottomLeft.width() - leftOffset, - toplevel->height() - bottom.height() + bottomOffset, - toplevel->width() - bottomLeft.width() - bottomRight.width() + leftOffset + rightOffset, - bottom.height(), - bottom); + + const QImage &shadowTexture = shadow->shadowTexture(); + const WindowQuadList &shadowQuads = shadow->shadowQuads(); + + for (const auto &q : shadowQuads) { + auto topLeft = q[0]; + auto bottomRight = q[2]; + QRectF target(topLeft.x(), topLeft.y(), + bottomRight.x() - topLeft.x(), + bottomRight.y() - topLeft.y()); + QRectF source(topLeft.textureX(), topLeft.textureY(), + bottomRight.textureX() - topLeft.textureX(), + bottomRight.textureY() - topLeft.textureY()); + painter->drawImage(target, shadowTexture, source); + } } void SceneQPainter::Window::renderWindowDecorations(QPainter *painter) @@ -557,12 +529,242 @@ { } +void SceneQPainterShadow::buildQuads() +{ + // Do not draw shadows if window width or window height is less than + // 5 px. 5 is an arbitrary choice. + if (topLevel()->width() < 5 || topLevel()->height() < 5) { + m_shadowQuads.clear(); + setShadowRegion(QRegion()); + return; + } + + const QSizeF top(elementSize(ShadowElementTop)); + const QSizeF topRight(elementSize(ShadowElementTopRight)); + const QSizeF right(elementSize(ShadowElementRight)); + const QSizeF bottomRight(elementSize(ShadowElementBottomRight)); + const QSizeF bottom(elementSize(ShadowElementBottom)); + const QSizeF bottomLeft(elementSize(ShadowElementBottomLeft)); + const QSizeF left(elementSize(ShadowElementLeft)); + const QSizeF topLeft(elementSize(ShadowElementTopLeft)); + + const QRectF outerRect(QPointF(-leftOffset(), -topOffset()), + QPointF(topLevel()->width() + rightOffset(), + topLevel()->height() + bottomOffset())); + + const int width = std::max({topLeft.width(), left.width(), bottomLeft.width()}) + + std::max(top.width(), bottom.width()) + + std::max({topRight.width(), right.width(), bottomRight.width()}); + const int height = std::max({topLeft.height(), top.height(), topRight.height()}) + + std::max(left.height(), right.height()) + + std::max({bottomLeft.height(), bottom.height(), bottomRight.height()}); + + QRectF topLeftRect(outerRect.topLeft(), topLeft); + QRectF topRightRect(outerRect.topRight() - QPointF(topRight.width(), 0), topRight); + QRectF bottomRightRect( + outerRect.bottomRight() - QPointF(bottomRight.width(), bottomRight.height()), + bottomRight); + QRectF bottomLeftRect(outerRect.bottomLeft() - QPointF(0, bottomLeft.height()), bottomLeft); + + // Re-distribute the corner tiles so no one of them is overlapping with others. + // By doing this, we assume that shadow's corner tiles are symmetric + // and it is OK to not draw top/right/bottom/left tile between corners. + // For example, let's say top-left and top-right tiles are overlapping. + // In that case, the right side of the top-left tile will be shifted to left, + // the left side of the top-right tile will shifted to right, and the top + // tile won't be rendered. + bool drawTop = true; + if (topLeftRect.right() >= topRightRect.left()) { + const float halfOverlap = qAbs(topLeftRect.right() - topRightRect.left()) / 2; + topLeftRect.setRight(topLeftRect.right() - std::floor(halfOverlap)); + topRightRect.setLeft(topRightRect.left() + std::ceil(halfOverlap)); + drawTop = false; + } + + bool drawRight = true; + if (topRightRect.bottom() >= bottomRightRect.top()) { + const float halfOverlap = qAbs(topRightRect.bottom() - bottomRightRect.top()) / 2; + topRightRect.setBottom(topRightRect.bottom() - std::floor(halfOverlap)); + bottomRightRect.setTop(bottomRightRect.top() + std::ceil(halfOverlap)); + drawRight = false; + } + + bool drawBottom = true; + if (bottomLeftRect.right() >= bottomRightRect.left()) { + const float halfOverlap = qAbs(bottomLeftRect.right() - bottomRightRect.left()) / 2; + bottomLeftRect.setRight(bottomLeftRect.right() - std::floor(halfOverlap)); + bottomRightRect.setLeft(bottomRightRect.left() + std::ceil(halfOverlap)); + drawBottom = false; + } + + bool drawLeft = true; + if (topLeftRect.bottom() >= bottomLeftRect.top()) { + const float halfOverlap = qAbs(topLeftRect.bottom() - bottomLeftRect.top()) / 2; + topLeftRect.setBottom(topLeftRect.bottom() - std::floor(halfOverlap)); + bottomLeftRect.setTop(bottomLeftRect.top() + std::ceil(halfOverlap)); + drawLeft = false; + } + + qreal tx1 = 0.0, + tx2 = 0.0, + ty1 = 0.0, + ty2 = 0.0; + + m_shadowQuads.clear(); + + tx1 = 0.0; + ty1 = 0.0; + tx2 = topLeftRect.width(); + ty2 = topLeftRect.height(); + WindowQuad topLeftQuad(WindowQuadShadow); + topLeftQuad[0] = WindowVertex(topLeftRect.left(), topLeftRect.top(), tx1, ty1); + topLeftQuad[1] = WindowVertex(topLeftRect.right(), topLeftRect.top(), tx2, ty1); + topLeftQuad[2] = WindowVertex(topLeftRect.right(), topLeftRect.bottom(), tx2, ty2); + topLeftQuad[3] = WindowVertex(topLeftRect.left(), topLeftRect.bottom(), tx1, ty2); + m_shadowQuads.append(topLeftQuad); + + tx1 = width - topRightRect.width(); + ty1 = 0.0; + tx2 = width; + ty2 = topRightRect.height(); + WindowQuad topRightQuad(WindowQuadShadow); + topRightQuad[0] = WindowVertex(topRightRect.left(), topRightRect.top(), tx1, ty1); + topRightQuad[1] = WindowVertex(topRightRect.right(), topRightRect.top(), tx2, ty1); + topRightQuad[2] = WindowVertex(topRightRect.right(), topRightRect.bottom(), tx2, ty2); + topRightQuad[3] = WindowVertex(topRightRect.left(), topRightRect.bottom(), tx1, ty2); + m_shadowQuads.append(topRightQuad); + + tx1 = width - bottomRightRect.width(); + tx2 = width; + ty1 = height - bottomRightRect.height(); + ty2 = height; + WindowQuad bottomRightQuad(WindowQuadShadow); + bottomRightQuad[0] = WindowVertex(bottomRightRect.left(), bottomRightRect.top(), tx1, ty1); + bottomRightQuad[1] = WindowVertex(bottomRightRect.right(), bottomRightRect.top(), tx2, ty1); + bottomRightQuad[2] = WindowVertex(bottomRightRect.right(), bottomRightRect.bottom(), tx2, ty2); + bottomRightQuad[3] = WindowVertex(bottomRightRect.left(), bottomRightRect.bottom(), tx1, ty2); + m_shadowQuads.append(bottomRightQuad); + + tx1 = 0.0; + tx2 = bottomLeftRect.width(); + ty1 = height - bottomLeftRect.height(); + ty2 = height; + WindowQuad bottomLeftQuad(WindowQuadShadow); + bottomLeftQuad[0] = WindowVertex(bottomLeftRect.left(), bottomLeftRect.top(), tx1, ty1); + bottomLeftQuad[1] = WindowVertex(bottomLeftRect.right(), bottomLeftRect.top(), tx2, ty1); + bottomLeftQuad[2] = WindowVertex(bottomLeftRect.right(), bottomLeftRect.bottom(), tx2, ty2); + bottomLeftQuad[3] = WindowVertex(bottomLeftRect.left(), bottomLeftRect.bottom(), tx1, ty2); + m_shadowQuads.append(bottomLeftQuad); + + if (drawTop) { + QRectF topRect( + topLeftRect.topRight(), + topRightRect.bottomLeft()); + tx1 = topLeft.width(); + ty1 = 0.0; + tx2 = width - topRight.width(); + ty2 = topRect.height(); + WindowQuad topQuad(WindowQuadShadow); + topQuad[0] = WindowVertex(topRect.left(), topRect.top(), tx1, ty1); + topQuad[1] = WindowVertex(topRect.right(), topRect.top(), tx2, ty1); + topQuad[2] = WindowVertex(topRect.right(), topRect.bottom(), tx2, ty2); + topQuad[3] = WindowVertex(topRect.left(), topRect.bottom(), tx1, ty2); + m_shadowQuads.append(topQuad); + } + + if (drawRight) { + QRectF rightRect( + topRightRect.bottomLeft(), + bottomRightRect.topRight()); + tx1 = width - rightRect.width(); + ty1 = topRight.height(); + tx2 = width; + ty2 = height - bottomRight.height(); + WindowQuad rightQuad(WindowQuadShadow); + rightQuad[0] = WindowVertex(rightRect.left(), rightRect.top(), tx1, ty1); + rightQuad[1] = WindowVertex(rightRect.right(), rightRect.top(), tx2, ty1); + rightQuad[2] = WindowVertex(rightRect.right(), rightRect.bottom(), tx2, ty2); + rightQuad[3] = WindowVertex(rightRect.left(), rightRect.bottom(), tx1, ty2); + m_shadowQuads.append(rightQuad); + } + + if (drawBottom) { + QRectF bottomRect( + bottomLeftRect.topRight(), + bottomRightRect.bottomLeft()); + tx1 = bottomLeft.width(); + ty1 = height - bottomRect.height(); + tx2 = width - bottomRight.width(); + ty2 = height; + WindowQuad bottomQuad(WindowQuadShadow); + bottomQuad[0] = WindowVertex(bottomRect.left(), bottomRect.top(), tx1, ty1); + bottomQuad[1] = WindowVertex(bottomRect.right(), bottomRect.top(), tx2, ty1); + bottomQuad[2] = WindowVertex(bottomRect.right(), bottomRect.bottom(), tx2, ty2); + bottomQuad[3] = WindowVertex(bottomRect.left(), bottomRect.bottom(), tx1, ty2); + m_shadowQuads.append(bottomQuad); + } + + if (drawLeft) { + QRectF leftRect( + topLeftRect.bottomLeft(), + bottomLeftRect.topRight()); + tx1 = 0.0; + ty1 = topLeft.height(); + tx2 = leftRect.width(); + ty2 = height - bottomRight.height(); + WindowQuad leftQuad(WindowQuadShadow); + leftQuad[0] = WindowVertex(leftRect.left(), leftRect.top(), tx1, ty1); + leftQuad[1] = WindowVertex(leftRect.right(), leftRect.top(), tx2, ty1); + leftQuad[2] = WindowVertex(leftRect.right(), leftRect.bottom(), tx2, ty2); + leftQuad[3] = WindowVertex(leftRect.left(), leftRect.bottom(), tx1, ty2); + m_shadowQuads.append(leftQuad); + } +} + bool SceneQPainterShadow::prepareBackend() { if (hasDecorationShadow()) { - // TODO: implement for QPainter + m_texture = decorationShadowImage(); + return true; + } + + const QPixmap &topLeft = shadowPixmap(ShadowElementTopLeft); + const QPixmap &top = shadowPixmap(ShadowElementTop); + const QPixmap &topRight = shadowPixmap(ShadowElementTopRight); + const QPixmap &bottomLeft = shadowPixmap(ShadowElementBottomLeft); + const QPixmap &bottom = shadowPixmap(ShadowElementBottom); + const QPixmap &bottomRight = shadowPixmap(ShadowElementBottomRight); + const QPixmap &left = shadowPixmap(ShadowElementLeft); + const QPixmap &right = shadowPixmap(ShadowElementRight); + + const int width = std::max({topLeft.width(), left.width(), bottomLeft.width()}) + + std::max(top.width(), bottom.width()) + + std::max({topRight.width(), right.width(), bottomRight.width()}); + const int height = std::max({topLeft.height(), top.height(), topRight.height()}) + + std::max(left.height(), right.height()) + + std::max({bottomLeft.height(), bottom.height(), bottomRight.height()}); + + if (width == 0 || height == 0) { return false; } + + QImage image(width, height, QImage::Format_ARGB32_Premultiplied); + image.fill(Qt::transparent); + + QPainter painter; + painter.begin(&image); + painter.drawPixmap(0, 0, topLeft); + painter.drawPixmap(topLeft.width(), 0, top); + painter.drawPixmap(width - topRight.width(), 0, topRight); + painter.drawPixmap(0, height - bottomLeft.height(), bottomLeft); + painter.drawPixmap(bottomLeft.width(), height - bottom.height(), bottom); + painter.drawPixmap(width - bottomRight.width(), height - bottomRight.height(), bottomRight); + painter.drawPixmap(0, topLeft.height(), left); + painter.drawPixmap(width - right.width(), topRight.height(), right); + painter.end(); + + m_texture = image; + return true; }