kwin/autotests/integration/scene_opengl_shadow_test.cpp
Vlad Zahorodnii d1b35f306d Introduce started signal in Application
The new signal is emitted when the Application has fully been initialized.

It allows us to change the startup sequence, for example create workspace
before starting the Xwayland server, without making any adjustments in our
test suit.
2020-07-17 09:10:51 +00:00

863 lines
34 KiB
C++

/********************************************************************
KWin - the KDE window manager
This file is part of the KDE project.
Copyright (C) 2018 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
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 <QByteArray>
#include <QDir>
#include <QObject>
#include <QPair>
#include <QVector>
#include <KDecoration2/Decoration>
#include <KDecoration2/DecorationShadow>
#include <KWayland/Client/server_decoration.h>
#include <KWayland/Client/shadow.h>
#include <KWayland/Client/shm_pool.h>
#include <KWayland/Client/surface.h>
#include <KWaylandServer/shadow_interface.h>
#include <KWaylandServer/surface_interface.h>
#include "kwin_wayland_test.h"
#include "abstract_client.h"
#include "composite.h"
#include "effect_builtins.h"
#include "effectloader.h"
#include "effects.h"
#include "platform.h"
#include "shadow.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_opengl_shadow-0");
class SceneOpenGLShadowTest : public QObject
{
Q_OBJECT
public:
SceneOpenGLShadowTest() {}
private Q_SLOTS:
void initTestCase();
void cleanup();
void testShadowTileOverlaps_data();
void testShadowTileOverlaps();
void testNoCornerShadowTiles();
void testDistributeHugeCornerTiles();
};
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].u(), b[i].u())
|| !isClose(a[i].v(), b[i].v())) {
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 SceneOpenGLShadowTest::initTestCase()
{
// Copied from generic_scene_opengl_test.cpp
qRegisterMetaType<KWin::AbstractClient*>();
QSignalSpy applicationStartedSpy(kwinApp(), &Application::started);
QVERIFY(applicationStartedSpy.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);
qputenv("XCURSOR_THEME", QByteArrayLiteral("DMZ-White"));
qputenv("XCURSOR_SIZE", QByteArrayLiteral("24"));
qputenv("KWIN_COMPOSE", QByteArrayLiteral("O2"));
kwinApp()->start();
QVERIFY(applicationStartedSpy.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();
auto scene = KWin::Compositor::self()->scene();
QVERIFY(scene);
QCOMPARE(scene->compositingType(), KWin::OpenGL2Compositing);
}
void SceneOpenGLShadowTest::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 SceneOpenGLShadowTest::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() / SHADOW_TEXTURE_WIDTH;
ty1 = topLeftTile.top() / SHADOW_TEXTURE_HEIGHT;
tx2 = topLeftTile.right() / SHADOW_TEXTURE_WIDTH;
ty2 = topLeftTile.bottom() / SHADOW_TEXTURE_HEIGHT;
shadowQuads << makeShadowQuad(topLeft, tx1, ty1, tx2, ty2);
const QRectF topRight(
outerRect.right() - topRightTile.width(),
outerRect.top(),
topRightTile.width(),
topRightTile.height());
tx1 = topRightTile.left() / SHADOW_TEXTURE_WIDTH;
ty1 = topRightTile.top() / SHADOW_TEXTURE_HEIGHT;
tx2 = topRightTile.right() / SHADOW_TEXTURE_WIDTH;
ty2 = topRightTile.bottom() / SHADOW_TEXTURE_HEIGHT;
shadowQuads << makeShadowQuad(topRight, tx1, ty1, tx2, ty2);
const QRectF top(topLeft.topRight(), topRight.bottomLeft());
tx1 = topTile.left() / SHADOW_TEXTURE_WIDTH;
ty1 = topTile.top() / SHADOW_TEXTURE_HEIGHT;
tx2 = topTile.right() / SHADOW_TEXTURE_WIDTH;
ty2 = topTile.bottom() / SHADOW_TEXTURE_HEIGHT;
shadowQuads << makeShadowQuad(top, tx1, ty1, tx2, ty2);
const QRectF bottomLeft(
outerRect.left(),
outerRect.bottom() - bottomLeftTile.height(),
bottomLeftTile.width(),
bottomLeftTile.height());
tx1 = bottomLeftTile.left() / SHADOW_TEXTURE_WIDTH;
ty1 = bottomLeftTile.top() / SHADOW_TEXTURE_HEIGHT;
tx2 = bottomLeftTile.right() / SHADOW_TEXTURE_WIDTH;
ty2 = bottomLeftTile.bottom() / SHADOW_TEXTURE_HEIGHT;
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() / SHADOW_TEXTURE_WIDTH;
ty1 = bottomRightTile.top() / SHADOW_TEXTURE_HEIGHT;
tx2 = bottomRightTile.right() / SHADOW_TEXTURE_WIDTH;
ty2 = bottomRightTile.bottom() / SHADOW_TEXTURE_HEIGHT;
shadowQuads << makeShadowQuad(bottomRight, tx1, ty1, tx2, ty2);
const QRectF bottom(bottomLeft.topRight(), bottomRight.bottomLeft());
tx1 = bottomTile.left() / SHADOW_TEXTURE_WIDTH;
ty1 = bottomTile.top() / SHADOW_TEXTURE_HEIGHT;
tx2 = bottomTile.right() / SHADOW_TEXTURE_WIDTH;
ty2 = bottomTile.bottom() / SHADOW_TEXTURE_HEIGHT;
shadowQuads << makeShadowQuad(bottom, tx1, ty1, tx2, ty2);
const QRectF left(topLeft.bottomLeft(), bottomLeft.topRight());
tx1 = leftTile.left() / SHADOW_TEXTURE_WIDTH;
ty1 = leftTile.top() / SHADOW_TEXTURE_HEIGHT;
tx2 = leftTile.right() / SHADOW_TEXTURE_WIDTH;
ty2 = leftTile.bottom() / SHADOW_TEXTURE_HEIGHT;
shadowQuads << makeShadowQuad(left, tx1, ty1, tx2, ty2);
const QRectF right(topRight.bottomLeft(), bottomRight.topRight());
tx1 = rightTile.left() / SHADOW_TEXTURE_WIDTH;
ty1 = rightTile.top() / SHADOW_TEXTURE_HEIGHT;
tx2 = rightTile.right() / SHADOW_TEXTURE_WIDTH;
ty2 = rightTile.bottom() / SHADOW_TEXTURE_HEIGHT;
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() - halfOverlap);
bottomLeft.setTop(bottomLeft.top() + halfOverlap);
tx1 = topLeftTile.left() / SHADOW_TEXTURE_WIDTH;
ty1 = topLeftTile.top() / SHADOW_TEXTURE_HEIGHT;
tx2 = topLeftTile.right() / SHADOW_TEXTURE_WIDTH;
ty2 = topLeft.height() / SHADOW_TEXTURE_HEIGHT;
shadowQuads << makeShadowQuad(topLeft, tx1, ty1, tx2, ty2);
tx1 = bottomLeftTile.left() / SHADOW_TEXTURE_WIDTH;
ty1 = 1.0 - (bottomLeft.height() / SHADOW_TEXTURE_HEIGHT);
tx2 = bottomLeftTile.right() / SHADOW_TEXTURE_WIDTH;
ty2 = bottomLeftTile.bottom() / SHADOW_TEXTURE_HEIGHT;
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() - halfOverlap);
bottomRight.setTop(bottomRight.top() + halfOverlap);
tx1 = topRightTile.left() / SHADOW_TEXTURE_WIDTH;
ty1 = topRightTile.top() / SHADOW_TEXTURE_HEIGHT;
tx2 = topRightTile.right() / SHADOW_TEXTURE_WIDTH;
ty2 = topRight.height() / SHADOW_TEXTURE_HEIGHT;
shadowQuads << makeShadowQuad(topRight, tx1, ty1, tx2, ty2);
tx1 = bottomRightTile.left() / SHADOW_TEXTURE_WIDTH;
ty1 = 1.0 - (bottomRight.height() / SHADOW_TEXTURE_HEIGHT);
tx2 = bottomRightTile.right() / SHADOW_TEXTURE_WIDTH;
ty2 = bottomRightTile.bottom() / SHADOW_TEXTURE_HEIGHT;
shadowQuads << makeShadowQuad(bottomRight, tx1, ty1, tx2, ty2);
const QRectF top(topLeft.topRight(), topRight.bottomLeft());
tx1 = topTile.left() / SHADOW_TEXTURE_WIDTH;
ty1 = topTile.top() / SHADOW_TEXTURE_HEIGHT;
tx2 = topTile.right() / SHADOW_TEXTURE_WIDTH;
ty2 = top.height() / SHADOW_TEXTURE_HEIGHT;
shadowQuads << makeShadowQuad(top, tx1, ty1, tx2, ty2);
const QRectF bottom(bottomLeft.topRight(), bottomRight.bottomLeft());
tx1 = bottomTile.left() / SHADOW_TEXTURE_WIDTH;
ty1 = 1.0 - (bottom.height() / SHADOW_TEXTURE_HEIGHT);
tx2 = bottomTile.right() / SHADOW_TEXTURE_WIDTH;
ty2 = bottomTile.bottom() / SHADOW_TEXTURE_HEIGHT;
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() - halfOverlap);
topRight.setLeft(topRight.left() + halfOverlap);
tx1 = topLeftTile.left() / SHADOW_TEXTURE_WIDTH;
ty1 = topLeftTile.top() / SHADOW_TEXTURE_HEIGHT;
tx2 = topLeft.width() / SHADOW_TEXTURE_WIDTH;
ty2 = topLeftTile.bottom() / SHADOW_TEXTURE_HEIGHT;
shadowQuads << makeShadowQuad(topLeft, tx1, ty1, tx2, ty2);
tx1 = 1.0 - (topRight.width() / SHADOW_TEXTURE_WIDTH);
ty1 = topRightTile.top() / SHADOW_TEXTURE_HEIGHT;
tx2 = topRightTile.right() / SHADOW_TEXTURE_WIDTH;
ty2 = topRightTile.bottom() / SHADOW_TEXTURE_HEIGHT;
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() - halfOverlap);
bottomRight.setLeft(bottomRight.left() + halfOverlap);
tx1 = bottomLeftTile.left() / SHADOW_TEXTURE_WIDTH;
ty1 = bottomLeftTile.top() / SHADOW_TEXTURE_HEIGHT;
tx2 = bottomLeft.width() / SHADOW_TEXTURE_WIDTH;
ty2 = bottomLeftTile.bottom() / SHADOW_TEXTURE_HEIGHT;
shadowQuads << makeShadowQuad(bottomLeft, tx1, ty1, tx2, ty2);
tx1 = 1.0 - (bottomRight.width() / SHADOW_TEXTURE_WIDTH);
ty1 = bottomRightTile.top() / SHADOW_TEXTURE_HEIGHT;
tx2 = bottomRightTile.right() / SHADOW_TEXTURE_WIDTH;
ty2 = bottomRightTile.bottom() / SHADOW_TEXTURE_HEIGHT;
shadowQuads << makeShadowQuad(bottomRight, tx1, ty1, tx2, ty2);
const QRectF left(topLeft.bottomLeft(), bottomLeft.topRight());
tx1 = leftTile.left() / SHADOW_TEXTURE_WIDTH;
ty1 = leftTile.top() / SHADOW_TEXTURE_HEIGHT;
tx2 = left.width() / SHADOW_TEXTURE_WIDTH;
ty2 = leftTile.bottom() / SHADOW_TEXTURE_HEIGHT;
shadowQuads << makeShadowQuad(left, tx1, ty1, tx2, ty2);
const QRectF right(topRight.bottomLeft(), bottomRight.topRight());
tx1 = 1.0 - (right.width() / SHADOW_TEXTURE_WIDTH);
ty1 = rightTile.top() / SHADOW_TEXTURE_HEIGHT;
tx2 = rightTile.right() / SHADOW_TEXTURE_WIDTH;
ty2 = rightTile.bottom() / SHADOW_TEXTURE_HEIGHT;
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() - halfOverlap);
topRight.setLeft(topRight.left() + halfOverlap);
halfOverlap = qAbs(bottomLeft.right() - bottomRight.left()) / 2;
bottomLeft.setRight(bottomLeft.right() - halfOverlap);
bottomRight.setLeft(bottomRight.left() + halfOverlap);
halfOverlap = qAbs(topLeft.bottom() - bottomLeft.top()) / 2;
topLeft.setBottom(topLeft.bottom() - halfOverlap);
bottomLeft.setTop(bottomLeft.top() + halfOverlap);
halfOverlap = qAbs(topRight.bottom() - bottomRight.top()) / 2;
topRight.setBottom(topRight.bottom() - halfOverlap);
bottomRight.setTop(bottomRight.top() + halfOverlap);
tx1 = topLeftTile.left() / SHADOW_TEXTURE_WIDTH;
ty1 = topLeftTile.top() / SHADOW_TEXTURE_HEIGHT;
tx2 = topLeft.width() / SHADOW_TEXTURE_WIDTH;
ty2 = topLeft.height() / SHADOW_TEXTURE_HEIGHT;
shadowQuads << makeShadowQuad(topLeft, tx1, ty1, tx2, ty2);
tx1 = 1.0 - (topRight.width() / SHADOW_TEXTURE_WIDTH);
ty1 = topRightTile.top() / SHADOW_TEXTURE_HEIGHT;
tx2 = topRightTile.right() / SHADOW_TEXTURE_WIDTH;
ty2 = topRight.height() / SHADOW_TEXTURE_HEIGHT;
shadowQuads << makeShadowQuad(topRight, tx1, ty1, tx2, ty2);
tx1 = bottomLeftTile.left() / SHADOW_TEXTURE_WIDTH;
ty1 = 1.0 - (bottomLeft.height() / SHADOW_TEXTURE_HEIGHT);
tx2 = bottomLeft.width() / SHADOW_TEXTURE_WIDTH;
ty2 = bottomLeftTile.bottom() / SHADOW_TEXTURE_HEIGHT;
shadowQuads << makeShadowQuad(bottomLeft, tx1, ty1, tx2, ty2);
tx1 = 1.0 - (bottomRight.width() / SHADOW_TEXTURE_WIDTH);
ty1 = 1.0 - (bottomRight.height() / SHADOW_TEXTURE_HEIGHT);
tx2 = bottomRightTile.right() / SHADOW_TEXTURE_WIDTH;
ty2 = bottomRightTile.bottom() / SHADOW_TEXTURE_HEIGHT;
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 SceneOpenGLShadowTest::testShadowTileOverlaps()
{
QFETCH(QSize, windowSize);
QFETCH(WindowQuadList, expectedQuads);
QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Decoration));
// Create a decorated client.
QScopedPointer<Surface> surface(Test::createSurface());
QScopedPointer<XdgShellSurface> shellSurface(Test::createXdgShellStableSurface(surface.data()));
QScopedPointer<ServerSideDecoration> 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<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 SceneOpenGLShadowTest::testNoCornerShadowTiles()
{
// this test verifies that top/right/bottom/left shadow tiles are
// still drawn even when corner tiles are missing
QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::ShadowManager));
// Create a surface.
QScopedPointer<Surface> surface(Test::createSurface());
QScopedPointer<XdgShellSurface> 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(256 + 1, 256 + 1, QImage::Format_ARGB32_Premultiplied);
referenceShadowTexture.fill(Qt::transparent);
// We don't care about content of the shadow.
// Submit the shadow to KWin.
QScopedPointer<KWayland::Client::Shadow> clientShadow(Test::waylandShadowManager()->createShadow(surface.data()));
QVERIFY(clientShadow->isValid());
auto *shmPool = Test::waylandShmPool();
Buffer::Ptr bufferTop = shmPool->createBuffer(
referenceShadowTexture.copy(QRect(128, 0, 1, 128)));
clientShadow->attachTop(bufferTop);
Buffer::Ptr bufferRight = shmPool->createBuffer(
referenceShadowTexture.copy(QRect(128 + 1, 128, 128, 1)));
clientShadow->attachRight(bufferRight);
Buffer::Ptr bufferBottom = shmPool->createBuffer(
referenceShadowTexture.copy(QRect(128, 128 + 1, 1, 128)));
clientShadow->attachBottom(bufferBottom);
Buffer::Ptr bufferLeft = shmPool->createBuffer(
referenceShadowTexture.copy(QRect(0, 128, 128, 1)));
clientShadow->attachLeft(bufferLeft);
clientShadow->setOffsets(QMarginsF(128, 128, 128, 128));
QSignalSpy shadowChangedSpy(client->surface(), &KWaylandServer::SurfaceInterface::shadowChanged);
QVERIFY(shadowChangedSpy.isValid());
clientShadow->commit();
surface->commit(Surface::CommitFlag::None);
QVERIFY(shadowChangedSpy.wait());
// Check that we got right shadow from the client.
QPointer<KWaylandServer::ShadowInterface> 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);
QVERIFY(client->effectWindow());
QVERIFY(client->effectWindow()->sceneWindow());
KWin::Shadow *shadow = client->effectWindow()->sceneWindow()->shadow();
QVERIFY(shadow != nullptr);
const WindowQuadList &quads = shadow->shadowQuads();
QCOMPARE(quads.count(), 4);
// Shadow size: 128
// Padding: QMargins(128, 128, 128, 128)
// Inner rect: QRect(128, 128, 1, 1)
// Texture size: QSize(257, 257)
// Window size: QSize(512, 512)
WindowQuadList expectedQuads;
expectedQuads << makeShadowQuad(QRectF( 0, -128, 512, 128), 128.0 / 257.0, 0.0, 129.0 / 257.0, 128.0 / 257.0); // top
expectedQuads << makeShadowQuad(QRectF( 512, 0, 128, 512), 129.0 / 257.0, 128.0 / 257.0, 1.0, 129.0 / 257.0); // right
expectedQuads << makeShadowQuad(QRectF( 0, 512, 512, 128), 128.0 / 257.0, 129.0 / 257.0, 129.0 / 257.0, 1.0); // bottom
expectedQuads << makeShadowQuad(QRectF(-128, 0, 128, 512), 0.0, 128.0 / 257.0, 128.0 / 257.0, 129.0 / 257.0); // left
for (const WindowQuad &expectedQuad : expectedQuads) {
auto it = std::find_if(quads.constBegin(), quads.constEnd(),
[&expectedQuad](const WindowQuad &quad) {
return compareQuads(quad, expectedQuad);
});
if (it == quads.constEnd()) {
const QString message = QStringLiteral("Missing shadow quad (left: %1, top: %2, right: %3, bottom: %4)")
.arg(expectedQuad.left())
.arg(expectedQuad.top())
.arg(expectedQuad.right())
.arg(expectedQuad.bottom());
const QByteArray rawMessage = message.toLocal8Bit().data();
QFAIL(rawMessage.data());
}
}
}
void SceneOpenGLShadowTest::testDistributeHugeCornerTiles()
{
// this test verifies that huge corner tiles are distributed correctly
QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::ShadowManager));
// Create a surface.
QScopedPointer<Surface> surface(Test::createSurface());
QScopedPointer<XdgShellSurface> shellSurface(Test::createXdgShellStableSurface(surface.data()));
auto *client = Test::renderAndWaitForShown(surface.data(), QSize(64, 64), Qt::blue);
QVERIFY(client);
QVERIFY(!client->isDecorated());
// Submit the shadow to KWin.
QScopedPointer<KWayland::Client::Shadow> clientShadow(Test::waylandShadowManager()->createShadow(surface.data()));
QVERIFY(clientShadow->isValid());
QImage referenceTileTexture(512, 512, QImage::Format_ARGB32_Premultiplied);
referenceTileTexture.fill(Qt::transparent);
auto *shmPool = Test::waylandShmPool();
Buffer::Ptr bufferTopLeft = shmPool->createBuffer(referenceTileTexture);
clientShadow->attachTopLeft(bufferTopLeft);
Buffer::Ptr bufferTopRight = shmPool->createBuffer(referenceTileTexture);
clientShadow->attachTopRight(bufferTopRight);
clientShadow->setOffsets(QMarginsF(256, 256, 256, 0));
QSignalSpy shadowChangedSpy(client->surface(), &KWaylandServer::SurfaceInterface::shadowChanged);
QVERIFY(shadowChangedSpy.isValid());
clientShadow->commit();
surface->commit(Surface::CommitFlag::None);
QVERIFY(shadowChangedSpy.wait());
// Check that we got right shadow from the client.
QPointer<KWaylandServer::ShadowInterface> shadowIface = client->surface()->shadow();
QVERIFY(!shadowIface.isNull());
QCOMPARE(shadowIface->offset().left(), 256.0);
QCOMPARE(shadowIface->offset().top(), 256.0);
QCOMPARE(shadowIface->offset().right(), 256.0);
QCOMPARE(shadowIface->offset().bottom(), 0.0);
QVERIFY(client->effectWindow());
QVERIFY(client->effectWindow()->sceneWindow());
KWin::Shadow *shadow = client->effectWindow()->sceneWindow()->shadow();
QVERIFY(shadow != nullptr);
WindowQuadList expectedQuads;
// Top-left quad
expectedQuads << makeShadowQuad(
QRectF(-256, -256, 256 + 32, 256 + 64),
0.0, 0.0, (256.0 + 32.0) / 1024.0, (256.0 + 64.0) / 512.0);
// Top-right quad
expectedQuads << makeShadowQuad(
QRectF(32, -256, 256 + 32, 256 + 64),
1.0 - (256.0 + 32.0) / 1024.0, 0.0, 1.0, (256.0 + 64.0) / 512.0);
const WindowQuadList &quads = shadow->shadowQuads();
QCOMPARE(quads.count(), expectedQuads.count());
for (const WindowQuad &expectedQuad : expectedQuads) {
auto it = std::find_if(quads.constBegin(), quads.constEnd(),
[&expectedQuad](const WindowQuad &quad) {
return compareQuads(quad, expectedQuad);
});
if (it == quads.constEnd()) {
const QString message = QStringLiteral("Missing shadow quad (left: %1, top: %2, right: %3, bottom: %4)")
.arg(expectedQuad.left())
.arg(expectedQuad.top())
.arg(expectedQuad.right())
.arg(expectedQuad.bottom());
const QByteArray rawMessage = message.toLocal8Bit().data();
QFAIL(rawMessage.data());
}
}
}
WAYLANDTEST_MAIN(SceneOpenGLShadowTest)
#include "scene_opengl_shadow_test.moc"