Changeset View
Changeset View
Standalone View
Standalone View
autotests/integration/scene_opengl_shadow_test.cpp
- This file was added.
1 | /******************************************************************** | ||||
---|---|---|---|---|---|
2 | KWin - the KDE window manager | ||||
3 | This file is part of the KDE project. | ||||
4 | | ||||
5 | Copyright (C) 2018 Vlad Zagorodniy <vladzzag@gmail.com> | ||||
6 | | ||||
7 | This program is free software; you can redistribute it and/or modify | ||||
8 | it under the terms of the GNU General Public License as published by | ||||
9 | the Free Software Foundation; either version 2 of the License, or | ||||
10 | (at your option) any later version. | ||||
11 | | ||||
12 | This program is distributed in the hope that it will be useful, | ||||
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
15 | GNU General Public License for more details. | ||||
16 | | ||||
17 | You should have received a copy of the GNU General Public License | ||||
18 | along with this program. If not, see <http://www.gnu.org/licenses/>. | ||||
19 | *********************************************************************/ | ||||
20 | | ||||
21 | #include <algorithm> | ||||
22 | | ||||
23 | #include <QByteArray> | ||||
24 | #include <QDir> | ||||
25 | #include <QObject> | ||||
26 | #include <QPair> | ||||
27 | #include <QVector> | ||||
28 | | ||||
29 | #include <KDecoration2/Decoration> | ||||
30 | #include <KDecoration2/DecorationShadow> | ||||
31 | | ||||
32 | #include <KWayland/Client/server_decoration.h> | ||||
33 | #include <KWayland/Client/shell.h> | ||||
34 | #include <KWayland/Client/surface.h> | ||||
35 | | ||||
36 | #include "kwin_wayland_test.h" | ||||
37 | | ||||
38 | #include "composite.h" | ||||
39 | #include "effect_builtins.h" | ||||
40 | #include "effectloader.h" | ||||
41 | #include "effects.h" | ||||
42 | #include "platform.h" | ||||
43 | #include "shadow.h" | ||||
44 | #include "shell_client.h" | ||||
45 | #include "wayland_server.h" | ||||
46 | #include "workspace.h" | ||||
47 | | ||||
48 | Q_DECLARE_METATYPE(KWin::WindowQuadList); | ||||
49 | | ||||
50 | using namespace KWin; | ||||
51 | using namespace KWayland::Client; | ||||
52 | | ||||
53 | static const QString s_socketName = QStringLiteral("wayland_test_kwin_scene_opengl_shadow-0"); | ||||
54 | | ||||
55 | class SceneOpenGLShadowTest : public QObject | ||||
56 | { | ||||
57 | Q_OBJECT | ||||
58 | | ||||
59 | public: | ||||
60 | SceneOpenGLShadowTest() {} | ||||
61 | | ||||
62 | private Q_SLOTS: | ||||
63 | void initTestCase(); | ||||
64 | void init(); | ||||
65 | void cleanup(); | ||||
66 | | ||||
67 | void testShadowTileOverlaps_data(); | ||||
68 | void testShadowTileOverlaps(); | ||||
69 | | ||||
70 | }; | ||||
71 | | ||||
72 | inline bool isClose(double a, double b, double eps = 1e-5) | ||||
73 | { | ||||
74 | if (a == b) { | ||||
75 | return true; | ||||
76 | } | ||||
77 | const double diff = std::fabs(a - b); | ||||
78 | if (a == 0 || b == 0) { | ||||
79 | return diff < eps; | ||||
80 | } | ||||
81 | return diff / std::max(a, b) < eps; | ||||
82 | } | ||||
83 | | ||||
84 | inline bool compareQuads(const WindowQuad &a, const WindowQuad &b) | ||||
85 | { | ||||
86 | for (int i = 0; i < 4; i++) { | ||||
87 | if (!isClose(a[i].x(), b[i].x()) | ||||
88 | || !isClose(a[i].y(), b[i].y()) | ||||
89 | || !isClose(a[i].u(), b[i].u()) | ||||
90 | || !isClose(a[i].v(), b[i].v())) { | ||||
91 | return false; | ||||
92 | } | ||||
93 | } | ||||
94 | return true; | ||||
95 | } | ||||
96 | | ||||
97 | inline WindowQuad makeShadowQuad(const QRectF &geo, qreal tx1, qreal ty1, qreal tx2, qreal ty2) | ||||
98 | { | ||||
99 | WindowQuad quad(WindowQuadShadow); | ||||
100 | quad[0] = WindowVertex(geo.left(), geo.top(), tx1, ty1); | ||||
101 | quad[1] = WindowVertex(geo.right(), geo.top(), tx2, ty1); | ||||
102 | quad[2] = WindowVertex(geo.right(), geo.bottom(), tx2, ty2); | ||||
103 | quad[3] = WindowVertex(geo.left(), geo.bottom(), tx1, ty2); | ||||
104 | return quad; | ||||
105 | } | ||||
106 | | ||||
107 | void SceneOpenGLShadowTest::initTestCase() | ||||
108 | { | ||||
109 | // Copied from generic_scene_opengl_test.cpp | ||||
110 | | ||||
111 | if (!QFile::exists(QStringLiteral("/dev/dri/card0"))) { | ||||
112 | QSKIP("Needs a dri device"); | ||||
113 | } | ||||
114 | qRegisterMetaType<KWin::ShellClient*>(); | ||||
115 | qRegisterMetaType<KWin::AbstractClient*>(); | ||||
116 | QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); | ||||
117 | QVERIFY(workspaceCreatedSpy.isValid()); | ||||
118 | kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); | ||||
119 | QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); | ||||
120 | | ||||
121 | // disable all effects - we don't want to have it interact with the rendering | ||||
122 | auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); | ||||
123 | KConfigGroup plugins(config, QStringLiteral("Plugins")); | ||||
124 | ScriptedEffectLoader loader; | ||||
125 | const auto builtinNames = BuiltInEffects::availableEffectNames() << loader.listOfKnownEffects(); | ||||
126 | for (QString name : builtinNames) { | ||||
127 | plugins.writeEntry(name + QStringLiteral("Enabled"), false); | ||||
128 | } | ||||
129 | | ||||
130 | config->sync(); | ||||
131 | kwinApp()->setConfig(config); | ||||
132 | | ||||
133 | qputenv("XCURSOR_THEME", QByteArrayLiteral("DMZ-White")); | ||||
134 | qputenv("XCURSOR_SIZE", QByteArrayLiteral("24")); | ||||
135 | qputenv("KWIN_COMPOSE", QByteArrayLiteral("O2")); | ||||
136 | | ||||
137 | kwinApp()->start(); | ||||
138 | QVERIFY(workspaceCreatedSpy.wait()); | ||||
139 | QVERIFY(KWin::Compositor::self()); | ||||
140 | | ||||
141 | // Add directory with fake decorations to the plugin search path. | ||||
142 | QCoreApplication::addLibraryPath( | ||||
143 | QDir(QCoreApplication::applicationDirPath()).absoluteFilePath("fakes") | ||||
144 | ); | ||||
145 | | ||||
146 | // Change decoration theme. | ||||
147 | KConfigGroup group = kwinApp()->config()->group("org.kde.kdecoration2"); | ||||
148 | group.writeEntry("library", "org.kde.test.fakedecowithshadows"); | ||||
149 | group.sync(); | ||||
150 | Workspace::self()->slotReconfigure(); | ||||
151 | } | ||||
152 | | ||||
153 | void SceneOpenGLShadowTest::init() | ||||
154 | { | ||||
155 | QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Decoration)); | ||||
156 | } | ||||
157 | | ||||
158 | void SceneOpenGLShadowTest::cleanup() | ||||
159 | { | ||||
160 | Test::destroyWaylandConnection(); | ||||
161 | } | ||||
162 | | ||||
163 | namespace { | ||||
164 | const int SHADOW_SIZE = 128; | ||||
165 | | ||||
166 | const int SHADOW_OFFSET_TOP = 64; | ||||
167 | const int SHADOW_OFFSET_LEFT = 48; | ||||
168 | | ||||
169 | // NOTE: We assume deco shadows are generated with blur so that's | ||||
170 | // why there is 4, 1 is the size of the inner shadow rect. | ||||
171 | const int SHADOW_TEXTURE_WIDTH = 4 * SHADOW_SIZE + 1; | ||||
172 | const int SHADOW_TEXTURE_HEIGHT = 4 * SHADOW_SIZE + 1; | ||||
173 | | ||||
174 | const int SHADOW_PADDING_TOP = SHADOW_SIZE - SHADOW_OFFSET_TOP; | ||||
175 | const int SHADOW_PADDING_RIGHT = SHADOW_SIZE + SHADOW_OFFSET_LEFT; | ||||
176 | const int SHADOW_PADDING_BOTTOM = SHADOW_SIZE + SHADOW_OFFSET_TOP; | ||||
177 | const int SHADOW_PADDING_LEFT = SHADOW_SIZE - SHADOW_OFFSET_LEFT; | ||||
178 | | ||||
179 | const QRectF SHADOW_INNER_RECT(2 * SHADOW_SIZE, 2 * SHADOW_SIZE, 1, 1); | ||||
180 | } | ||||
181 | | ||||
182 | void SceneOpenGLShadowTest::testShadowTileOverlaps_data() | ||||
183 | { | ||||
184 | QTest::addColumn<QSize>("windowSize"); | ||||
185 | QTest::addColumn<WindowQuadList>("expectedQuads"); | ||||
186 | | ||||
187 | // Precompute shadow tile geometries(in texture's space). | ||||
188 | const QRectF topLeftTile( | ||||
189 | 0, | ||||
190 | 0, | ||||
191 | SHADOW_INNER_RECT.x(), | ||||
192 | SHADOW_INNER_RECT.y()); | ||||
193 | const QRectF topRightTile( | ||||
194 | SHADOW_INNER_RECT.right(), | ||||
195 | 0, | ||||
196 | SHADOW_TEXTURE_WIDTH - SHADOW_INNER_RECT.right(), | ||||
197 | SHADOW_INNER_RECT.y()); | ||||
198 | const QRectF topTile(topLeftTile.topRight(), topRightTile.bottomLeft()); | ||||
199 | | ||||
200 | const QRectF bottomLeftTile( | ||||
201 | 0, | ||||
202 | SHADOW_INNER_RECT.bottom(), | ||||
203 | SHADOW_INNER_RECT.x(), | ||||
204 | SHADOW_TEXTURE_HEIGHT - SHADOW_INNER_RECT.bottom()); | ||||
205 | const QRectF bottomRightTile( | ||||
206 | SHADOW_INNER_RECT.right(), | ||||
207 | SHADOW_INNER_RECT.bottom(), | ||||
208 | SHADOW_TEXTURE_WIDTH - SHADOW_INNER_RECT.right(), | ||||
209 | SHADOW_TEXTURE_HEIGHT - SHADOW_INNER_RECT.bottom()); | ||||
210 | const QRectF bottomTile(bottomLeftTile.topRight(), bottomRightTile.bottomLeft()); | ||||
211 | | ||||
212 | const QRectF leftTile(topLeftTile.bottomLeft(), bottomLeftTile.topRight()); | ||||
213 | const QRectF rightTile(topRightTile.bottomLeft(), bottomRightTile.topRight()); | ||||
214 | | ||||
215 | qreal tx1 = 0; | ||||
216 | qreal ty1 = 0; | ||||
217 | qreal tx2 = 0; | ||||
218 | qreal ty2 = 0; | ||||
219 | | ||||
220 | // Explanation behind numbers: (256+1 x 256+1) is the minimum window size | ||||
221 | // which doesn't cause overlapping of shadow tiles. For example, if a window | ||||
222 | // has (256 x 256+1) size, top-left and top-right or bottom-left and | ||||
223 | // bottom-right shadow tiles overlap. | ||||
224 | | ||||
225 | // No overlaps: In this case corner tiles are rendered as they are, | ||||
226 | // and top/right/bottom/left tiles are stretched. | ||||
227 | { | ||||
228 | const QSize windowSize(256 + 1, 256 + 1); | ||||
229 | WindowQuadList shadowQuads; | ||||
230 | | ||||
231 | const QRectF outerRect( | ||||
232 | -SHADOW_PADDING_LEFT, | ||||
233 | -SHADOW_PADDING_TOP, | ||||
234 | windowSize.width() + SHADOW_PADDING_LEFT + SHADOW_PADDING_RIGHT, | ||||
235 | windowSize.height() + SHADOW_PADDING_TOP + SHADOW_PADDING_BOTTOM); | ||||
236 | | ||||
237 | const QRectF topLeft( | ||||
238 | outerRect.left(), | ||||
239 | outerRect.top(), | ||||
240 | topLeftTile.width(), | ||||
241 | topLeftTile.height()); | ||||
242 | tx1 = topLeftTile.left() / SHADOW_TEXTURE_WIDTH; | ||||
243 | ty1 = topLeftTile.top() / SHADOW_TEXTURE_HEIGHT; | ||||
244 | tx2 = topLeftTile.right() / SHADOW_TEXTURE_WIDTH; | ||||
245 | ty2 = topLeftTile.bottom() / SHADOW_TEXTURE_HEIGHT; | ||||
246 | shadowQuads << makeShadowQuad(topLeft, tx1, ty1, tx2, ty2); | ||||
247 | | ||||
248 | const QRectF topRight( | ||||
249 | outerRect.right() - topRightTile.width(), | ||||
250 | outerRect.top(), | ||||
251 | topRightTile.width(), | ||||
252 | topRightTile.height()); | ||||
253 | tx1 = topRightTile.left() / SHADOW_TEXTURE_WIDTH; | ||||
254 | ty1 = topRightTile.top() / SHADOW_TEXTURE_HEIGHT; | ||||
255 | tx2 = topRightTile.right() / SHADOW_TEXTURE_WIDTH; | ||||
256 | ty2 = topRightTile.bottom() / SHADOW_TEXTURE_HEIGHT; | ||||
257 | shadowQuads << makeShadowQuad(topRight, tx1, ty1, tx2, ty2); | ||||
258 | | ||||
259 | const QRectF top(topLeft.topRight(), topRight.bottomLeft()); | ||||
260 | tx1 = topTile.left() / SHADOW_TEXTURE_WIDTH; | ||||
261 | ty1 = topTile.top() / SHADOW_TEXTURE_HEIGHT; | ||||
262 | tx2 = topTile.right() / SHADOW_TEXTURE_WIDTH; | ||||
263 | ty2 = topTile.bottom() / SHADOW_TEXTURE_HEIGHT; | ||||
264 | shadowQuads << makeShadowQuad(top, tx1, ty1, tx2, ty2); | ||||
265 | | ||||
266 | const QRectF bottomLeft( | ||||
267 | outerRect.left(), | ||||
268 | outerRect.bottom() - bottomLeftTile.height(), | ||||
269 | bottomLeftTile.width(), | ||||
270 | bottomLeftTile.height()); | ||||
271 | tx1 = bottomLeftTile.left() / SHADOW_TEXTURE_WIDTH; | ||||
272 | ty1 = bottomLeftTile.top() / SHADOW_TEXTURE_HEIGHT; | ||||
273 | tx2 = bottomLeftTile.right() / SHADOW_TEXTURE_WIDTH; | ||||
274 | ty2 = bottomLeftTile.bottom() / SHADOW_TEXTURE_HEIGHT; | ||||
275 | shadowQuads << makeShadowQuad(bottomLeft, tx1, ty1, tx2, ty2); | ||||
276 | | ||||
277 | const QRectF bottomRight( | ||||
278 | outerRect.right() - bottomRightTile.width(), | ||||
279 | outerRect.bottom() - bottomRightTile.height(), | ||||
280 | bottomRightTile.width(), | ||||
281 | bottomRightTile.height()); | ||||
282 | tx1 = bottomRightTile.left() / SHADOW_TEXTURE_WIDTH; | ||||
283 | ty1 = bottomRightTile.top() / SHADOW_TEXTURE_HEIGHT; | ||||
284 | tx2 = bottomRightTile.right() / SHADOW_TEXTURE_WIDTH; | ||||
285 | ty2 = bottomRightTile.bottom() / SHADOW_TEXTURE_HEIGHT; | ||||
286 | shadowQuads << makeShadowQuad(bottomRight, tx1, ty1, tx2, ty2); | ||||
287 | | ||||
288 | const QRectF bottom(bottomLeft.topRight(), bottomRight.bottomLeft()); | ||||
289 | tx1 = bottomTile.left() / SHADOW_TEXTURE_WIDTH; | ||||
290 | ty1 = bottomTile.top() / SHADOW_TEXTURE_HEIGHT; | ||||
291 | tx2 = bottomTile.right() / SHADOW_TEXTURE_WIDTH; | ||||
292 | ty2 = bottomTile.bottom() / SHADOW_TEXTURE_HEIGHT; | ||||
293 | shadowQuads << makeShadowQuad(bottom, tx1, ty1, tx2, ty2); | ||||
294 | | ||||
295 | const QRectF left(topLeft.bottomLeft(), bottomLeft.topRight()); | ||||
296 | tx1 = leftTile.left() / SHADOW_TEXTURE_WIDTH; | ||||
297 | ty1 = leftTile.top() / SHADOW_TEXTURE_HEIGHT; | ||||
298 | tx2 = leftTile.right() / SHADOW_TEXTURE_WIDTH; | ||||
299 | ty2 = leftTile.bottom() / SHADOW_TEXTURE_HEIGHT; | ||||
300 | shadowQuads << makeShadowQuad(left, tx1, ty1, tx2, ty2); | ||||
301 | | ||||
302 | const QRectF right(topRight.bottomLeft(), bottomRight.topRight()); | ||||
303 | tx1 = rightTile.left() / SHADOW_TEXTURE_WIDTH; | ||||
304 | ty1 = rightTile.top() / SHADOW_TEXTURE_HEIGHT; | ||||
305 | tx2 = rightTile.right() / SHADOW_TEXTURE_WIDTH; | ||||
306 | ty2 = rightTile.bottom() / SHADOW_TEXTURE_HEIGHT; | ||||
307 | shadowQuads << makeShadowQuad(right, tx1, ty1, tx2, ty2); | ||||
308 | | ||||
309 | QTest::newRow("no overlaps") << windowSize << shadowQuads; | ||||
310 | } | ||||
311 | | ||||
312 | // Top-Left & Bottom-Left/Top-Right & Bottom-Right overlap: | ||||
313 | // In this case overlapping parts are clipped and left/right | ||||
314 | // tiles aren't rendered. | ||||
315 | const QVector<QPair<QByteArray, QSize>> verticalOverlapTestTable { | ||||
316 | QPair<QByteArray, QSize> { | ||||
317 | QByteArray("top-left & bottom-left/top-right & bottom-right overlap"), | ||||
318 | QSize(256 + 1, 256) | ||||
319 | }, | ||||
320 | QPair<QByteArray, QSize> { | ||||
321 | QByteArray("top-left & bottom-left/top-right & bottom-right overlap :: pre"), | ||||
322 | QSize(256 + 1, 256 - 1) | ||||
323 | } | ||||
324 | // No need to test the case when window size is QSize(256 + 1, 256 + 1). | ||||
325 | // It has been tested already (no overlaps test case). | ||||
326 | }; | ||||
327 | | ||||
328 | for (auto const &tt : verticalOverlapTestTable) { | ||||
329 | const char *testName = tt.first.constData(); | ||||
330 | const QSize windowSize = tt.second; | ||||
331 | | ||||
332 | WindowQuadList shadowQuads; | ||||
333 | qreal halfOverlap = 0.0; | ||||
334 | | ||||
335 | const QRectF outerRect( | ||||
336 | -SHADOW_PADDING_LEFT, | ||||
337 | -SHADOW_PADDING_TOP, | ||||
338 | windowSize.width() + SHADOW_PADDING_LEFT + SHADOW_PADDING_RIGHT, | ||||
339 | windowSize.height() + SHADOW_PADDING_TOP + SHADOW_PADDING_BOTTOM); | ||||
340 | | ||||
341 | QRectF topLeft( | ||||
342 | outerRect.left(), | ||||
343 | outerRect.top(), | ||||
344 | topLeftTile.width(), | ||||
345 | topLeftTile.height()); | ||||
346 | | ||||
347 | QRectF bottomLeft( | ||||
348 | outerRect.left(), | ||||
349 | outerRect.bottom() - bottomLeftTile.height(), | ||||
350 | bottomLeftTile.width(), | ||||
351 | bottomLeftTile.height()); | ||||
352 | | ||||
353 | halfOverlap = qAbs(topLeft.bottom() - bottomLeft.top()) / 2; | ||||
354 | topLeft.setBottom(topLeft.bottom() - halfOverlap); | ||||
355 | bottomLeft.setTop(bottomLeft.top() + halfOverlap); | ||||
356 | | ||||
357 | tx1 = topLeftTile.left() / SHADOW_TEXTURE_WIDTH; | ||||
358 | ty1 = topLeftTile.top() / SHADOW_TEXTURE_HEIGHT; | ||||
359 | tx2 = topLeftTile.right() / SHADOW_TEXTURE_WIDTH; | ||||
360 | ty2 = topLeft.height() / SHADOW_TEXTURE_HEIGHT; | ||||
361 | shadowQuads << makeShadowQuad(topLeft, tx1, ty1, tx2, ty2); | ||||
362 | | ||||
363 | tx1 = bottomLeftTile.left() / SHADOW_TEXTURE_WIDTH; | ||||
364 | ty1 = 1.0 - (bottomLeft.height() / SHADOW_TEXTURE_HEIGHT); | ||||
365 | tx2 = bottomLeftTile.right() / SHADOW_TEXTURE_WIDTH; | ||||
366 | ty2 = bottomLeftTile.bottom() / SHADOW_TEXTURE_HEIGHT; | ||||
367 | shadowQuads << makeShadowQuad(bottomLeft, tx1, ty1, tx2, ty2); | ||||
368 | | ||||
369 | QRectF topRight( | ||||
370 | outerRect.right() - topRightTile.width(), | ||||
371 | outerRect.top(), | ||||
372 | topRightTile.width(), | ||||
373 | topRightTile.height()); | ||||
374 | | ||||
375 | QRectF bottomRight( | ||||
376 | outerRect.right() - bottomRightTile.width(), | ||||
377 | outerRect.bottom() - bottomRightTile.height(), | ||||
378 | bottomRightTile.width(), | ||||
379 | bottomRightTile.height()); | ||||
380 | | ||||
381 | halfOverlap = qAbs(topRight.bottom() - bottomRight.top()) / 2; | ||||
382 | topRight.setBottom(topRight.bottom() - halfOverlap); | ||||
383 | bottomRight.setTop(bottomRight.top() + halfOverlap); | ||||
384 | | ||||
385 | tx1 = topRightTile.left() / SHADOW_TEXTURE_WIDTH; | ||||
386 | ty1 = topRightTile.top() / SHADOW_TEXTURE_HEIGHT; | ||||
387 | tx2 = topRightTile.right() / SHADOW_TEXTURE_WIDTH; | ||||
388 | ty2 = topRight.height() / SHADOW_TEXTURE_HEIGHT; | ||||
389 | shadowQuads << makeShadowQuad(topRight, tx1, ty1, tx2, ty2); | ||||
390 | | ||||
391 | tx1 = bottomRightTile.left() / SHADOW_TEXTURE_WIDTH; | ||||
392 | ty1 = 1.0 - (bottomRight.height() / SHADOW_TEXTURE_HEIGHT); | ||||
393 | tx2 = bottomRightTile.right() / SHADOW_TEXTURE_WIDTH; | ||||
394 | ty2 = bottomRightTile.bottom() / SHADOW_TEXTURE_HEIGHT; | ||||
395 | shadowQuads << makeShadowQuad(bottomRight, tx1, ty1, tx2, ty2); | ||||
396 | | ||||
397 | const QRectF top(topLeft.topRight(), topRight.bottomLeft()); | ||||
398 | tx1 = topTile.left() / SHADOW_TEXTURE_WIDTH; | ||||
399 | ty1 = topTile.top() / SHADOW_TEXTURE_HEIGHT; | ||||
400 | tx2 = topTile.right() / SHADOW_TEXTURE_WIDTH; | ||||
401 | ty2 = top.height() / SHADOW_TEXTURE_HEIGHT; | ||||
402 | shadowQuads << makeShadowQuad(top, tx1, ty1, tx2, ty2); | ||||
403 | | ||||
404 | const QRectF bottom(bottomLeft.topRight(), bottomRight.bottomLeft()); | ||||
405 | tx1 = bottomTile.left() / SHADOW_TEXTURE_WIDTH; | ||||
406 | ty1 = 1.0 - (bottom.height() / SHADOW_TEXTURE_HEIGHT); | ||||
407 | tx2 = bottomTile.right() / SHADOW_TEXTURE_WIDTH; | ||||
408 | ty2 = bottomTile.bottom() / SHADOW_TEXTURE_HEIGHT; | ||||
409 | shadowQuads << makeShadowQuad(bottom, tx1, ty1, tx2, ty2); | ||||
410 | | ||||
411 | QTest::newRow(testName) << windowSize << shadowQuads; | ||||
412 | } | ||||
413 | | ||||
414 | // Top-Left & Top-Right/Bottom-Left & Bottom-Right overlap: | ||||
415 | // In this case overlapping parts are clipped and top/bottom | ||||
416 | // tiles aren't rendered. | ||||
417 | const QVector<QPair<QByteArray, QSize>> horizontalOverlapTestTable { | ||||
418 | QPair<QByteArray, QSize> { | ||||
419 | QByteArray("top-left & top-right/bottom-left & bottom-right overlap"), | ||||
420 | QSize(256, 256 + 1) | ||||
421 | }, | ||||
422 | QPair<QByteArray, QSize> { | ||||
423 | QByteArray("top-left & top-right/bottom-left & bottom-right overlap :: pre"), | ||||
424 | QSize(256 - 1, 256 + 1) | ||||
425 | } | ||||
426 | // No need to test the case when window size is QSize(256 + 1, 256 + 1). | ||||
427 | // It has been tested already (no overlaps test case). | ||||
428 | }; | ||||
429 | | ||||
430 | for (auto const &tt : horizontalOverlapTestTable) { | ||||
431 | const char *testName = tt.first.constData(); | ||||
432 | const QSize windowSize = tt.second; | ||||
433 | | ||||
434 | WindowQuadList shadowQuads; | ||||
435 | qreal halfOverlap = 0.0; | ||||
436 | | ||||
437 | const QRectF outerRect( | ||||
438 | -SHADOW_PADDING_LEFT, | ||||
439 | -SHADOW_PADDING_TOP, | ||||
440 | windowSize.width() + SHADOW_PADDING_LEFT + SHADOW_PADDING_RIGHT, | ||||
441 | windowSize.height() + SHADOW_PADDING_TOP + SHADOW_PADDING_BOTTOM); | ||||
442 | | ||||
443 | QRectF topLeft( | ||||
444 | outerRect.left(), | ||||
445 | outerRect.top(), | ||||
446 | topLeftTile.width(), | ||||
447 | topLeftTile.height()); | ||||
448 | | ||||
449 | QRectF topRight( | ||||
450 | outerRect.right() - topRightTile.width(), | ||||
451 | outerRect.top(), | ||||
452 | topRightTile.width(), | ||||
453 | topRightTile.height()); | ||||
454 | | ||||
455 | halfOverlap = qAbs(topLeft.right() - topRight.left()) / 2; | ||||
456 | topLeft.setRight(topLeft.right() - halfOverlap); | ||||
457 | topRight.setLeft(topRight.left() + halfOverlap); | ||||
458 | | ||||
459 | tx1 = topLeftTile.left() / SHADOW_TEXTURE_WIDTH; | ||||
460 | ty1 = topLeftTile.top() / SHADOW_TEXTURE_HEIGHT; | ||||
461 | tx2 = topLeft.width() / SHADOW_TEXTURE_WIDTH; | ||||
462 | ty2 = topLeftTile.bottom() / SHADOW_TEXTURE_HEIGHT; | ||||
463 | shadowQuads << makeShadowQuad(topLeft, tx1, ty1, tx2, ty2); | ||||
464 | | ||||
465 | tx1 = 1.0 - (topRight.width() / SHADOW_TEXTURE_WIDTH); | ||||
466 | ty1 = topRightTile.top() / SHADOW_TEXTURE_HEIGHT; | ||||
467 | tx2 = topRightTile.right() / SHADOW_TEXTURE_WIDTH; | ||||
468 | ty2 = topRightTile.bottom() / SHADOW_TEXTURE_HEIGHT; | ||||
469 | shadowQuads << makeShadowQuad(topRight, tx1, ty1, tx2, ty2); | ||||
470 | | ||||
471 | QRectF bottomLeft( | ||||
472 | outerRect.left(), | ||||
473 | outerRect.bottom() - bottomLeftTile.height(), | ||||
474 | bottomLeftTile.width(), | ||||
475 | bottomLeftTile.height()); | ||||
476 | | ||||
477 | QRectF bottomRight( | ||||
478 | outerRect.right() - bottomRightTile.width(), | ||||
479 | outerRect.bottom() - bottomRightTile.height(), | ||||
480 | bottomRightTile.width(), | ||||
481 | bottomRightTile.height()); | ||||
482 | | ||||
483 | halfOverlap = qAbs(bottomLeft.right() - bottomRight.left()) / 2; | ||||
484 | bottomLeft.setRight(bottomLeft.right() - halfOverlap); | ||||
485 | bottomRight.setLeft(bottomRight.left() + halfOverlap); | ||||
486 | | ||||
487 | tx1 = bottomLeftTile.left() / SHADOW_TEXTURE_WIDTH; | ||||
488 | ty1 = bottomLeftTile.top() / SHADOW_TEXTURE_HEIGHT; | ||||
489 | tx2 = bottomLeft.width() / SHADOW_TEXTURE_WIDTH; | ||||
490 | ty2 = bottomLeftTile.bottom() / SHADOW_TEXTURE_HEIGHT; | ||||
491 | shadowQuads << makeShadowQuad(bottomLeft, tx1, ty1, tx2, ty2); | ||||
492 | | ||||
493 | tx1 = 1.0 - (bottomRight.width() / SHADOW_TEXTURE_WIDTH); | ||||
494 | ty1 = bottomRightTile.top() / SHADOW_TEXTURE_HEIGHT; | ||||
495 | tx2 = bottomRightTile.right() / SHADOW_TEXTURE_WIDTH; | ||||
496 | ty2 = bottomRightTile.bottom() / SHADOW_TEXTURE_HEIGHT; | ||||
497 | shadowQuads << makeShadowQuad(bottomRight, tx1, ty1, tx2, ty2); | ||||
498 | | ||||
499 | const QRectF left(topLeft.bottomLeft(), bottomLeft.topRight()); | ||||
500 | tx1 = leftTile.left() / SHADOW_TEXTURE_WIDTH; | ||||
501 | ty1 = leftTile.top() / SHADOW_TEXTURE_HEIGHT; | ||||
502 | tx2 = left.width() / SHADOW_TEXTURE_WIDTH; | ||||
503 | ty2 = leftTile.bottom() / SHADOW_TEXTURE_HEIGHT; | ||||
504 | shadowQuads << makeShadowQuad(left, tx1, ty1, tx2, ty2); | ||||
505 | | ||||
506 | const QRectF right(topRight.bottomLeft(), bottomRight.topRight()); | ||||
507 | tx1 = 1.0 - (right.width() / SHADOW_TEXTURE_WIDTH); | ||||
508 | ty1 = rightTile.top() / SHADOW_TEXTURE_HEIGHT; | ||||
509 | tx2 = rightTile.right() / SHADOW_TEXTURE_WIDTH; | ||||
510 | ty2 = rightTile.bottom() / SHADOW_TEXTURE_HEIGHT; | ||||
511 | shadowQuads << makeShadowQuad(right, tx1, ty1, tx2, ty2); | ||||
512 | | ||||
513 | QTest::newRow(testName) << windowSize << shadowQuads; | ||||
514 | } | ||||
515 | | ||||
516 | // All shadow tiles overlap: In this case all overlapping parts | ||||
517 | // are clippend and top/right/bottom/left tiles aren't rendered. | ||||
518 | const QVector<QPair<QByteArray, QSize>> allOverlapTestTable { | ||||
519 | QPair<QByteArray, QSize> { | ||||
520 | QByteArray("all corner tiles overlap"), | ||||
521 | QSize(256, 256) | ||||
522 | }, | ||||
523 | QPair<QByteArray, QSize> { | ||||
524 | QByteArray("all corner tiles overlap :: pre"), | ||||
525 | QSize(256 - 1, 256 - 1) | ||||
526 | } | ||||
527 | // No need to test the case when window size is QSize(256 + 1, 256 + 1). | ||||
528 | // It has been tested already (no overlaps test case). | ||||
529 | }; | ||||
530 | | ||||
531 | for (auto const &tt : allOverlapTestTable) { | ||||
532 | const char *testName = tt.first.constData(); | ||||
533 | const QSize windowSize = tt.second; | ||||
534 | | ||||
535 | WindowQuadList shadowQuads; | ||||
536 | qreal halfOverlap = 0.0; | ||||
537 | | ||||
538 | const QRectF outerRect( | ||||
539 | -SHADOW_PADDING_LEFT, | ||||
540 | -SHADOW_PADDING_TOP, | ||||
541 | windowSize.width() + SHADOW_PADDING_LEFT + SHADOW_PADDING_RIGHT, | ||||
542 | windowSize.height() + SHADOW_PADDING_TOP + SHADOW_PADDING_BOTTOM); | ||||
543 | | ||||
544 | QRectF topLeft( | ||||
545 | outerRect.left(), | ||||
546 | outerRect.top(), | ||||
547 | topLeftTile.width(), | ||||
548 | topLeftTile.height()); | ||||
549 | | ||||
550 | QRectF topRight( | ||||
551 | outerRect.right() - topRightTile.width(), | ||||
552 | outerRect.top(), | ||||
553 | topRightTile.width(), | ||||
554 | topRightTile.height()); | ||||
555 | | ||||
556 | QRectF bottomLeft( | ||||
557 | outerRect.left(), | ||||
558 | outerRect.bottom() - bottomLeftTile.height(), | ||||
559 | bottomLeftTile.width(), | ||||
560 | bottomLeftTile.height()); | ||||
561 | | ||||
562 | QRectF bottomRight( | ||||
563 | outerRect.right() - bottomRightTile.width(), | ||||
564 | outerRect.bottom() - bottomRightTile.height(), | ||||
565 | bottomRightTile.width(), | ||||
566 | bottomRightTile.height()); | ||||
567 | | ||||
568 | halfOverlap = qAbs(topLeft.right() - topRight.left()) / 2; | ||||
569 | topLeft.setRight(topLeft.right() - halfOverlap); | ||||
570 | topRight.setLeft(topRight.left() + halfOverlap); | ||||
571 | | ||||
572 | halfOverlap = qAbs(bottomLeft.right() - bottomRight.left()) / 2; | ||||
573 | bottomLeft.setRight(bottomLeft.right() - halfOverlap); | ||||
574 | bottomRight.setLeft(bottomRight.left() + halfOverlap); | ||||
575 | | ||||
576 | halfOverlap = qAbs(topLeft.bottom() - bottomLeft.top()) / 2; | ||||
577 | topLeft.setBottom(topLeft.bottom() - halfOverlap); | ||||
578 | bottomLeft.setTop(bottomLeft.top() + halfOverlap); | ||||
579 | | ||||
580 | halfOverlap = qAbs(topRight.bottom() - bottomRight.top()) / 2; | ||||
581 | topRight.setBottom(topRight.bottom() - halfOverlap); | ||||
582 | bottomRight.setTop(bottomRight.top() + halfOverlap); | ||||
583 | | ||||
584 | tx1 = topLeftTile.left() / SHADOW_TEXTURE_WIDTH; | ||||
585 | ty1 = topLeftTile.top() / SHADOW_TEXTURE_HEIGHT; | ||||
586 | tx2 = topLeft.width() / SHADOW_TEXTURE_WIDTH; | ||||
587 | ty2 = topLeft.height() / SHADOW_TEXTURE_HEIGHT; | ||||
588 | shadowQuads << makeShadowQuad(topLeft, tx1, ty1, tx2, ty2); | ||||
589 | | ||||
590 | tx1 = 1.0 - (topRight.width() / SHADOW_TEXTURE_WIDTH); | ||||
591 | ty1 = topRightTile.top() / SHADOW_TEXTURE_HEIGHT; | ||||
592 | tx2 = topRightTile.right() / SHADOW_TEXTURE_WIDTH; | ||||
593 | ty2 = topRight.height() / SHADOW_TEXTURE_HEIGHT; | ||||
594 | shadowQuads << makeShadowQuad(topRight, tx1, ty1, tx2, ty2); | ||||
595 | | ||||
596 | tx1 = bottomLeftTile.left() / SHADOW_TEXTURE_WIDTH; | ||||
597 | ty1 = 1.0 - (bottomLeft.height() / SHADOW_TEXTURE_HEIGHT); | ||||
598 | tx2 = bottomLeft.width() / SHADOW_TEXTURE_WIDTH; | ||||
599 | ty2 = bottomLeftTile.bottom() / SHADOW_TEXTURE_HEIGHT; | ||||
600 | shadowQuads << makeShadowQuad(bottomLeft, tx1, ty1, tx2, ty2); | ||||
601 | | ||||
602 | tx1 = 1.0 - (bottomRight.width() / SHADOW_TEXTURE_WIDTH); | ||||
603 | ty1 = 1.0 - (bottomRight.height() / SHADOW_TEXTURE_HEIGHT); | ||||
604 | tx2 = bottomRightTile.right() / SHADOW_TEXTURE_WIDTH; | ||||
605 | ty2 = bottomRightTile.bottom() / SHADOW_TEXTURE_HEIGHT; | ||||
606 | shadowQuads << makeShadowQuad(bottomRight, tx1, ty1, tx2, ty2); | ||||
607 | | ||||
608 | QTest::newRow(testName) << windowSize << shadowQuads; | ||||
609 | } | ||||
610 | | ||||
611 | // Window is too small: do not render any shadow tiles. | ||||
612 | { | ||||
613 | const QSize windowSize(1, 1); | ||||
614 | const WindowQuadList shadowQuads; | ||||
615 | | ||||
616 | QTest::newRow("window is too small") << windowSize << shadowQuads; | ||||
617 | } | ||||
618 | } | ||||
619 | | ||||
620 | void SceneOpenGLShadowTest::testShadowTileOverlaps() | ||||
621 | { | ||||
622 | QFETCH(QSize, windowSize); | ||||
623 | QFETCH(WindowQuadList, expectedQuads); | ||||
624 | | ||||
625 | // Create a decorated client. | ||||
626 | QScopedPointer<Surface> surface(Test::createSurface()); | ||||
627 | QScopedPointer<ShellSurface> shellSurface(Test::createShellSurface(surface.data())); | ||||
628 | QScopedPointer<ServerSideDecoration> ssd(Test::waylandServerSideDecoration()->create(surface.data())); | ||||
629 | | ||||
630 | auto *client = Test::renderAndWaitForShown(surface.data(), windowSize, Qt::blue); | ||||
631 | | ||||
632 | QSignalSpy sizeChangedSpy(shellSurface.data(), &ShellSurface::sizeChanged); | ||||
633 | QVERIFY(sizeChangedSpy.isValid()); | ||||
634 | | ||||
635 | // Check the client is decorated. | ||||
636 | QVERIFY(client); | ||||
637 | QVERIFY(client->isDecorated()); | ||||
638 | auto *decoration = client->decoration(); | ||||
639 | QVERIFY(decoration); | ||||
640 | | ||||
641 | // If speciefied decoration theme is not found, KWin loads a default one | ||||
642 | // so we have to check whether a client has right decoration. | ||||
643 | auto decoShadow = decoration->shadow(); | ||||
644 | QCOMPARE(decoShadow->shadow().size(), QSize(SHADOW_TEXTURE_WIDTH, SHADOW_TEXTURE_HEIGHT)); | ||||
645 | QCOMPARE(decoShadow->paddingTop(), SHADOW_PADDING_TOP); | ||||
646 | QCOMPARE(decoShadow->paddingRight(), SHADOW_PADDING_RIGHT); | ||||
647 | QCOMPARE(decoShadow->paddingBottom(), SHADOW_PADDING_BOTTOM); | ||||
648 | QCOMPARE(decoShadow->paddingLeft(), SHADOW_PADDING_LEFT); | ||||
649 | | ||||
650 | // Get shadow. | ||||
651 | QVERIFY(client->effectWindow()); | ||||
652 | QVERIFY(client->effectWindow()->sceneWindow()); | ||||
653 | QVERIFY(client->effectWindow()->sceneWindow()->shadow()); | ||||
654 | auto *shadow = client->effectWindow()->sceneWindow()->shadow(); | ||||
655 | | ||||
656 | // Validate shadow quads. | ||||
657 | const WindowQuadList &quads = shadow->shadowQuads(); | ||||
658 | QCOMPARE(quads.size(), expectedQuads.size()); | ||||
659 | | ||||
660 | QVector<bool> mask(expectedQuads.size(), false); | ||||
661 | for (const auto &q : quads) { | ||||
662 | for (int i = 0; i < expectedQuads.size(); i++) { | ||||
663 | if (!compareQuads(q, expectedQuads[i])) { | ||||
664 | continue; | ||||
665 | } | ||||
666 | if (!mask[i]) { | ||||
667 | mask[i] = true; | ||||
668 | break; | ||||
669 | } else { | ||||
670 | QFAIL("got a duplicate shadow quad"); | ||||
671 | } | ||||
672 | } | ||||
673 | } | ||||
674 | | ||||
675 | for (const auto &v : qAsConst(mask)) { | ||||
676 | if (!v) { | ||||
677 | QFAIL("missed a shadow quad"); | ||||
678 | } | ||||
679 | } | ||||
680 | } | ||||
681 | | ||||
682 | WAYLANDTEST_MAIN(SceneOpenGLShadowTest) | ||||
683 | #include "scene_opengl_shadow_test.moc" |