Changeset View
Changeset View
Standalone View
Standalone View
autotests/integration/scene_qpainter_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 | #include <cmath> | ||||
23 | | ||||
24 | #include <QByteArray> | ||||
25 | #include <QDir> | ||||
26 | #include <QImage> | ||||
27 | #include <QMarginsF> | ||||
28 | #include <QObject> | ||||
29 | #include <QPainter> | ||||
30 | #include <QPair> | ||||
31 | #include <QVector> | ||||
32 | | ||||
33 | #include <KDecoration2/Decoration> | ||||
34 | #include <KDecoration2/DecorationShadow> | ||||
35 | | ||||
36 | #include <KWayland/Client/server_decoration.h> | ||||
37 | #include <KWayland/Client/shadow.h> | ||||
38 | #include <KWayland/Client/shell.h> | ||||
39 | #include <KWayland/Client/shm_pool.h> | ||||
40 | #include <KWayland/Client/surface.h> | ||||
41 | #include <KWayland/Server/shadow_interface.h> | ||||
42 | #include <KWayland/Server/surface_interface.h> | ||||
43 | | ||||
44 | #include "kwin_wayland_test.h" | ||||
45 | | ||||
46 | #include "composite.h" | ||||
47 | #include "effect_builtins.h" | ||||
48 | #include "effectloader.h" | ||||
49 | #include "effects.h" | ||||
50 | #include "platform.h" | ||||
51 | #include "plugins/scenes/qpainter/scene_qpainter.h" | ||||
52 | #include "shadow.h" | ||||
53 | #include "shell_client.h" | ||||
54 | #include "wayland_server.h" | ||||
55 | #include "workspace.h" | ||||
56 | | ||||
57 | using namespace KWin; | ||||
58 | using namespace KWayland::Client; | ||||
59 | | ||||
60 | static const QString s_socketName = QStringLiteral("wayland_test_kwin_scene_qpainter_shadow-0"); | ||||
61 | | ||||
62 | class SceneQPainterShadowTest : public QObject | ||||
63 | { | ||||
64 | Q_OBJECT | ||||
65 | public: | ||||
66 | SceneQPainterShadowTest() {} | ||||
67 | | ||||
68 | private Q_SLOTS: | ||||
69 | void initTestCase(); | ||||
70 | void cleanup(); | ||||
71 | | ||||
72 | void testShadowTileOverlaps_data(); | ||||
73 | void testShadowTileOverlaps(); | ||||
74 | void testShadowTextureReconstruction(); | ||||
75 | | ||||
76 | }; | ||||
77 | | ||||
78 | | ||||
79 | /////////////////////////////////////////////////////////// | ||||
80 | // Helpers. | ||||
81 | /////////////////////////////////////////////////////////// | ||||
82 | | ||||
83 | inline bool compareDoubles(double a, double b, double eps = 1e-5) | ||||
84 | { | ||||
85 | if (a == b) { | ||||
86 | return true; | ||||
87 | } | ||||
88 | double diff = std::fabs(a - b); | ||||
89 | if (a == 0 || b == 0) { | ||||
90 | return diff < eps; | ||||
91 | } | ||||
92 | return diff / std::max(a, b) < eps; | ||||
93 | } | ||||
94 | | ||||
95 | inline bool compareQuads(const WindowQuad &a, const WindowQuad &b) | ||||
96 | { | ||||
97 | for (int i = 0; i < 4; i++) { | ||||
98 | if (compareDoubles(a[i].x(), b[i].x()) | ||||
99 | && compareDoubles(a[i].y(), b[i].y()) | ||||
100 | && compareDoubles(a[i].textureX(), b[i].textureX()) | ||||
101 | && compareDoubles(a[i].textureY(), b[i].textureY())) { | ||||
102 | return true; | ||||
103 | } | ||||
104 | } | ||||
105 | return false; | ||||
106 | } | ||||
107 | | ||||
108 | inline WindowQuad makeShadowQuad(const QRectF &geo, qreal tx1, qreal ty1, qreal tx2, qreal ty2) | ||||
109 | { | ||||
110 | WindowQuad quad(WindowQuadShadow); | ||||
111 | quad[0] = WindowVertex(geo.left(), geo.top(), tx1, ty1); | ||||
112 | quad[1] = WindowVertex(geo.right(), geo.top(), tx2, ty1); | ||||
113 | quad[2] = WindowVertex(geo.right(), geo.bottom(), tx2, ty2); | ||||
114 | quad[3] = WindowVertex(geo.left(), geo.bottom(), tx1, ty2); | ||||
115 | return quad; | ||||
116 | } | ||||
117 | | ||||
118 | // Need this one because WindowQuadList isn't registered with Q_DECLARE_METATYPE | ||||
119 | class WindowQuadListWrapper | ||||
120 | { | ||||
121 | public: | ||||
122 | WindowQuadListWrapper() {} | ||||
123 | WindowQuadListWrapper(const WindowQuadList &quadList) | ||||
124 | : m_quadList(quadList) {} | ||||
125 | WindowQuadListWrapper(const WindowQuadListWrapper &other) | ||||
126 | : m_quadList(other.m_quadList) {} | ||||
127 | ~WindowQuadListWrapper() {} | ||||
128 | | ||||
129 | WindowQuadList &quadList() { return m_quadList; } | ||||
130 | | ||||
131 | private: | ||||
132 | WindowQuadList m_quadList; | ||||
133 | }; | ||||
134 | Q_DECLARE_METATYPE(WindowQuadListWrapper); | ||||
135 | | ||||
136 | /////////////////////////////////////////////////////////// | ||||
137 | // End of helpers. | ||||
138 | /////////////////////////////////////////////////////////// | ||||
139 | | ||||
140 | void SceneQPainterShadowTest::initTestCase() | ||||
141 | { | ||||
142 | // Copied from scene_qpainter_test.cpp | ||||
143 | | ||||
144 | qRegisterMetaType<KWin::ShellClient*>(); | ||||
145 | qRegisterMetaType<KWin::AbstractClient*>(); | ||||
146 | QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); | ||||
147 | QVERIFY(workspaceCreatedSpy.isValid()); | ||||
148 | kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); | ||||
149 | QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); | ||||
150 | | ||||
151 | // disable all effects - we don't want to have it interact with the rendering | ||||
152 | auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); | ||||
153 | KConfigGroup plugins(config, QStringLiteral("Plugins")); | ||||
154 | ScriptedEffectLoader loader; | ||||
155 | const auto builtinNames = BuiltInEffects::availableEffectNames() << loader.listOfKnownEffects(); | ||||
156 | for (QString name : builtinNames) { | ||||
157 | plugins.writeEntry(name + QStringLiteral("Enabled"), false); | ||||
158 | } | ||||
159 | | ||||
160 | config->sync(); | ||||
161 | kwinApp()->setConfig(config); | ||||
162 | | ||||
163 | if (!QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("icons/DMZ-White/index.theme")).isEmpty()) { | ||||
164 | qputenv("XCURSOR_THEME", QByteArrayLiteral("DMZ-White")); | ||||
165 | } else { | ||||
166 | // might be vanilla-dmz (e.g. Arch, FreeBSD) | ||||
167 | qputenv("XCURSOR_THEME", QByteArrayLiteral("Vanilla-DMZ")); | ||||
168 | } | ||||
169 | qputenv("XCURSOR_SIZE", QByteArrayLiteral("24")); | ||||
170 | qputenv("KWIN_COMPOSE", QByteArrayLiteral("Q")); | ||||
171 | | ||||
172 | kwinApp()->start(); | ||||
173 | QVERIFY(workspaceCreatedSpy.wait()); | ||||
174 | QVERIFY(KWin::Compositor::self()); | ||||
175 | | ||||
176 | // Add directory with fake decorations to the plugin search path. | ||||
177 | QCoreApplication::addLibraryPath( | ||||
178 | QDir(QCoreApplication::applicationDirPath()).absoluteFilePath("fakes") | ||||
179 | ); | ||||
180 | | ||||
181 | // Change decoration theme. | ||||
182 | KConfigGroup group = kwinApp()->config()->group("org.kde.kdecoration2"); | ||||
183 | group.writeEntry("library", "org.kde.test.fakedecowithshadows"); | ||||
184 | group.sync(); | ||||
185 | Workspace::self()->slotReconfigure(); | ||||
186 | } | ||||
187 | | ||||
188 | void SceneQPainterShadowTest::cleanup() | ||||
189 | { | ||||
190 | Test::destroyWaylandConnection(); | ||||
191 | } | ||||
192 | | ||||
193 | namespace { | ||||
194 | const int SHADOW_SIZE = 128; | ||||
195 | | ||||
196 | const int SHADOW_OFFSET_TOP = 64; | ||||
197 | const int SHADOW_OFFSET_LEFT = 48; | ||||
198 | | ||||
199 | // NOTE: We assume deco shadows are generated with blur so that's | ||||
200 | // why there is 4, 1 is the size of the inner shadow rect. | ||||
201 | const int SHADOW_TEXTURE_WIDTH = 4 * SHADOW_SIZE + 1; | ||||
202 | const int SHADOW_TEXTURE_HEIGHT = 4 * SHADOW_SIZE + 1; | ||||
203 | | ||||
204 | const int SHADOW_PADDING_TOP = SHADOW_SIZE - SHADOW_OFFSET_TOP; | ||||
205 | const int SHADOW_PADDING_RIGHT = SHADOW_SIZE + SHADOW_OFFSET_LEFT; | ||||
206 | const int SHADOW_PADDING_BOTTOM = SHADOW_SIZE + SHADOW_OFFSET_TOP; | ||||
207 | const int SHADOW_PADDING_LEFT = SHADOW_SIZE - SHADOW_OFFSET_LEFT; | ||||
208 | | ||||
209 | const QRectF SHADOW_INNER_RECT(2 * SHADOW_SIZE, 2 * SHADOW_SIZE, 1, 1); | ||||
210 | } | ||||
211 | | ||||
212 | void SceneQPainterShadowTest::testShadowTileOverlaps_data() | ||||
213 | { | ||||
214 | QTest::addColumn<QSize>("windowSize"); | ||||
215 | QTest::addColumn<WindowQuadListWrapper>("expectedQuadsWrapper"); | ||||
216 | | ||||
217 | // Precompute shadow tile geometries(in texture's space). | ||||
218 | QRectF topLeftTile( | ||||
219 | 0, | ||||
220 | 0, | ||||
221 | SHADOW_INNER_RECT.x(), | ||||
222 | SHADOW_INNER_RECT.y()); | ||||
223 | QRectF topRightTile( | ||||
224 | SHADOW_INNER_RECT.right(), | ||||
225 | 0, | ||||
226 | SHADOW_TEXTURE_WIDTH - SHADOW_INNER_RECT.right(), | ||||
227 | SHADOW_INNER_RECT.y()); | ||||
228 | QRectF topTile(topLeftTile.topRight(), topRightTile.bottomLeft()); | ||||
229 | | ||||
230 | QRectF bottomLeftTile( | ||||
231 | 0, | ||||
232 | SHADOW_INNER_RECT.bottom(), | ||||
233 | SHADOW_INNER_RECT.x(), | ||||
234 | SHADOW_TEXTURE_HEIGHT - SHADOW_INNER_RECT.bottom()); | ||||
235 | QRectF bottomRightTile( | ||||
236 | SHADOW_INNER_RECT.right(), | ||||
237 | SHADOW_INNER_RECT.bottom(), | ||||
238 | SHADOW_TEXTURE_WIDTH - SHADOW_INNER_RECT.right(), | ||||
239 | SHADOW_TEXTURE_HEIGHT - SHADOW_INNER_RECT.bottom()); | ||||
240 | QRectF bottomTile(bottomLeftTile.topRight(), bottomRightTile.bottomLeft()); | ||||
241 | | ||||
242 | QRectF leftTile(topLeftTile.bottomLeft(), bottomLeftTile.topRight()); | ||||
243 | QRectF rightTile(topRightTile.bottomLeft(), bottomRightTile.topRight()); | ||||
244 | | ||||
245 | qreal tx1 = 0; | ||||
246 | qreal ty1 = 0; | ||||
247 | qreal tx2 = 0; | ||||
248 | qreal ty2 = 0; | ||||
249 | | ||||
250 | // Explanation behind numbers: (256+1 x 256+1) is the minimum window size | ||||
251 | // which doesn't cause overlapping of shadow tiles. For example, if a window | ||||
252 | // has (256 x 256+1) size, top-left and top-right or bottom-left and | ||||
253 | // bottom-right shadow tiles overlap. | ||||
254 | | ||||
255 | // No overlaps: In this case corner tiles are rendered as they are, | ||||
256 | // and top/right/bottom/left tiles are stretched. | ||||
257 | { | ||||
258 | QSize windowSize(256 + 1, 256 + 1); | ||||
259 | WindowQuadList shadowQuads; | ||||
260 | | ||||
261 | QRectF outerRect( | ||||
262 | -SHADOW_PADDING_LEFT, | ||||
263 | -SHADOW_PADDING_TOP, | ||||
264 | windowSize.width() + SHADOW_PADDING_LEFT + SHADOW_PADDING_RIGHT, | ||||
265 | windowSize.height() + SHADOW_PADDING_TOP + SHADOW_PADDING_BOTTOM); | ||||
266 | | ||||
267 | QRectF topLeft( | ||||
268 | outerRect.left(), | ||||
269 | outerRect.top(), | ||||
270 | topLeftTile.width(), | ||||
271 | topLeftTile.height()); | ||||
272 | tx1 = topLeftTile.left(); | ||||
273 | ty1 = topLeftTile.top(); | ||||
274 | tx2 = topLeftTile.right(); | ||||
275 | ty2 = topLeftTile.bottom(); | ||||
276 | shadowQuads << makeShadowQuad(topLeft, tx1, ty1, tx2, ty2); | ||||
277 | | ||||
278 | QRectF topRight( | ||||
279 | outerRect.right() - topRightTile.width(), | ||||
280 | outerRect.top(), | ||||
281 | topRightTile.width(), | ||||
282 | topRightTile.height()); | ||||
283 | tx1 = topRightTile.left(); | ||||
284 | ty1 = topRightTile.top(); | ||||
285 | tx2 = topRightTile.right(); | ||||
286 | ty2 = topRightTile.bottom(); | ||||
287 | shadowQuads << makeShadowQuad(topRight, tx1, ty1, tx2, ty2); | ||||
288 | | ||||
289 | QRectF top(topLeft.topRight(), topRight.bottomLeft()); | ||||
290 | tx1 = topTile.left(); | ||||
291 | ty1 = topTile.top(); | ||||
292 | tx2 = topTile.right(); | ||||
293 | ty2 = topTile.bottom(); | ||||
294 | shadowQuads << makeShadowQuad(top, tx1, ty1, tx2, ty2); | ||||
295 | | ||||
296 | QRectF bottomLeft( | ||||
297 | outerRect.left(), | ||||
298 | outerRect.bottom() - bottomLeftTile.height(), | ||||
299 | bottomLeftTile.width(), | ||||
300 | bottomLeftTile.height()); | ||||
301 | tx1 = bottomLeftTile.left(); | ||||
302 | ty1 = bottomLeftTile.top(); | ||||
303 | tx2 = bottomLeftTile.right(); | ||||
304 | ty2 = bottomLeftTile.bottom(); | ||||
305 | shadowQuads << makeShadowQuad(bottomLeft, tx1, ty1, tx2, ty2); | ||||
306 | | ||||
307 | QRectF bottomRight( | ||||
308 | outerRect.right() - bottomRightTile.width(), | ||||
309 | outerRect.bottom() - bottomRightTile.height(), | ||||
310 | bottomRightTile.width(), | ||||
311 | bottomRightTile.height()); | ||||
312 | tx1 = bottomRightTile.left(); | ||||
313 | ty1 = bottomRightTile.top(); | ||||
314 | tx2 = bottomRightTile.right(); | ||||
315 | ty2 = bottomRightTile.bottom(); | ||||
316 | shadowQuads << makeShadowQuad(bottomRight, tx1, ty1, tx2, ty2); | ||||
317 | | ||||
318 | QRectF bottom(bottomLeft.topRight(), bottomRight.bottomLeft()); | ||||
319 | tx1 = bottomTile.left(); | ||||
320 | ty1 = bottomTile.top(); | ||||
321 | tx2 = bottomTile.right(); | ||||
322 | ty2 = bottomTile.bottom(); | ||||
323 | shadowQuads << makeShadowQuad(bottom, tx1, ty1, tx2, ty2); | ||||
324 | | ||||
325 | QRectF left(topLeft.bottomLeft(), bottomLeft.topRight()); | ||||
326 | tx1 = leftTile.left(); | ||||
327 | ty1 = leftTile.top(); | ||||
328 | tx2 = leftTile.right(); | ||||
329 | ty2 = leftTile.bottom(); | ||||
330 | shadowQuads << makeShadowQuad(left, tx1, ty1, tx2, ty2); | ||||
331 | | ||||
332 | QRectF right(topRight.bottomLeft(), bottomRight.topRight()); | ||||
333 | tx1 = rightTile.left(); | ||||
334 | ty1 = rightTile.top(); | ||||
335 | tx2 = rightTile.right(); | ||||
336 | ty2 = rightTile.bottom(); | ||||
337 | shadowQuads << makeShadowQuad(right, tx1, ty1, tx2, ty2); | ||||
338 | | ||||
339 | QTest::newRow("no overlaps") | ||||
340 | << windowSize | ||||
341 | << WindowQuadListWrapper(shadowQuads); | ||||
342 | } | ||||
343 | | ||||
344 | // Top-Left & Bottom-Left/Top-Right & Bottom-Right overlap: | ||||
345 | // In this case overlapping parts are clipped and left/right | ||||
346 | // tiles aren't rendered. | ||||
347 | const QVector<QPair<QByteArray, QSize>> verticalOverlapTestTable { | ||||
348 | QPair<QByteArray, QSize> { | ||||
349 | QByteArray("top-left & bottom-left/top-right & bottom-right overlap"), | ||||
350 | QSize(256 + 1, 256) | ||||
351 | }, | ||||
352 | QPair<QByteArray, QSize> { | ||||
353 | QByteArray("top-left & bottom-left/top-right & bottom-right overlap :: pre"), | ||||
354 | QSize(256 + 1, 256 - 1) | ||||
355 | } | ||||
356 | // No need to test the case when window size is QSize(256 + 1, 256 + 1). | ||||
357 | // It has been tested already (no overlaps test case). | ||||
358 | }; | ||||
359 | | ||||
360 | for (auto const &tt : verticalOverlapTestTable) { | ||||
361 | const char *testName = tt.first.constData(); | ||||
362 | const QSize windowSize = tt.second; | ||||
363 | | ||||
364 | WindowQuadList shadowQuads; | ||||
365 | qreal halfOverlap = 0.0; | ||||
366 | | ||||
367 | QRectF outerRect( | ||||
368 | -SHADOW_PADDING_LEFT, | ||||
369 | -SHADOW_PADDING_TOP, | ||||
370 | windowSize.width() + SHADOW_PADDING_LEFT + SHADOW_PADDING_RIGHT, | ||||
371 | windowSize.height() + SHADOW_PADDING_TOP + SHADOW_PADDING_BOTTOM); | ||||
372 | | ||||
373 | QRectF topLeft( | ||||
374 | outerRect.left(), | ||||
375 | outerRect.top(), | ||||
376 | topLeftTile.width(), | ||||
377 | topLeftTile.height()); | ||||
378 | | ||||
379 | QRectF bottomLeft( | ||||
380 | outerRect.left(), | ||||
381 | outerRect.bottom() - bottomLeftTile.height(), | ||||
382 | bottomLeftTile.width(), | ||||
383 | bottomLeftTile.height()); | ||||
384 | | ||||
385 | halfOverlap = qAbs(topLeft.bottom() - bottomLeft.top()) / 2; | ||||
386 | topLeft.setBottom(topLeft.bottom() - std::ceil(halfOverlap)); | ||||
387 | bottomLeft.setTop(bottomLeft.top() + std::floor(halfOverlap)); | ||||
388 | | ||||
389 | tx1 = topLeftTile.left(); | ||||
390 | ty1 = topLeftTile.top(); | ||||
391 | tx2 = topLeftTile.right(); | ||||
392 | ty2 = topLeft.bottom(); | ||||
393 | shadowQuads << makeShadowQuad(topLeft, tx1, ty1, tx2, ty2); | ||||
394 | | ||||
395 | tx1 = bottomLeftTile.left(); | ||||
396 | ty1 = bottomLeft.top(); | ||||
397 | tx2 = bottomLeftTile.right(); | ||||
398 | ty2 = bottomLeftTile.bottom(); | ||||
399 | shadowQuads << makeShadowQuad(bottomLeft, tx1, ty1, tx2, ty2); | ||||
400 | | ||||
401 | QRectF topRight( | ||||
402 | outerRect.right() - topRightTile.width(), | ||||
403 | outerRect.top(), | ||||
404 | topRightTile.width(), | ||||
405 | topRightTile.height()); | ||||
406 | | ||||
407 | QRectF bottomRight( | ||||
408 | outerRect.right() - bottomRightTile.width(), | ||||
409 | outerRect.bottom() - bottomRightTile.height(), | ||||
410 | bottomRightTile.width(), | ||||
411 | bottomRightTile.height()); | ||||
412 | | ||||
413 | halfOverlap = qAbs(topRight.bottom() - bottomRight.top()) / 2; | ||||
414 | topRight.setBottom(topRight.bottom() - std::ceil(halfOverlap)); | ||||
415 | bottomRight.setTop(bottomRight.top() + std::floor(halfOverlap)); | ||||
416 | | ||||
417 | tx1 = topRightTile.left(); | ||||
418 | ty1 = topRightTile.top(); | ||||
419 | tx2 = topRightTile.right(); | ||||
420 | ty2 = topRight.bottom(); | ||||
421 | shadowQuads << makeShadowQuad(topRight, tx1, ty1, tx2, ty2); | ||||
422 | | ||||
423 | tx1 = bottomRightTile.left(); | ||||
424 | ty1 = bottomRight.top(); | ||||
425 | tx2 = bottomRightTile.right(); | ||||
426 | ty2 = bottomRightTile.bottom(); | ||||
427 | shadowQuads << makeShadowQuad(bottomRight, tx1, ty1, tx2, ty2); | ||||
428 | | ||||
429 | QRectF top(topLeft.topRight(), topRight.bottomLeft()); | ||||
430 | tx1 = topTile.left(); | ||||
431 | ty1 = topTile.top(); | ||||
432 | tx2 = topTile.right(); | ||||
433 | ty2 = top.height(); | ||||
434 | shadowQuads << makeShadowQuad(top, tx1, ty1, tx2, ty2); | ||||
435 | | ||||
436 | QRectF bottom(bottomLeft.topRight(), bottomRight.bottomLeft()); | ||||
437 | tx1 = bottomTile.left(); | ||||
438 | ty1 = SHADOW_TEXTURE_HEIGHT - bottomTile.height(); | ||||
439 | tx2 = bottomTile.right(); | ||||
440 | ty2 = bottomTile.bottom(); | ||||
441 | shadowQuads << makeShadowQuad(bottom, tx1, ty1, tx2, ty2); | ||||
442 | | ||||
443 | QTest::newRow(testName) | ||||
444 | << windowSize | ||||
445 | << WindowQuadListWrapper(shadowQuads); | ||||
446 | } | ||||
447 | | ||||
448 | // Top-Left & Top-Right/Bottom-Left & Bottom-Right overlap: | ||||
449 | // In this case overlapping parts are clipped and top/bottom | ||||
450 | // tiles aren't rendered. | ||||
451 | const QVector<QPair<QByteArray, QSize>> horizontalOverlapTestTable { | ||||
452 | QPair<QByteArray, QSize> { | ||||
453 | QByteArray("top-left & top-right/bottom-left & bottom-right overlap"), | ||||
454 | QSize(256, 256 + 1) | ||||
455 | }, | ||||
456 | QPair<QByteArray, QSize> { | ||||
457 | QByteArray("top-left & top-right/bottom-left & bottom-right overlap :: pre"), | ||||
458 | QSize(256 - 1, 256 + 1) | ||||
459 | } | ||||
460 | // No need to test the case when window size is QSize(256 + 1, 256 + 1). | ||||
461 | // It has been tested already (no overlaps test case). | ||||
462 | }; | ||||
463 | | ||||
464 | for (auto const &tt : horizontalOverlapTestTable) { | ||||
465 | const char *testName = tt.first.constData(); | ||||
466 | const QSize windowSize = tt.second; | ||||
467 | | ||||
468 | WindowQuadList shadowQuads; | ||||
469 | qreal halfOverlap = 0.0; | ||||
470 | | ||||
471 | QRectF outerRect( | ||||
472 | -SHADOW_PADDING_LEFT, | ||||
473 | -SHADOW_PADDING_TOP, | ||||
474 | windowSize.width() + SHADOW_PADDING_LEFT + SHADOW_PADDING_RIGHT, | ||||
475 | windowSize.height() + SHADOW_PADDING_TOP + SHADOW_PADDING_BOTTOM); | ||||
476 | | ||||
477 | QRectF topLeft( | ||||
478 | outerRect.left(), | ||||
479 | outerRect.top(), | ||||
480 | topLeftTile.width(), | ||||
481 | topLeftTile.height()); | ||||
482 | | ||||
483 | QRectF topRight( | ||||
484 | outerRect.right() - topRightTile.width(), | ||||
485 | outerRect.top(), | ||||
486 | topRightTile.width(), | ||||
487 | topRightTile.height()); | ||||
488 | | ||||
489 | halfOverlap = qAbs(topLeft.right() - topRight.left()) / 2; | ||||
490 | topLeft.setRight(topLeft.right() - std::ceil(halfOverlap)); | ||||
491 | topRight.setLeft(topRight.left() + std::floor(halfOverlap)); | ||||
492 | | ||||
493 | tx1 = topLeftTile.left(); | ||||
494 | ty1 = topLeftTile.top(); | ||||
495 | tx2 = topLeft.right(); | ||||
496 | ty2 = topLeftTile.bottom(); | ||||
497 | shadowQuads << makeShadowQuad(topLeft, tx1, ty1, tx2, ty2); | ||||
498 | | ||||
499 | tx1 = topRight.left(); | ||||
500 | ty1 = topRightTile.top(); | ||||
501 | tx2 = topRightTile.right(); | ||||
502 | ty2 = topRightTile.bottom(); | ||||
503 | shadowQuads << makeShadowQuad(topRight, tx1, ty1, tx2, ty2); | ||||
504 | | ||||
505 | QRectF bottomLeft( | ||||
506 | outerRect.left(), | ||||
507 | outerRect.bottom() - bottomLeftTile.height(), | ||||
508 | bottomLeftTile.width(), | ||||
509 | bottomLeftTile.height()); | ||||
510 | | ||||
511 | QRectF bottomRight( | ||||
512 | outerRect.right() - bottomRightTile.width(), | ||||
513 | outerRect.bottom() - bottomRightTile.height(), | ||||
514 | bottomRightTile.width(), | ||||
515 | bottomRightTile.height()); | ||||
516 | | ||||
517 | halfOverlap = qAbs(bottomLeft.right() - bottomRight.left()) / 2; | ||||
518 | bottomLeft.setRight(bottomLeft.right() - std::ceil(halfOverlap)); | ||||
519 | bottomRight.setLeft(bottomRight.left() + std::floor(halfOverlap)); | ||||
520 | | ||||
521 | tx1 = bottomLeftTile.left(); | ||||
522 | ty1 = bottomLeftTile.top(); | ||||
523 | tx2 = bottomLeft.right(); | ||||
524 | ty2 = bottomLeftTile.bottom(); | ||||
525 | shadowQuads << makeShadowQuad(bottomLeft, tx1, ty1, tx2, ty2); | ||||
526 | | ||||
527 | tx1 = bottomRight.left(); | ||||
528 | ty1 = bottomRightTile.top(); | ||||
529 | tx2 = bottomRightTile.right(); | ||||
530 | ty2 = bottomRightTile.bottom(); | ||||
531 | shadowQuads << makeShadowQuad(bottomRight, tx1, ty1, tx2, ty2); | ||||
532 | | ||||
533 | QRectF left(topLeft.bottomLeft(), bottomLeft.topRight()); | ||||
534 | tx1 = leftTile.left(); | ||||
535 | ty1 = leftTile.top(); | ||||
536 | tx2 = left.width(); | ||||
537 | ty2 = leftTile.bottom(); | ||||
538 | shadowQuads << makeShadowQuad(left, tx1, ty1, tx2, ty2); | ||||
539 | | ||||
540 | QRectF right(topRight.bottomLeft(), bottomRight.topRight()); | ||||
541 | tx1 = SHADOW_TEXTURE_WIDTH - right.width(); | ||||
542 | ty1 = rightTile.top(); | ||||
543 | tx2 = rightTile.right(); | ||||
544 | ty2 = rightTile.bottom(); | ||||
545 | shadowQuads << makeShadowQuad(right, tx1, ty1, tx2, ty2); | ||||
546 | | ||||
547 | QTest::newRow(testName) | ||||
548 | << windowSize | ||||
549 | << WindowQuadListWrapper(shadowQuads); | ||||
550 | } | ||||
551 | | ||||
552 | // All shadow tiles overlap: In this case all overlapping parts | ||||
553 | // are clippend and top/right/bottom/left tiles aren't rendered. | ||||
554 | const QVector<QPair<QByteArray, QSize>> allOverlapTestTable { | ||||
555 | QPair<QByteArray, QSize> { | ||||
556 | QByteArray("all corner tiles overlap"), | ||||
557 | QSize(256, 256) | ||||
558 | }, | ||||
559 | QPair<QByteArray, QSize> { | ||||
560 | QByteArray("all corner tiles overlap :: pre"), | ||||
561 | QSize(256 - 1, 256 - 1) | ||||
562 | } | ||||
563 | // No need to test the case when window size is QSize(256 + 1, 256 + 1). | ||||
564 | // It has been tested already (no overlaps test case). | ||||
565 | }; | ||||
566 | | ||||
567 | for (auto const &tt : allOverlapTestTable) { | ||||
568 | const char *testName = tt.first.constData(); | ||||
569 | const QSize windowSize = tt.second; | ||||
570 | | ||||
571 | WindowQuadList shadowQuads; | ||||
572 | qreal halfOverlap = 0.0; | ||||
573 | | ||||
574 | QRectF outerRect( | ||||
575 | -SHADOW_PADDING_LEFT, | ||||
576 | -SHADOW_PADDING_TOP, | ||||
577 | windowSize.width() + SHADOW_PADDING_LEFT + SHADOW_PADDING_RIGHT, | ||||
578 | windowSize.height() + SHADOW_PADDING_TOP + SHADOW_PADDING_BOTTOM); | ||||
579 | | ||||
580 | QRectF topLeft( | ||||
581 | outerRect.left(), | ||||
582 | outerRect.top(), | ||||
583 | topLeftTile.width(), | ||||
584 | topLeftTile.height()); | ||||
585 | | ||||
586 | QRectF topRight( | ||||
587 | outerRect.right() - topRightTile.width(), | ||||
588 | outerRect.top(), | ||||
589 | topRightTile.width(), | ||||
590 | topRightTile.height()); | ||||
591 | | ||||
592 | QRectF bottomLeft( | ||||
593 | outerRect.left(), | ||||
594 | outerRect.bottom() - bottomLeftTile.height(), | ||||
595 | bottomLeftTile.width(), | ||||
596 | bottomLeftTile.height()); | ||||
597 | | ||||
598 | QRectF bottomRight( | ||||
599 | outerRect.right() - bottomRightTile.width(), | ||||
600 | outerRect.bottom() - bottomRightTile.height(), | ||||
601 | bottomRightTile.width(), | ||||
602 | bottomRightTile.height()); | ||||
603 | | ||||
604 | halfOverlap = qAbs(topLeft.right() - topRight.left()) / 2; | ||||
605 | topLeft.setRight(topLeft.right() - std::ceil(halfOverlap)); | ||||
606 | topRight.setLeft(topRight.left() + std::floor(halfOverlap)); | ||||
607 | | ||||
608 | halfOverlap = qAbs(bottomLeft.right() - bottomRight.left()) / 2; | ||||
609 | bottomLeft.setRight(bottomLeft.right() - std::ceil(halfOverlap)); | ||||
610 | bottomRight.setLeft(bottomRight.left() + std::floor(halfOverlap)); | ||||
611 | | ||||
612 | halfOverlap = qAbs(topLeft.bottom() - bottomLeft.top()) / 2; | ||||
613 | topLeft.setBottom(topLeft.bottom() - std::ceil(halfOverlap)); | ||||
614 | bottomLeft.setTop(bottomLeft.top() + std::floor(halfOverlap)); | ||||
615 | | ||||
616 | halfOverlap = qAbs(topRight.bottom() - bottomRight.top()) / 2; | ||||
617 | topRight.setBottom(topRight.bottom() - std::ceil(halfOverlap)); | ||||
618 | bottomRight.setTop(bottomRight.top() + std::floor(halfOverlap)); | ||||
619 | | ||||
620 | tx1 = topLeftTile.left(); | ||||
621 | ty1 = topLeftTile.top(); | ||||
622 | tx2 = topLeft.width(); | ||||
623 | ty2 = topLeft.height(); | ||||
624 | shadowQuads << makeShadowQuad(topLeft, tx1, ty1, tx2, ty2); | ||||
625 | | ||||
626 | tx1 = SHADOW_TEXTURE_WIDTH - topRight.width(); | ||||
627 | ty1 = topRightTile.top(); | ||||
628 | tx2 = topRightTile.right(); | ||||
629 | ty2 = topRight.bottom(); | ||||
630 | shadowQuads << makeShadowQuad(topRight, tx1, ty1, tx2, ty2); | ||||
631 | | ||||
632 | tx1 = bottomLeftTile.left(); | ||||
633 | ty1 = SHADOW_TEXTURE_HEIGHT - bottomLeft.height(); | ||||
634 | tx2 = bottomLeft.width(); | ||||
635 | ty2 = bottomLeftTile.bottom(); | ||||
636 | shadowQuads << makeShadowQuad(bottomLeft, tx1, ty1, tx2, ty2); | ||||
637 | | ||||
638 | tx1 = SHADOW_TEXTURE_WIDTH - bottomRight.width(); | ||||
639 | ty1 = SHADOW_TEXTURE_HEIGHT - bottomRight.height(); | ||||
640 | tx2 = bottomRightTile.right(); | ||||
641 | ty2 = bottomRightTile.bottom(); | ||||
642 | shadowQuads << makeShadowQuad(bottomRight, tx1, ty1, tx2, ty2); | ||||
643 | | ||||
644 | QTest::newRow(testName) | ||||
645 | << windowSize | ||||
646 | << WindowQuadListWrapper(shadowQuads); | ||||
647 | } | ||||
648 | | ||||
649 | // Window is too small: do not render any shadow tiles. | ||||
650 | { | ||||
651 | QSize windowSize(1, 1); | ||||
652 | WindowQuadList shadowQuads; | ||||
653 | | ||||
654 | QTest::newRow("window too small") | ||||
655 | << windowSize | ||||
656 | << WindowQuadListWrapper(shadowQuads); | ||||
657 | } | ||||
658 | } | ||||
659 | | ||||
660 | void SceneQPainterShadowTest::testShadowTileOverlaps() | ||||
661 | { | ||||
662 | QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Decoration)); | ||||
663 | | ||||
664 | QFETCH(QSize, windowSize); | ||||
665 | QFETCH(WindowQuadListWrapper, expectedQuadsWrapper); | ||||
666 | WindowQuadList expectedQuads = expectedQuadsWrapper.quadList(); | ||||
667 | | ||||
668 | // Create a decorated client. | ||||
669 | QScopedPointer<Surface> surface(Test::createSurface()); | ||||
670 | QScopedPointer<ShellSurface> shellSurface(Test::createShellSurface(surface.data())); | ||||
671 | QScopedPointer<ServerSideDecoration> ssd(Test::waylandServerSideDecoration()->create(surface.data())); | ||||
672 | | ||||
673 | auto client = Test::renderAndWaitForShown(surface.data(), windowSize, Qt::blue); | ||||
674 | | ||||
675 | QSignalSpy sizeChangedSpy(shellSurface.data(), &ShellSurface::sizeChanged); | ||||
676 | QVERIFY(sizeChangedSpy.isValid()); | ||||
677 | | ||||
678 | // Check the client is decorated. | ||||
679 | QVERIFY(client); | ||||
680 | QVERIFY(client->isDecorated()); | ||||
681 | auto decoration = client->decoration(); | ||||
682 | QVERIFY(decoration); | ||||
683 | | ||||
684 | // If speciefied decoration theme is not found, KWin loads a default one | ||||
685 | // so we have to check whether a client has right decoration. | ||||
686 | auto decoShadow = decoration->shadow(); | ||||
687 | QCOMPARE(decoShadow->shadow().size(), QSize(SHADOW_TEXTURE_WIDTH, SHADOW_TEXTURE_HEIGHT)); | ||||
688 | QCOMPARE(decoShadow->paddingTop(), SHADOW_PADDING_TOP); | ||||
689 | QCOMPARE(decoShadow->paddingRight(), SHADOW_PADDING_RIGHT); | ||||
690 | QCOMPARE(decoShadow->paddingBottom(), SHADOW_PADDING_BOTTOM); | ||||
691 | QCOMPARE(decoShadow->paddingLeft(), SHADOW_PADDING_LEFT); | ||||
692 | | ||||
693 | // Get shadow. | ||||
694 | QVERIFY(client->effectWindow()); | ||||
695 | QVERIFY(client->effectWindow()->sceneWindow()); | ||||
696 | QVERIFY(client->effectWindow()->sceneWindow()->shadow()); | ||||
697 | auto shadow = client->effectWindow()->sceneWindow()->shadow(); | ||||
698 | | ||||
699 | // Validate shadow quads. | ||||
700 | const WindowQuadList &quads = shadow->shadowQuads(); | ||||
701 | QCOMPARE(quads.size(), expectedQuads.size()); | ||||
702 | | ||||
703 | QVector<bool> mask(expectedQuads.size(), false); | ||||
704 | for (const auto &q : quads) { | ||||
705 | for (int i = 0; i < expectedQuads.size(); i++) { | ||||
706 | if (! compareQuads(q, expectedQuads[i])) { | ||||
707 | continue; | ||||
708 | } | ||||
709 | if (! mask[i]) { | ||||
710 | mask[i] = true; | ||||
711 | break; | ||||
712 | } else { | ||||
713 | QFAIL("got a duplicate shadow quad"); | ||||
714 | } | ||||
715 | } | ||||
716 | } | ||||
717 | | ||||
718 | for (const auto v : mask) { | ||||
719 | if (! v) { | ||||
720 | QFAIL("missed a shadow quad"); | ||||
721 | } | ||||
722 | } | ||||
723 | } | ||||
724 | | ||||
725 | void SceneQPainterShadowTest::testShadowTextureReconstruction() | ||||
726 | { | ||||
727 | QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::ShadowManager)); | ||||
728 | | ||||
729 | // Create a surface. | ||||
730 | QScopedPointer<Surface> surface(Test::createSurface()); | ||||
731 | QScopedPointer<ShellSurface> shellSurface(Test::createShellSurface(surface.data())); | ||||
732 | auto client = Test::renderAndWaitForShown(surface.data(), QSize(512, 512), Qt::blue); | ||||
733 | QVERIFY(client); | ||||
734 | QVERIFY(!client->isDecorated()); | ||||
735 | | ||||
736 | // Render reference shadow texture with the following params: | ||||
737 | // - shadow size: 128 | ||||
738 | // - inner rect size: 1 | ||||
739 | // - padding: 128 | ||||
740 | QImage referenceShadowTexture(QSize(256 + 1, 256 + 1), QImage::Format_ARGB32_Premultiplied); | ||||
741 | referenceShadowTexture.fill(Qt::transparent); | ||||
742 | | ||||
743 | QPainter painter(&referenceShadowTexture); | ||||
744 | painter.fillRect(QRect(10, 10, 192, 200), QColor(255, 0, 0, 128)); | ||||
745 | painter.fillRect(QRect(128, 30, 10, 180), QColor(0, 0, 0, 30)); | ||||
746 | painter.fillRect(QRect(20, 140, 160, 10), QColor(0, 255, 0, 128)); | ||||
747 | | ||||
748 | painter.setCompositionMode(QPainter::CompositionMode_DestinationOut); | ||||
749 | painter.fillRect(QRect(128, 128, 1, 1), Qt::black); | ||||
750 | painter.end(); | ||||
751 | | ||||
752 | // Create shadow. | ||||
753 | QScopedPointer<KWayland::Client::Shadow> clientShadow(Test::waylandShadowManager()->createShadow(surface.data())); | ||||
754 | QVERIFY(clientShadow->isValid()); | ||||
755 | | ||||
756 | auto shmPool = Test::waylandShmPool(); | ||||
757 | | ||||
758 | Buffer::Ptr bufferTopLeft = shmPool->createBuffer( | ||||
759 | referenceShadowTexture.copy(QRect(0, 0, 128, 128))); | ||||
760 | clientShadow->attachTopLeft(bufferTopLeft); | ||||
761 | | ||||
762 | Buffer::Ptr bufferTop = shmPool->createBuffer( | ||||
763 | referenceShadowTexture.copy(QRect(128, 0, 1, 128))); | ||||
764 | clientShadow->attachTop(bufferTop); | ||||
765 | | ||||
766 | Buffer::Ptr bufferTopRight = shmPool->createBuffer( | ||||
767 | referenceShadowTexture.copy(QRect(128 + 1, 0, 128, 128))); | ||||
768 | clientShadow->attachTopRight(bufferTopRight); | ||||
769 | | ||||
770 | Buffer::Ptr bufferRight = shmPool->createBuffer( | ||||
771 | referenceShadowTexture.copy(QRect(128 + 1, 128, 128, 1))); | ||||
772 | clientShadow->attachRight(bufferRight); | ||||
773 | | ||||
774 | Buffer::Ptr bufferBottomRight = shmPool->createBuffer( | ||||
775 | referenceShadowTexture.copy(QRect(128 + 1, 128 + 1, 128, 128))); | ||||
776 | clientShadow->attachBottomRight(bufferBottomRight); | ||||
777 | | ||||
778 | Buffer::Ptr bufferBottom = shmPool->createBuffer( | ||||
779 | referenceShadowTexture.copy(QRect(128, 128 + 1, 1, 128))); | ||||
780 | clientShadow->attachBottom(bufferBottom); | ||||
781 | | ||||
782 | Buffer::Ptr bufferBottomLeft = shmPool->createBuffer( | ||||
783 | referenceShadowTexture.copy(QRect(0, 128 + 1, 128, 128))); | ||||
784 | clientShadow->attachBottomLeft(bufferBottomLeft); | ||||
785 | | ||||
786 | Buffer::Ptr bufferLeft = shmPool->createBuffer( | ||||
787 | referenceShadowTexture.copy(QRect(0, 128, 128, 1))); | ||||
788 | clientShadow->attachLeft(bufferLeft); | ||||
789 | | ||||
790 | clientShadow->setOffsets(QMarginsF(128, 128, 128, 128)); | ||||
791 | | ||||
792 | // Commit shadow. | ||||
793 | QSignalSpy shadowChangedSpy(client->surface(), &KWayland::Server::SurfaceInterface::shadowChanged); | ||||
794 | QVERIFY(shadowChangedSpy.isValid()); | ||||
795 | clientShadow->commit(); | ||||
796 | surface->commit(Surface::CommitFlag::None); | ||||
797 | QVERIFY(shadowChangedSpy.wait()); | ||||
798 | | ||||
799 | // Check whether we've got right shadow. | ||||
800 | auto shadowIface = client->surface()->shadow(); | ||||
801 | QVERIFY(!shadowIface.isNull()); | ||||
802 | QCOMPARE(shadowIface->offset().left(), 128); | ||||
803 | QCOMPARE(shadowIface->offset().top(), 128); | ||||
804 | QCOMPARE(shadowIface->offset().right(), 128); | ||||
805 | QCOMPARE(shadowIface->offset().bottom(), 128); | ||||
806 | | ||||
807 | // Get SceneQPainterShadow's texture. | ||||
808 | QVERIFY(client->effectWindow()); | ||||
809 | QVERIFY(client->effectWindow()->sceneWindow()); | ||||
810 | QVERIFY(client->effectWindow()->sceneWindow()->shadow()); | ||||
811 | auto shadowTexture = static_cast<SceneQPainterShadow *>(client->effectWindow()->sceneWindow()->shadow())->shadowTexture(); | ||||
812 | | ||||
813 | QCOMPARE(shadowTexture, referenceShadowTexture); | ||||
814 | } | ||||
815 | | ||||
816 | WAYLANDTEST_MAIN(SceneQPainterShadowTest) | ||||
817 | #include "scene_qpainter_shadow_test.moc" |