You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
782 lines
28 KiB
782 lines
28 KiB
/******************************************************************** |
|
KWin - the KDE window manager |
|
This file is part of the KDE project. |
|
|
|
Copyright (C) 2018 Vlad Zagorodniy <vladzzag@gmail.com> |
|
|
|
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 <http://www.gnu.org/licenses/>. |
|
*********************************************************************/ |
|
|
|
#include <algorithm> |
|
#include <cmath> |
|
|
|
#include <QByteArray> |
|
#include <QDir> |
|
#include <QImage> |
|
#include <QMarginsF> |
|
#include <QObject> |
|
#include <QPainter> |
|
#include <QPair> |
|
#include <QVector> |
|
|
|
#include <KDecoration2/Decoration> |
|
#include <KDecoration2/DecorationShadow> |
|
|
|
#include <KWayland/Client/server_decoration.h> |
|
#include <KWayland/Client/shadow.h> |
|
#include <KWayland/Client/shell.h> |
|
#include <KWayland/Client/shm_pool.h> |
|
#include <KWayland/Client/surface.h> |
|
#include <KWayland/Server/shadow_interface.h> |
|
#include <KWayland/Server/surface_interface.h> |
|
|
|
#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" |
|
|
|
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<KWin::ShellClient*>(); |
|
qRegisterMetaType<KWin::AbstractClient*>(); |
|
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<QSize>("windowSize"); |
|
QTest::addColumn<WindowQuadList>("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<QPair<QByteArray, QSize>> verticalOverlapTestTable { |
|
QPair<QByteArray, QSize> { |
|
QByteArray("top-left & bottom-left/top-right & bottom-right overlap"), |
|
QSize(256 + 1, 256) |
|
}, |
|
QPair<QByteArray, QSize> { |
|
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<QPair<QByteArray, QSize>> horizontalOverlapTestTable { |
|
QPair<QByteArray, QSize> { |
|
QByteArray("top-left & top-right/bottom-left & bottom-right overlap"), |
|
QSize(256, 256 + 1) |
|
}, |
|
QPair<QByteArray, QSize> { |
|
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<QPair<QByteArray, QSize>> allOverlapTestTable { |
|
QPair<QByteArray, QSize> { |
|
QByteArray("all corner tiles overlap"), |
|
QSize(256, 256) |
|
}, |
|
QPair<QByteArray, QSize> { |
|
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> surface(Test::createSurface()); |
|
QScopedPointer<ShellSurface> shellSurface(Test::createShellSurface(surface.data())); |
|
QScopedPointer<ServerSideDecoration> 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<bool> 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> surface(Test::createSurface()); |
|
QScopedPointer<ShellSurface> 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<KWayland::Client::Shadow> 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<SceneQPainterShadow *>(client->effectWindow()->sceneWindow()->shadow())->shadowTexture(); |
|
|
|
QCOMPARE(shadowTexture, referenceShadowTexture); |
|
} |
|
|
|
WAYLANDTEST_MAIN(SceneQPainterShadowTest) |
|
#include "scene_qpainter_shadow_test.moc"
|
|
|