kwin/autotests/integration/scene_opengl_shadow_test.cpp

864 lines
34 KiB
C++
Raw Normal View History

/********************************************************************
KWin - the KDE window manager
This file is part of the KDE project.
2020-01-14 16:17:18 +00:00
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>
[scenes/opengl] Correctly draw shadows when corner tiles are missing Summary: Current implementation of buildQuads assumes that corner shadow tiles are always present: const QRectF leftRect( topLeftRect.bottomLeft(), bottomLeftRect.topRight()); but that assumption is wrong. For example, if the default panel is on the bottom screen edge, then the calendar popup won't have the bottom-left shadow tile(at least on Wayland). Which means that the left shadow tile won't be visible because topLeftRect.left() == bottomLeftRect.right(). Corner rectangles only have to influence height of the left/right tile and width of the top/bottom tile. Width of the left/right tile and height of the top/bottom tile should not be controlled by corner tiles. Overall, this is how shadow quads are computed: * Compute the outer rectangle; * Compute target rectangle for each corner tile. If some corner tile is missing, move the target rectangle to the corresponding corner of the inner shadow rect and set its width and height to 0. We need to do that to prevent top/right/bottom/left tiles from spanning over corners: {F6190219, layout=center, size=full} We would rather prefer something like this if the top-left tile is missing: {F6190233, layout=center, size=full} * Fix overlaps between corner tiles; * Compute target rectangles for top, right, bottom, and left tiles; * Fix overlaps between left/right and top/bottom shadow tiles. Test Plan: * Ran tests; * Resized Konsole to its minimimum size(on X11 and Wayland); * Opened the calendar popup(on X11 and Wayland): Before: {F6190344, layout=center, size=full} After: {F6190346, layout=center, size=full} Reviewers: #kwin, davidedmundson Reviewed By: #kwin, davidedmundson Subscribers: abetts, davidedmundson, kwin Tags: #kwin Differential Revision: https://phabricator.kde.org/D14783
2018-08-12 18:23:12 +00:00
#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();
[scenes/opengl] Correctly draw shadows when corner tiles are missing Summary: Current implementation of buildQuads assumes that corner shadow tiles are always present: const QRectF leftRect( topLeftRect.bottomLeft(), bottomLeftRect.topRight()); but that assumption is wrong. For example, if the default panel is on the bottom screen edge, then the calendar popup won't have the bottom-left shadow tile(at least on Wayland). Which means that the left shadow tile won't be visible because topLeftRect.left() == bottomLeftRect.right(). Corner rectangles only have to influence height of the left/right tile and width of the top/bottom tile. Width of the left/right tile and height of the top/bottom tile should not be controlled by corner tiles. Overall, this is how shadow quads are computed: * Compute the outer rectangle; * Compute target rectangle for each corner tile. If some corner tile is missing, move the target rectangle to the corresponding corner of the inner shadow rect and set its width and height to 0. We need to do that to prevent top/right/bottom/left tiles from spanning over corners: {F6190219, layout=center, size=full} We would rather prefer something like this if the top-left tile is missing: {F6190233, layout=center, size=full} * Fix overlaps between corner tiles; * Compute target rectangles for top, right, bottom, and left tiles; * Fix overlaps between left/right and top/bottom shadow tiles. Test Plan: * Ran tests; * Resized Konsole to its minimimum size(on X11 and Wayland); * Opened the calendar popup(on X11 and Wayland): Before: {F6190344, layout=center, size=full} After: {F6190346, layout=center, size=full} Reviewers: #kwin, davidedmundson Reviewed By: #kwin, davidedmundson Subscribers: abetts, davidedmundson, kwin Tags: #kwin Differential Revision: https://phabricator.kde.org/D14783
2018-08-12 18:23:12 +00:00
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 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);
qputenv("XCURSOR_THEME", QByteArrayLiteral("DMZ-White"));
qputenv("XCURSOR_SIZE", QByteArrayLiteral("24"));
qputenv("KWIN_COMPOSE", QByteArrayLiteral("O2"));
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();
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);
[scenes/opengl] Correctly draw shadows when corner tiles are missing Summary: Current implementation of buildQuads assumes that corner shadow tiles are always present: const QRectF leftRect( topLeftRect.bottomLeft(), bottomLeftRect.topRight()); but that assumption is wrong. For example, if the default panel is on the bottom screen edge, then the calendar popup won't have the bottom-left shadow tile(at least on Wayland). Which means that the left shadow tile won't be visible because topLeftRect.left() == bottomLeftRect.right(). Corner rectangles only have to influence height of the left/right tile and width of the top/bottom tile. Width of the left/right tile and height of the top/bottom tile should not be controlled by corner tiles. Overall, this is how shadow quads are computed: * Compute the outer rectangle; * Compute target rectangle for each corner tile. If some corner tile is missing, move the target rectangle to the corresponding corner of the inner shadow rect and set its width and height to 0. We need to do that to prevent top/right/bottom/left tiles from spanning over corners: {F6190219, layout=center, size=full} We would rather prefer something like this if the top-left tile is missing: {F6190233, layout=center, size=full} * Fix overlaps between corner tiles; * Compute target rectangles for top, right, bottom, and left tiles; * Fix overlaps between left/right and top/bottom shadow tiles. Test Plan: * Ran tests; * Resized Konsole to its minimimum size(on X11 and Wayland); * Opened the calendar popup(on X11 and Wayland): Before: {F6190344, layout=center, size=full} After: {F6190346, layout=center, size=full} Reviewers: #kwin, davidedmundson Reviewed By: #kwin, davidedmundson Subscribers: abetts, davidedmundson, kwin Tags: #kwin Differential Revision: https://phabricator.kde.org/D14783
2018-08-12 18:23:12 +00:00
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");
}
}
}
[scenes/opengl] Correctly draw shadows when corner tiles are missing Summary: Current implementation of buildQuads assumes that corner shadow tiles are always present: const QRectF leftRect( topLeftRect.bottomLeft(), bottomLeftRect.topRight()); but that assumption is wrong. For example, if the default panel is on the bottom screen edge, then the calendar popup won't have the bottom-left shadow tile(at least on Wayland). Which means that the left shadow tile won't be visible because topLeftRect.left() == bottomLeftRect.right(). Corner rectangles only have to influence height of the left/right tile and width of the top/bottom tile. Width of the left/right tile and height of the top/bottom tile should not be controlled by corner tiles. Overall, this is how shadow quads are computed: * Compute the outer rectangle; * Compute target rectangle for each corner tile. If some corner tile is missing, move the target rectangle to the corresponding corner of the inner shadow rect and set its width and height to 0. We need to do that to prevent top/right/bottom/left tiles from spanning over corners: {F6190219, layout=center, size=full} We would rather prefer something like this if the top-left tile is missing: {F6190233, layout=center, size=full} * Fix overlaps between corner tiles; * Compute target rectangles for top, right, bottom, and left tiles; * Fix overlaps between left/right and top/bottom shadow tiles. Test Plan: * Ran tests; * Resized Konsole to its minimimum size(on X11 and Wayland); * Opened the calendar popup(on X11 and Wayland): Before: {F6190344, layout=center, size=full} After: {F6190346, layout=center, size=full} Reviewers: #kwin, davidedmundson Reviewed By: #kwin, davidedmundson Subscribers: abetts, davidedmundson, kwin Tags: #kwin Differential Revision: https://phabricator.kde.org/D14783
2018-08-12 18:23:12 +00:00
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()));
[scenes/opengl] Correctly draw shadows when corner tiles are missing Summary: Current implementation of buildQuads assumes that corner shadow tiles are always present: const QRectF leftRect( topLeftRect.bottomLeft(), bottomLeftRect.topRight()); but that assumption is wrong. For example, if the default panel is on the bottom screen edge, then the calendar popup won't have the bottom-left shadow tile(at least on Wayland). Which means that the left shadow tile won't be visible because topLeftRect.left() == bottomLeftRect.right(). Corner rectangles only have to influence height of the left/right tile and width of the top/bottom tile. Width of the left/right tile and height of the top/bottom tile should not be controlled by corner tiles. Overall, this is how shadow quads are computed: * Compute the outer rectangle; * Compute target rectangle for each corner tile. If some corner tile is missing, move the target rectangle to the corresponding corner of the inner shadow rect and set its width and height to 0. We need to do that to prevent top/right/bottom/left tiles from spanning over corners: {F6190219, layout=center, size=full} We would rather prefer something like this if the top-left tile is missing: {F6190233, layout=center, size=full} * Fix overlaps between corner tiles; * Compute target rectangles for top, right, bottom, and left tiles; * Fix overlaps between left/right and top/bottom shadow tiles. Test Plan: * Ran tests; * Resized Konsole to its minimimum size(on X11 and Wayland); * Opened the calendar popup(on X11 and Wayland): Before: {F6190344, layout=center, size=full} After: {F6190346, layout=center, size=full} Reviewers: #kwin, davidedmundson Reviewed By: #kwin, davidedmundson Subscribers: abetts, davidedmundson, kwin Tags: #kwin Differential Revision: https://phabricator.kde.org/D14783
2018-08-12 18:23:12 +00:00
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);
[scenes/opengl] Correctly draw shadows when corner tiles are missing Summary: Current implementation of buildQuads assumes that corner shadow tiles are always present: const QRectF leftRect( topLeftRect.bottomLeft(), bottomLeftRect.topRight()); but that assumption is wrong. For example, if the default panel is on the bottom screen edge, then the calendar popup won't have the bottom-left shadow tile(at least on Wayland). Which means that the left shadow tile won't be visible because topLeftRect.left() == bottomLeftRect.right(). Corner rectangles only have to influence height of the left/right tile and width of the top/bottom tile. Width of the left/right tile and height of the top/bottom tile should not be controlled by corner tiles. Overall, this is how shadow quads are computed: * Compute the outer rectangle; * Compute target rectangle for each corner tile. If some corner tile is missing, move the target rectangle to the corresponding corner of the inner shadow rect and set its width and height to 0. We need to do that to prevent top/right/bottom/left tiles from spanning over corners: {F6190219, layout=center, size=full} We would rather prefer something like this if the top-left tile is missing: {F6190233, layout=center, size=full} * Fix overlaps between corner tiles; * Compute target rectangles for top, right, bottom, and left tiles; * Fix overlaps between left/right and top/bottom shadow tiles. Test Plan: * Ran tests; * Resized Konsole to its minimimum size(on X11 and Wayland); * Opened the calendar popup(on X11 and Wayland): Before: {F6190344, layout=center, size=full} After: {F6190346, layout=center, size=full} Reviewers: #kwin, davidedmundson Reviewed By: #kwin, davidedmundson Subscribers: abetts, davidedmundson, kwin Tags: #kwin Differential Revision: https://phabricator.kde.org/D14783
2018-08-12 18:23:12 +00:00
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();
[scenes/opengl] Correctly draw shadows when corner tiles are missing Summary: Current implementation of buildQuads assumes that corner shadow tiles are always present: const QRectF leftRect( topLeftRect.bottomLeft(), bottomLeftRect.topRight()); but that assumption is wrong. For example, if the default panel is on the bottom screen edge, then the calendar popup won't have the bottom-left shadow tile(at least on Wayland). Which means that the left shadow tile won't be visible because topLeftRect.left() == bottomLeftRect.right(). Corner rectangles only have to influence height of the left/right tile and width of the top/bottom tile. Width of the left/right tile and height of the top/bottom tile should not be controlled by corner tiles. Overall, this is how shadow quads are computed: * Compute the outer rectangle; * Compute target rectangle for each corner tile. If some corner tile is missing, move the target rectangle to the corresponding corner of the inner shadow rect and set its width and height to 0. We need to do that to prevent top/right/bottom/left tiles from spanning over corners: {F6190219, layout=center, size=full} We would rather prefer something like this if the top-left tile is missing: {F6190233, layout=center, size=full} * Fix overlaps between corner tiles; * Compute target rectangles for top, right, bottom, and left tiles; * Fix overlaps between left/right and top/bottom shadow tiles. Test Plan: * Ran tests; * Resized Konsole to its minimimum size(on X11 and Wayland); * Opened the calendar popup(on X11 and Wayland): Before: {F6190344, layout=center, size=full} After: {F6190346, layout=center, size=full} Reviewers: #kwin, davidedmundson Reviewed By: #kwin, davidedmundson Subscribers: abetts, davidedmundson, kwin Tags: #kwin Differential Revision: https://phabricator.kde.org/D14783
2018-08-12 18:23:12 +00:00
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
[scenes/opengl] Correctly draw shadows when corner tiles are missing Summary: Current implementation of buildQuads assumes that corner shadow tiles are always present: const QRectF leftRect( topLeftRect.bottomLeft(), bottomLeftRect.topRight()); but that assumption is wrong. For example, if the default panel is on the bottom screen edge, then the calendar popup won't have the bottom-left shadow tile(at least on Wayland). Which means that the left shadow tile won't be visible because topLeftRect.left() == bottomLeftRect.right(). Corner rectangles only have to influence height of the left/right tile and width of the top/bottom tile. Width of the left/right tile and height of the top/bottom tile should not be controlled by corner tiles. Overall, this is how shadow quads are computed: * Compute the outer rectangle; * Compute target rectangle for each corner tile. If some corner tile is missing, move the target rectangle to the corresponding corner of the inner shadow rect and set its width and height to 0. We need to do that to prevent top/right/bottom/left tiles from spanning over corners: {F6190219, layout=center, size=full} We would rather prefer something like this if the top-left tile is missing: {F6190233, layout=center, size=full} * Fix overlaps between corner tiles; * Compute target rectangles for top, right, bottom, and left tiles; * Fix overlaps between left/right and top/bottom shadow tiles. Test Plan: * Ran tests; * Resized Konsole to its minimimum size(on X11 and Wayland); * Opened the calendar popup(on X11 and Wayland): Before: {F6190344, layout=center, size=full} After: {F6190346, layout=center, size=full} Reviewers: #kwin, davidedmundson Reviewed By: #kwin, davidedmundson Subscribers: abetts, davidedmundson, kwin Tags: #kwin Differential Revision: https://phabricator.kde.org/D14783
2018-08-12 18:23:12 +00:00
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()));
[scenes/opengl] Correctly draw shadows when corner tiles are missing Summary: Current implementation of buildQuads assumes that corner shadow tiles are always present: const QRectF leftRect( topLeftRect.bottomLeft(), bottomLeftRect.topRight()); but that assumption is wrong. For example, if the default panel is on the bottom screen edge, then the calendar popup won't have the bottom-left shadow tile(at least on Wayland). Which means that the left shadow tile won't be visible because topLeftRect.left() == bottomLeftRect.right(). Corner rectangles only have to influence height of the left/right tile and width of the top/bottom tile. Width of the left/right tile and height of the top/bottom tile should not be controlled by corner tiles. Overall, this is how shadow quads are computed: * Compute the outer rectangle; * Compute target rectangle for each corner tile. If some corner tile is missing, move the target rectangle to the corresponding corner of the inner shadow rect and set its width and height to 0. We need to do that to prevent top/right/bottom/left tiles from spanning over corners: {F6190219, layout=center, size=full} We would rather prefer something like this if the top-left tile is missing: {F6190233, layout=center, size=full} * Fix overlaps between corner tiles; * Compute target rectangles for top, right, bottom, and left tiles; * Fix overlaps between left/right and top/bottom shadow tiles. Test Plan: * Ran tests; * Resized Konsole to its minimimum size(on X11 and Wayland); * Opened the calendar popup(on X11 and Wayland): Before: {F6190344, layout=center, size=full} After: {F6190346, layout=center, size=full} Reviewers: #kwin, davidedmundson Reviewed By: #kwin, davidedmundson Subscribers: abetts, davidedmundson, kwin Tags: #kwin Differential Revision: https://phabricator.kde.org/D14783
2018-08-12 18:23:12 +00:00
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);
[scenes/opengl] Correctly draw shadows when corner tiles are missing Summary: Current implementation of buildQuads assumes that corner shadow tiles are always present: const QRectF leftRect( topLeftRect.bottomLeft(), bottomLeftRect.topRight()); but that assumption is wrong. For example, if the default panel is on the bottom screen edge, then the calendar popup won't have the bottom-left shadow tile(at least on Wayland). Which means that the left shadow tile won't be visible because topLeftRect.left() == bottomLeftRect.right(). Corner rectangles only have to influence height of the left/right tile and width of the top/bottom tile. Width of the left/right tile and height of the top/bottom tile should not be controlled by corner tiles. Overall, this is how shadow quads are computed: * Compute the outer rectangle; * Compute target rectangle for each corner tile. If some corner tile is missing, move the target rectangle to the corresponding corner of the inner shadow rect and set its width and height to 0. We need to do that to prevent top/right/bottom/left tiles from spanning over corners: {F6190219, layout=center, size=full} We would rather prefer something like this if the top-left tile is missing: {F6190233, layout=center, size=full} * Fix overlaps between corner tiles; * Compute target rectangles for top, right, bottom, and left tiles; * Fix overlaps between left/right and top/bottom shadow tiles. Test Plan: * Ran tests; * Resized Konsole to its minimimum size(on X11 and Wayland); * Opened the calendar popup(on X11 and Wayland): Before: {F6190344, layout=center, size=full} After: {F6190346, layout=center, size=full} Reviewers: #kwin, davidedmundson Reviewed By: #kwin, davidedmundson Subscribers: abetts, davidedmundson, kwin Tags: #kwin Differential Revision: https://phabricator.kde.org/D14783
2018-08-12 18:23:12 +00:00
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();
[scenes/opengl] Correctly draw shadows when corner tiles are missing Summary: Current implementation of buildQuads assumes that corner shadow tiles are always present: const QRectF leftRect( topLeftRect.bottomLeft(), bottomLeftRect.topRight()); but that assumption is wrong. For example, if the default panel is on the bottom screen edge, then the calendar popup won't have the bottom-left shadow tile(at least on Wayland). Which means that the left shadow tile won't be visible because topLeftRect.left() == bottomLeftRect.right(). Corner rectangles only have to influence height of the left/right tile and width of the top/bottom tile. Width of the left/right tile and height of the top/bottom tile should not be controlled by corner tiles. Overall, this is how shadow quads are computed: * Compute the outer rectangle; * Compute target rectangle for each corner tile. If some corner tile is missing, move the target rectangle to the corresponding corner of the inner shadow rect and set its width and height to 0. We need to do that to prevent top/right/bottom/left tiles from spanning over corners: {F6190219, layout=center, size=full} We would rather prefer something like this if the top-left tile is missing: {F6190233, layout=center, size=full} * Fix overlaps between corner tiles; * Compute target rectangles for top, right, bottom, and left tiles; * Fix overlaps between left/right and top/bottom shadow tiles. Test Plan: * Ran tests; * Resized Konsole to its minimimum size(on X11 and Wayland); * Opened the calendar popup(on X11 and Wayland): Before: {F6190344, layout=center, size=full} After: {F6190346, layout=center, size=full} Reviewers: #kwin, davidedmundson Reviewed By: #kwin, davidedmundson Subscribers: abetts, davidedmundson, kwin Tags: #kwin Differential Revision: https://phabricator.kde.org/D14783
2018-08-12 18:23:12 +00:00
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"