/******************************************************************** 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 "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 "xdgshellclient.h" #include "wayland_server.h" #include "workspace.h" Q_DECLARE_METATYPE(KWin::WindowQuadList) 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(); }; inline bool isClose(double a, double b, double eps = 1e-5) { if (a == b) { return true; } const 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 (!isClose(a[i].x(), b[i].x()) || !isClose(a[i].y(), b[i].y()) || !isClose(a[i].textureX(), b[i].textureX()) || !isClose(a[i].textureY(), b[i].textureY())) { return false; } } return true; } 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; } 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("expectedQuads"); // Precompute shadow tile geometries(in texture's space). const QRectF topLeftTile( 0, 0, SHADOW_INNER_RECT.x(), SHADOW_INNER_RECT.y()); const QRectF topRightTile( SHADOW_INNER_RECT.right(), 0, SHADOW_TEXTURE_WIDTH - SHADOW_INNER_RECT.right(), SHADOW_INNER_RECT.y()); const QRectF topTile(topLeftTile.topRight(), topRightTile.bottomLeft()); const QRectF bottomLeftTile( 0, SHADOW_INNER_RECT.bottom(), SHADOW_INNER_RECT.x(), SHADOW_TEXTURE_HEIGHT - SHADOW_INNER_RECT.bottom()); const QRectF bottomRightTile( SHADOW_INNER_RECT.right(), SHADOW_INNER_RECT.bottom(), SHADOW_TEXTURE_WIDTH - SHADOW_INNER_RECT.right(), SHADOW_TEXTURE_HEIGHT - SHADOW_INNER_RECT.bottom()); const QRectF bottomTile(bottomLeftTile.topRight(), bottomRightTile.bottomLeft()); const QRectF leftTile(topLeftTile.bottomLeft(), bottomLeftTile.topRight()); const 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. { const QSize windowSize(256 + 1, 256 + 1); WindowQuadList shadowQuads; const QRectF outerRect( -SHADOW_PADDING_LEFT, -SHADOW_PADDING_TOP, windowSize.width() + SHADOW_PADDING_LEFT + SHADOW_PADDING_RIGHT, windowSize.height() + SHADOW_PADDING_TOP + SHADOW_PADDING_BOTTOM); const 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); const 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); const 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); const 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); const 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); const 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); const 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); const 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 << 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; const 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::floor(halfOverlap)); bottomLeft.setTop(bottomLeft.top() + std::ceil(halfOverlap)); tx1 = topLeftTile.left(); ty1 = topLeftTile.top(); tx2 = topLeftTile.right(); ty2 = topLeft.height(); shadowQuads << makeShadowQuad(topLeft, tx1, ty1, tx2, ty2); tx1 = bottomLeftTile.left(); ty1 = SHADOW_TEXTURE_HEIGHT - bottomLeft.height(); 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::floor(halfOverlap)); bottomRight.setTop(bottomRight.top() + std::ceil(halfOverlap)); tx1 = topRightTile.left(); ty1 = topRightTile.top(); tx2 = topRightTile.right(); ty2 = topRight.height(); shadowQuads << makeShadowQuad(topRight, tx1, ty1, tx2, ty2); tx1 = bottomRightTile.left(); ty1 = SHADOW_TEXTURE_HEIGHT - bottomRight.height(); tx2 = bottomRightTile.right(); ty2 = bottomRightTile.bottom(); shadowQuads << makeShadowQuad(bottomRight, tx1, ty1, tx2, ty2); const 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); const QRectF bottom(bottomLeft.topRight(), bottomRight.bottomLeft()); tx1 = bottomTile.left(); ty1 = SHADOW_TEXTURE_HEIGHT - bottom.height(); tx2 = bottomTile.right(); ty2 = bottomTile.bottom(); shadowQuads << makeShadowQuad(bottom, tx1, ty1, tx2, ty2); QTest::newRow(testName) << windowSize << 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; const 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::floor(halfOverlap)); topRight.setLeft(topRight.left() + std::ceil(halfOverlap)); tx1 = topLeftTile.left(); ty1 = topLeftTile.top(); tx2 = topLeft.width(); ty2 = topLeftTile.bottom(); shadowQuads << makeShadowQuad(topLeft, tx1, ty1, tx2, ty2); tx1 = SHADOW_TEXTURE_WIDTH - topRight.width(); 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::floor(halfOverlap)); bottomRight.setLeft(bottomRight.left() + std::ceil(halfOverlap)); tx1 = bottomLeftTile.left(); ty1 = bottomLeftTile.top(); tx2 = bottomLeft.width(); ty2 = bottomLeftTile.bottom(); shadowQuads << makeShadowQuad(bottomLeft, tx1, ty1, tx2, ty2); tx1 = SHADOW_TEXTURE_WIDTH - bottomRight.width(); ty1 = bottomRightTile.top(); tx2 = bottomRightTile.right(); ty2 = bottomRightTile.bottom(); shadowQuads << makeShadowQuad(bottomRight, tx1, ty1, tx2, ty2); const 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); const 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 << 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; const 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::floor(halfOverlap)); topRight.setLeft(topRight.left() + std::ceil(halfOverlap)); halfOverlap = qAbs(bottomLeft.right() - bottomRight.left()) / 2; bottomLeft.setRight(bottomLeft.right() - std::floor(halfOverlap)); bottomRight.setLeft(bottomRight.left() + std::ceil(halfOverlap)); halfOverlap = qAbs(topLeft.bottom() - bottomLeft.top()) / 2; topLeft.setBottom(topLeft.bottom() - std::floor(halfOverlap)); bottomLeft.setTop(bottomLeft.top() + std::ceil(halfOverlap)); halfOverlap = qAbs(topRight.bottom() - bottomRight.top()) / 2; topRight.setBottom(topRight.bottom() - std::floor(halfOverlap)); bottomRight.setTop(bottomRight.top() + std::ceil(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.height(); 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 << shadowQuads; } // Window is too small: do not render any shadow tiles. { const QSize windowSize(1, 1); const WindowQuadList shadowQuads; QTest::newRow("window is too small") << windowSize << shadowQuads; } } void SceneQPainterShadowTest::testShadowTileOverlaps() { QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Decoration)); QFETCH(QSize, windowSize); QFETCH(WindowQuadList, expectedQuads); // Create a decorated client. QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createXdgShellStableSurface(surface.data())); QScopedPointer ssd(Test::waylandServerSideDecoration()->create(surface.data())); auto *client = Test::renderAndWaitForShown(surface.data(), windowSize, Qt::blue); QSignalSpy sizeChangedSpy(shellSurface.data(), &XdgShellSurface::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 : qAsConst(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::createXdgShellStableSurface(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.0); QCOMPARE(shadowIface->offset().top(), 128.0); QCOMPARE(shadowIface->offset().right(), 128.0); QCOMPARE(shadowIface->offset().bottom(), 128.0); // 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"