1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the test suite of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
21 | ** included in the packaging of this file. Please review the following |
22 | ** information to ensure the GNU General Public License requirements will |
23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
24 | ** |
25 | ** $QT_END_LICENSE$ |
26 | ** |
27 | ****************************************************************************/ |
28 | |
29 | #include <QtWidgets/QOpenGLWidget> |
30 | #include <QtGui/QOpenGLFunctions> |
31 | #include <QtGui/QPainter> |
32 | #include <QtGui/QScreen> |
33 | #include <QtGui/QStaticText> |
34 | #include <QtWidgets/QDesktopWidget> |
35 | #include <QtWidgets/QGraphicsView> |
36 | #include <QtWidgets/QGraphicsScene> |
37 | #include <QtWidgets/QGraphicsRectItem> |
38 | #include <QtWidgets/QVBoxLayout> |
39 | #include <QtWidgets/QPushButton> |
40 | #include <QtWidgets/QStackedWidget> |
41 | #include <QtTest/QtTest> |
42 | #include <QSignalSpy> |
43 | #include <private/qguiapplication_p.h> |
44 | #include <private/qstatictext_p.h> |
45 | #include <private/qopengltextureglyphcache_p.h> |
46 | #include <qpa/qplatformintegration.h> |
47 | |
48 | class tst_QOpenGLWidget : public QObject |
49 | { |
50 | Q_OBJECT |
51 | |
52 | public: |
53 | static void initMain() { QCoreApplication::setAttribute(attribute: Qt::AA_DisableHighDpiScaling); } |
54 | |
55 | private slots: |
56 | void initTestCase(); |
57 | void create(); |
58 | void clearAndGrab(); |
59 | void clearAndResizeAndGrab(); |
60 | void createNonTopLevel(); |
61 | void painter(); |
62 | void reparentToAlreadyCreated(); |
63 | void reparentToNotYetCreated(); |
64 | void reparentHidden(); |
65 | void asViewport(); |
66 | void requestUpdate(); |
67 | void fboRedirect(); |
68 | void showHide(); |
69 | void nativeWindow(); |
70 | void stackWidgetOpaqueChildIsVisible(); |
71 | void offscreen(); |
72 | void offscreenThenOnscreen(); |
73 | |
74 | #ifdef QT_BUILD_INTERNAL |
75 | void staticTextDanglingPointer(); |
76 | #endif |
77 | }; |
78 | |
79 | void tst_QOpenGLWidget::initTestCase() |
80 | { |
81 | // See QOpenGLWidget constructor |
82 | if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(cap: QPlatformIntegration::RasterGLSurface)) |
83 | QSKIP("QOpenGLWidget is not supported on this platform." ); |
84 | } |
85 | |
86 | void tst_QOpenGLWidget::create() |
87 | { |
88 | QScopedPointer<QOpenGLWidget> w(new QOpenGLWidget); |
89 | QVERIFY(!w->isValid()); |
90 | QVERIFY(w->textureFormat() == 0); |
91 | QSignalSpy frameSwappedSpy(w.data(), SIGNAL(frameSwapped())); |
92 | w->show(); |
93 | QVERIFY(QTest::qWaitForWindowExposed(w.data())); |
94 | QVERIFY(frameSwappedSpy.count() > 0); |
95 | |
96 | QVERIFY(w->isValid()); |
97 | QVERIFY(w->context()); |
98 | QCOMPARE(w->context()->format(), w->format()); |
99 | QVERIFY(w->defaultFramebufferObject() != 0); |
100 | QVERIFY(w->textureFormat() != 0); |
101 | } |
102 | |
103 | class ClearWidget : public QOpenGLWidget, protected QOpenGLFunctions |
104 | { |
105 | public: |
106 | ClearWidget(QWidget *parent, int expectedWidth, int expectedHeight) |
107 | : QOpenGLWidget(parent), |
108 | m_initCalled(false), m_paintCalled(false), m_resizeCalled(false), |
109 | m_resizeOk(false), |
110 | m_w(expectedWidth), m_h(expectedHeight), |
111 | r(1.0f), g(0.0f), b(0.0f) { } |
112 | |
113 | void initializeGL() override { |
114 | m_initCalled = true; |
115 | initializeOpenGLFunctions(); |
116 | } |
117 | void paintGL() override { |
118 | m_paintCalled = true; |
119 | glClearColor(red: r, green: g, blue: b, alpha: 1.0f); |
120 | glClear(GL_COLOR_BUFFER_BIT); |
121 | } |
122 | void resizeGL(int w, int h) override { |
123 | m_resizeCalled = true; |
124 | m_resizeOk = w == m_w && h == m_h; |
125 | } |
126 | void setClearColor(float r, float g, float b) { |
127 | this->r = r; this->g = g; this->b = b; |
128 | } |
129 | |
130 | bool m_initCalled; |
131 | bool m_paintCalled; |
132 | bool m_resizeCalled; |
133 | bool m_resizeOk; |
134 | int m_w; |
135 | int m_h; |
136 | float r, g, b; |
137 | }; |
138 | |
139 | void tst_QOpenGLWidget::clearAndGrab() |
140 | { |
141 | QScopedPointer<ClearWidget> w(new ClearWidget(0, 800, 600)); |
142 | w->resize(w: 800, h: 600); |
143 | w->show(); |
144 | QVERIFY(QTest::qWaitForWindowExposed(w.data())); |
145 | QVERIFY(w->m_initCalled); |
146 | QVERIFY(w->m_resizeCalled); |
147 | QVERIFY(w->m_resizeOk); |
148 | QVERIFY(w->m_paintCalled); |
149 | |
150 | QImage image = w->grabFramebuffer(); |
151 | QVERIFY(!image.isNull()); |
152 | QCOMPARE(image.width(), w->width()); |
153 | QCOMPARE(image.height(), w->height()); |
154 | QVERIFY(image.pixel(30, 40) == qRgb(255, 0, 0)); |
155 | } |
156 | |
157 | void tst_QOpenGLWidget::clearAndResizeAndGrab() |
158 | { |
159 | QScopedPointer<QOpenGLWidget> w(new ClearWidget(0, 640, 480)); |
160 | w->resize(w: 640, h: 480); |
161 | w->show(); |
162 | QVERIFY(QTest::qWaitForWindowExposed(w.data())); |
163 | |
164 | QImage image = w->grabFramebuffer(); |
165 | QVERIFY(!image.isNull()); |
166 | QCOMPARE(image.width(), w->width()); |
167 | QCOMPARE(image.height(), w->height()); |
168 | w->resize(w: 800, h: 600); |
169 | image = w->grabFramebuffer(); |
170 | QVERIFY(!image.isNull()); |
171 | QCOMPARE(image.width(), 800); |
172 | QCOMPARE(image.height(), 600); |
173 | QCOMPARE(image.width(), w->width()); |
174 | QCOMPARE(image.height(), w->height()); |
175 | QVERIFY(image.pixel(30, 40) == qRgb(255, 0, 0)); |
176 | } |
177 | |
178 | void tst_QOpenGLWidget::createNonTopLevel() |
179 | { |
180 | QWidget w; |
181 | ClearWidget *glw = new ClearWidget(&w, 600, 700); |
182 | QSignalSpy frameSwappedSpy(glw, SIGNAL(frameSwapped())); |
183 | w.resize(w: 400, h: 400); |
184 | w.show(); |
185 | QVERIFY(QTest::qWaitForWindowExposed(&w)); |
186 | QVERIFY(frameSwappedSpy.count() > 0); |
187 | |
188 | QVERIFY(glw->m_resizeCalled); |
189 | glw->m_resizeCalled = false; |
190 | QVERIFY(!glw->m_resizeOk); |
191 | glw->resize(w: 600, h: 700); |
192 | |
193 | QVERIFY(glw->m_initCalled); |
194 | QVERIFY(glw->m_resizeCalled); |
195 | QVERIFY(glw->m_resizeOk); |
196 | QVERIFY(glw->m_paintCalled); |
197 | |
198 | QImage image = glw->grabFramebuffer(); |
199 | QVERIFY(!image.isNull()); |
200 | QCOMPARE(image.width(), glw->width()); |
201 | QCOMPARE(image.height(), glw->height()); |
202 | QVERIFY(image.pixel(30, 40) == qRgb(255, 0, 0)); |
203 | |
204 | glw->doneCurrent(); |
205 | QVERIFY(!QOpenGLContext::currentContext()); |
206 | glw->makeCurrent(); |
207 | QVERIFY(QOpenGLContext::currentContext() == glw->context() && glw->context()); |
208 | } |
209 | |
210 | class PainterWidget : public QOpenGLWidget, protected QOpenGLFunctions |
211 | { |
212 | public: |
213 | PainterWidget(QWidget *parent) |
214 | : QOpenGLWidget(parent), m_clear(false) { } |
215 | |
216 | void initializeGL() override { |
217 | initializeOpenGLFunctions(); |
218 | } |
219 | void paintGL() override { |
220 | QPainter p(this); |
221 | QCOMPARE(p.device()->width(), width()); |
222 | QCOMPARE(p.device()->height(), height()); |
223 | p.fillRect(r: QRect(QPoint(0, 0), QSize(width(), height())), c: Qt::blue); |
224 | if (m_clear) { |
225 | p.beginNativePainting(); |
226 | glClearColor(red: 0.0f, green: 1.0f, blue: 0.0f, alpha: 1.0f); |
227 | glClear(GL_COLOR_BUFFER_BIT); |
228 | p.endNativePainting(); |
229 | } |
230 | } |
231 | bool m_clear; |
232 | }; |
233 | |
234 | void tst_QOpenGLWidget::painter() |
235 | { |
236 | QWidget w; |
237 | PainterWidget *glw = new PainterWidget(&w); |
238 | w.resize(w: 640, h: 480); |
239 | glw->resize(w: 320, h: 200); |
240 | w.show(); |
241 | QVERIFY(QTest::qWaitForWindowExposed(&w)); |
242 | |
243 | QImage image = glw->grabFramebuffer(); |
244 | QCOMPARE(image.width(), glw->width()); |
245 | QCOMPARE(image.height(), glw->height()); |
246 | QVERIFY(image.pixel(20, 10) == qRgb(0, 0, 255)); |
247 | |
248 | glw->m_clear = true; |
249 | image = glw->grabFramebuffer(); |
250 | QVERIFY(image.pixel(20, 10) == qRgb(0, 255, 0)); |
251 | |
252 | QPixmap pix = glw->grab(); // QTBUG-61036 |
253 | } |
254 | |
255 | void tst_QOpenGLWidget::reparentToAlreadyCreated() |
256 | { |
257 | QWidget w1; |
258 | PainterWidget *glw = new PainterWidget(&w1); |
259 | w1.resize(w: 640, h: 480); |
260 | glw->resize(w: 320, h: 200); |
261 | w1.show(); |
262 | QVERIFY(QTest::qWaitForWindowExposed(&w1)); |
263 | |
264 | QWidget w2; |
265 | w2.show(); |
266 | QVERIFY(QTest::qWaitForWindowExposed(&w2)); |
267 | |
268 | glw->setParent(&w2); |
269 | glw->show(); |
270 | |
271 | QImage image = glw->grabFramebuffer(); |
272 | QCOMPARE(image.width(), 320); |
273 | QCOMPARE(image.height(), 200); |
274 | QVERIFY(image.pixel(20, 10) == qRgb(0, 0, 255)); |
275 | } |
276 | |
277 | void tst_QOpenGLWidget::reparentToNotYetCreated() |
278 | { |
279 | QWidget w1; |
280 | PainterWidget *glw = new PainterWidget(&w1); |
281 | w1.resize(w: 640, h: 480); |
282 | glw->resize(w: 320, h: 200); |
283 | w1.show(); |
284 | QVERIFY(QTest::qWaitForWindowExposed(&w1)); |
285 | |
286 | QWidget w2; |
287 | glw->setParent(&w2); |
288 | w2.show(); |
289 | QVERIFY(QTest::qWaitForWindowExposed(&w2)); |
290 | |
291 | QImage image = glw->grabFramebuffer(); |
292 | QCOMPARE(image.width(), 320); |
293 | QCOMPARE(image.height(), 200); |
294 | QVERIFY(image.pixel(20, 10) == qRgb(0, 0, 255)); |
295 | } |
296 | |
297 | void tst_QOpenGLWidget::reparentHidden() |
298 | { |
299 | // Tests QTBUG-60896 |
300 | QWidget topLevel1; |
301 | |
302 | QWidget *container = new QWidget(&topLevel1); |
303 | PainterWidget *glw = new PainterWidget(container); |
304 | topLevel1.resize(w: 640, h: 480); |
305 | glw->resize(w: 320, h: 200); |
306 | topLevel1.show(); |
307 | |
308 | glw->hide(); // Explicitly hidden |
309 | |
310 | QVERIFY(QTest::qWaitForWindowExposed(&topLevel1)); |
311 | |
312 | QWidget topLevel2; |
313 | topLevel2.resize(w: 640, h: 480); |
314 | topLevel2.show(); |
315 | QVERIFY(QTest::qWaitForWindowExposed(&topLevel2)); |
316 | |
317 | QOpenGLContext *originalContext = glw->context(); |
318 | QVERIFY(originalContext); |
319 | |
320 | container->setParent(&topLevel2); |
321 | glw->show(); // Should get a new context now |
322 | |
323 | QOpenGLContext *newContext = glw->context(); |
324 | QVERIFY(originalContext != newContext); |
325 | } |
326 | |
327 | class CountingGraphicsView : public QGraphicsView |
328 | { |
329 | public: |
330 | CountingGraphicsView(): m_count(0) { } |
331 | int paintCount() const { return m_count; } |
332 | void resetPaintCount() { m_count = 0; } |
333 | |
334 | protected: |
335 | void drawForeground(QPainter *, const QRectF &) override; |
336 | int m_count; |
337 | }; |
338 | |
339 | void CountingGraphicsView::drawForeground(QPainter *, const QRectF &) |
340 | { |
341 | ++m_count; |
342 | |
343 | // QTBUG-59318: verify that the context's internal default fbo redirection |
344 | // is active also when using the QOpenGLWidget as a viewport. |
345 | GLint currentFbo = -1; |
346 | QOpenGLContext::currentContext()->functions()->glGetIntegerv(GL_FRAMEBUFFER_BINDING, params: ¤tFbo); |
347 | GLuint defFbo = QOpenGLContext::currentContext()->defaultFramebufferObject(); |
348 | QCOMPARE(GLuint(currentFbo), defFbo); |
349 | } |
350 | |
351 | void tst_QOpenGLWidget::asViewport() |
352 | { |
353 | // Have a QGraphicsView with a QOpenGLWidget as its viewport. |
354 | QGraphicsScene scene; |
355 | scene.addItem(item: new QGraphicsRectItem(10, 10, 100, 100)); |
356 | CountingGraphicsView *view = new CountingGraphicsView; |
357 | view->setScene(&scene); |
358 | view->setViewport(new QOpenGLWidget); |
359 | QWidget widget; |
360 | QVBoxLayout *layout = new QVBoxLayout; |
361 | layout->addWidget(view); |
362 | QPushButton *btn = new QPushButton("Test" ); |
363 | layout->addWidget(btn); |
364 | widget.setLayout(layout); |
365 | widget.show(); |
366 | QVERIFY(QTest::qWaitForWindowExposed(&widget)); |
367 | |
368 | QVERIFY(view->paintCount() > 0); |
369 | view->resetPaintCount(); |
370 | |
371 | // And now trigger a repaint on the push button. We must not |
372 | // receive paint events for the graphics view. If we do, that's a |
373 | // side effect of QOpenGLWidget's special behavior and handling in |
374 | // the widget stack. |
375 | btn->update(); |
376 | qApp->processEvents(); |
377 | QCOMPARE(view->paintCount(), 0); |
378 | } |
379 | |
380 | class PaintCountWidget : public QOpenGLWidget |
381 | { |
382 | public: |
383 | PaintCountWidget() : m_count(0) { } |
384 | void reset() { m_count = 0; } |
385 | void paintGL() override { ++m_count; } |
386 | int m_count; |
387 | }; |
388 | |
389 | void tst_QOpenGLWidget::requestUpdate() |
390 | { |
391 | if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland" ), cs: Qt::CaseInsensitive)) |
392 | QSKIP("Wayland: This fails. Figure out why." ); |
393 | |
394 | PaintCountWidget w; |
395 | w.resize(w: 640, h: 480); |
396 | w.show(); |
397 | QVERIFY(QTest::qWaitForWindowExposed(&w)); |
398 | |
399 | w.reset(); |
400 | QCOMPARE(w.m_count, 0); |
401 | |
402 | w.windowHandle()->requestUpdate(); |
403 | QTRY_VERIFY(w.m_count > 0); |
404 | } |
405 | |
406 | class FboCheckWidget : public QOpenGLWidget |
407 | { |
408 | public: |
409 | void paintGL() override { |
410 | GLuint reportedDefaultFbo = QOpenGLContext::currentContext()->defaultFramebufferObject(); |
411 | GLuint expectedDefaultFbo = defaultFramebufferObject(); |
412 | QCOMPARE(reportedDefaultFbo, expectedDefaultFbo); |
413 | } |
414 | }; |
415 | |
416 | void tst_QOpenGLWidget::fboRedirect() |
417 | { |
418 | FboCheckWidget w; |
419 | w.resize(w: 640, h: 480); |
420 | w.show(); |
421 | QVERIFY(QTest::qWaitForWindowExposed(&w)); |
422 | |
423 | // Unlike in paintGL(), the default fbo reported by the context is not affected by the widget, |
424 | // so we get the real default fbo: either 0 or (on iOS) the fbo associated with the window. |
425 | w.makeCurrent(); |
426 | GLuint reportedDefaultFbo = QOpenGLContext::currentContext()->defaultFramebufferObject(); |
427 | GLuint widgetFbo = w.defaultFramebufferObject(); |
428 | QVERIFY(reportedDefaultFbo != widgetFbo); |
429 | } |
430 | |
431 | void tst_QOpenGLWidget::showHide() |
432 | { |
433 | QScopedPointer<ClearWidget> w(new ClearWidget(0, 800, 600)); |
434 | w->resize(w: 800, h: 600); |
435 | w->show(); |
436 | QVERIFY(QTest::qWaitForWindowExposed(w.data())); |
437 | |
438 | w->hide(); |
439 | |
440 | QImage image = w->grabFramebuffer(); |
441 | QVERIFY(!image.isNull()); |
442 | QCOMPARE(image.width(), w->width()); |
443 | QCOMPARE(image.height(), w->height()); |
444 | QVERIFY(image.pixel(30, 40) == qRgb(255, 0, 0)); |
445 | |
446 | w->setClearColor(r: 0, g: 0, b: 1); |
447 | w->show(); |
448 | QVERIFY(QTest::qWaitForWindowExposed(w.data())); |
449 | |
450 | image = w->grabFramebuffer(); |
451 | QVERIFY(!image.isNull()); |
452 | QCOMPARE(image.width(), w->width()); |
453 | QCOMPARE(image.height(), w->height()); |
454 | QVERIFY(image.pixel(30, 40) == qRgb(0, 0, 255)); |
455 | } |
456 | |
457 | void tst_QOpenGLWidget::nativeWindow() |
458 | { |
459 | QScopedPointer<ClearWidget> w(new ClearWidget(0, 800, 600)); |
460 | w->resize(w: 800, h: 600); |
461 | w->show(); |
462 | w->winId(); |
463 | QVERIFY(QTest::qWaitForWindowExposed(w.data())); |
464 | |
465 | QImage image = w->grabFramebuffer(); |
466 | QVERIFY(!image.isNull()); |
467 | QCOMPARE(image.width(), w->width()); |
468 | QCOMPARE(image.height(), w->height()); |
469 | QVERIFY(image.pixel(30, 40) == qRgb(255, 0, 0)); |
470 | QVERIFY(w->internalWinId()); |
471 | |
472 | // Now as a native child. |
473 | QWidget nativeParent; |
474 | nativeParent.resize(w: 800, h: 600); |
475 | nativeParent.setAttribute(Qt::WA_NativeWindow); |
476 | ClearWidget *child = new ClearWidget(0, 800, 600); |
477 | child->setClearColor(r: 0, g: 1, b: 0); |
478 | child->setParent(&nativeParent); |
479 | child->resize(w: 400, h: 400); |
480 | child->move(ax: 23, ay: 34); |
481 | nativeParent.show(); |
482 | QVERIFY(QTest::qWaitForWindowExposed(&nativeParent)); |
483 | |
484 | QVERIFY(nativeParent.internalWinId()); |
485 | QVERIFY(!child->internalWinId()); |
486 | |
487 | image = child->grabFramebuffer(); |
488 | QVERIFY(!image.isNull()); |
489 | QCOMPARE(image.width(), child->width()); |
490 | QCOMPARE(image.height(), child->height()); |
491 | QVERIFY(image.pixel(30, 40) == qRgb(0, 255, 0)); |
492 | } |
493 | |
494 | static inline QString msgRgbMismatch(unsigned actual, unsigned expected) |
495 | { |
496 | return QString::asprintf(format: "Color mismatch, %#010x != %#010x" , actual, expected); |
497 | } |
498 | |
499 | static QPixmap grabWidgetWithoutRepaint(const QWidget *widget, QRect clipArea) |
500 | { |
501 | const QWidget *targetWidget = widget; |
502 | #ifdef Q_OS_WIN |
503 | // OpenGL content is not properly grabbed on Windows when passing a top level widget window, |
504 | // because GDI functions can't grab OpenGL layer content. |
505 | // Instead the whole screen should be captured, with an adjusted clip area, which contains |
506 | // the final composited content. |
507 | QDesktopWidget *desktopWidget = QApplication::desktop(); |
508 | const QWidget *mainScreenWidget = desktopWidget->screen(); |
509 | targetWidget = mainScreenWidget; |
510 | clipArea = QRect(widget->mapToGlobal(clipArea.topLeft()), |
511 | widget->mapToGlobal(clipArea.bottomRight())); |
512 | #endif |
513 | |
514 | const QWindow *window = targetWidget->window()->windowHandle(); |
515 | Q_ASSERT(window); |
516 | WId windowId = window->winId(); |
517 | |
518 | QScreen *screen = window->screen(); |
519 | Q_ASSERT(screen); |
520 | |
521 | const QSize size = clipArea.size(); |
522 | const QPixmap result = screen->grabWindow(window: windowId, |
523 | x: clipArea.x(), |
524 | y: clipArea.y(), |
525 | w: size.width(), |
526 | h: size.height()); |
527 | return result; |
528 | } |
529 | |
530 | #define VERIFY_COLOR(child, region, color) verifyColor(child, region, color, __LINE__) |
531 | |
532 | bool verifyColor(const QWidget *widget, const QRect &clipArea, const QColor &color, int callerLine) |
533 | { |
534 | for (int t = 0; t < 6; t++) { |
535 | const QPixmap pixmap = grabWidgetWithoutRepaint(widget, clipArea); |
536 | if (!QTest::qCompare(t1: pixmap.size(), |
537 | t2: clipArea.size(), |
538 | actual: "pixmap.size()" , |
539 | expected: "rect.size()" , |
540 | __FILE__, |
541 | line: callerLine)) |
542 | return false; |
543 | |
544 | |
545 | const QImage image = pixmap.toImage(); |
546 | QPixmap expectedPixmap(pixmap); /* ensure equal formats */ |
547 | expectedPixmap.detach(); |
548 | expectedPixmap.fill(fillColor: color); |
549 | |
550 | uint alphaCorrection = image.format() == QImage::Format_RGB32 ? 0xff000000 : 0; |
551 | uint firstPixel = image.pixel(x: 0,y: 0) | alphaCorrection; |
552 | |
553 | // Retry a couple of times. Some window managers have transparency animation, or are |
554 | // just slow to render. |
555 | if (t < 5) { |
556 | if (firstPixel == QColor(color).rgb() |
557 | && image == expectedPixmap.toImage()) |
558 | return true; |
559 | else |
560 | QTest::qWait(ms: 200); |
561 | } else { |
562 | if (!QTest::qVerify(statement: firstPixel == QColor(color).rgb(), |
563 | statementStr: "firstPixel == QColor(color).rgb()" , |
564 | qPrintable(msgRgbMismatch(firstPixel, QColor(color).rgb())), |
565 | __FILE__, line: callerLine)) { |
566 | return false; |
567 | } |
568 | if (!QTest::qVerify(statement: image == expectedPixmap.toImage(), |
569 | statementStr: "image == expectedPixmap.toImage()" , |
570 | description: "grabbed pixmap differs from expected pixmap" , |
571 | __FILE__, line: callerLine)) { |
572 | return false; |
573 | } |
574 | } |
575 | } |
576 | |
577 | return false; |
578 | } |
579 | |
580 | void tst_QOpenGLWidget::stackWidgetOpaqueChildIsVisible() |
581 | { |
582 | #ifdef Q_OS_MACOS |
583 | QSKIP("QScreen::grabWindow() doesn't work properly on OSX HighDPI screen: QTBUG-46803" ); |
584 | return; |
585 | #endif |
586 | if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland" ), cs: Qt::CaseInsensitive)) |
587 | QSKIP("Wayland: This fails. Figure out why." ); |
588 | |
589 | |
590 | QStackedWidget stack; |
591 | |
592 | QWidget* emptyWidget = new QWidget(&stack); |
593 | stack.addWidget(w: emptyWidget); |
594 | |
595 | // Create an opaque red QOpenGLWidget. |
596 | const int dimensionSize = 400; |
597 | ClearWidget* clearWidget = new ClearWidget(&stack, dimensionSize, dimensionSize); |
598 | clearWidget->setAttribute(Qt::WA_OpaquePaintEvent); |
599 | stack.addWidget(w: clearWidget); |
600 | |
601 | // Show initial QWidget. |
602 | stack.setCurrentIndex(0); |
603 | stack.resize(w: dimensionSize, h: dimensionSize); |
604 | stack.show(); |
605 | QVERIFY(QTest::qWaitForWindowExposed(&stack)); |
606 | QVERIFY(QTest::qWaitForWindowActive(&stack)); |
607 | |
608 | // Switch to the QOpenGLWidget. |
609 | stack.setCurrentIndex(1); |
610 | QTRY_COMPARE(clearWidget->m_paintCalled, true); |
611 | |
612 | // Resize the tested region to be half size in the middle, because some OSes make the widget |
613 | // have rounded corners (e.g. OSX), and the grabbed window pixmap will not coincide perfectly |
614 | // with what was actually painted. |
615 | QRect clipArea = stack.rect(); |
616 | clipArea.setSize(clipArea.size() / 2); |
617 | const int translationOffsetToMiddle = dimensionSize / 4; |
618 | clipArea.translate(dx: translationOffsetToMiddle, dy: translationOffsetToMiddle); |
619 | |
620 | // Verify that the QOpenGLWidget was actually painted AND displayed. |
621 | const QColor red(255, 0, 0, 255); |
622 | VERIFY_COLOR(&stack, clipArea, red); |
623 | #undef VERIFY_COLOR |
624 | } |
625 | |
626 | void tst_QOpenGLWidget::offscreen() |
627 | { |
628 | { |
629 | QScopedPointer<ClearWidget> w(new ClearWidget(0, 800, 600)); |
630 | w->resize(w: 800, h: 600); |
631 | |
632 | w->setClearColor(r: 0, g: 0, b: 1); |
633 | QImage image = w->grabFramebuffer(); |
634 | |
635 | QVERIFY(!image.isNull()); |
636 | QCOMPARE(image.width(), w->width()); |
637 | QCOMPARE(image.height(), w->height()); |
638 | QVERIFY(image.pixel(30, 40) == qRgb(0, 0, 255)); |
639 | } |
640 | |
641 | // QWidget::grab() should eventually end up in grabFramebuffer() as well |
642 | { |
643 | QScopedPointer<ClearWidget> w(new ClearWidget(0, 800, 600)); |
644 | w->resize(w: 800, h: 600); |
645 | |
646 | w->setClearColor(r: 0, g: 0, b: 1); |
647 | QPixmap pm = w->grab(); |
648 | QImage image = pm.toImage(); |
649 | |
650 | QVERIFY(!image.isNull()); |
651 | QCOMPARE(image.width(), w->width()); |
652 | QCOMPARE(image.height(), w->height()); |
653 | QVERIFY(image.pixel(30, 40) == qRgb(0, 0, 255)); |
654 | } |
655 | |
656 | // ditto for QWidget::render() |
657 | { |
658 | QScopedPointer<ClearWidget> w(new ClearWidget(0, 800, 600)); |
659 | w->resize(w: 800, h: 600); |
660 | |
661 | w->setClearColor(r: 0, g: 0, b: 1); |
662 | QImage image(800, 600, QImage::Format_ARGB32); |
663 | w->render(target: &image); |
664 | |
665 | QVERIFY(image.pixel(30, 40) == qRgb(0, 0, 255)); |
666 | } |
667 | } |
668 | |
669 | void tst_QOpenGLWidget::offscreenThenOnscreen() |
670 | { |
671 | QScopedPointer<ClearWidget> w(new ClearWidget(0, 800, 600)); |
672 | w->resize(w: 800, h: 600); |
673 | |
674 | w->setClearColor(r: 0, g: 0, b: 1); |
675 | QImage image = w->grabFramebuffer(); |
676 | |
677 | QVERIFY(!image.isNull()); |
678 | QCOMPARE(image.width(), w->width()); |
679 | QCOMPARE(image.height(), w->height()); |
680 | QVERIFY(image.pixel(30, 40) == qRgb(0, 0, 255)); |
681 | |
682 | // now let's make things more challenging: show. Internally this needs |
683 | // recreating the context. |
684 | w->show(); |
685 | QVERIFY(QTest::qWaitForWindowExposed(w.data())); |
686 | |
687 | image = w->grabFramebuffer(); |
688 | QVERIFY(!image.isNull()); |
689 | QCOMPARE(image.width(), w->width()); |
690 | QCOMPARE(image.height(), w->height()); |
691 | QVERIFY(image.pixel(30, 40) == qRgb(0, 0, 255)); |
692 | } |
693 | |
694 | class StaticTextPainterWidget : public QOpenGLWidget |
695 | { |
696 | public: |
697 | StaticTextPainterWidget(QWidget *parent = nullptr) |
698 | : QOpenGLWidget(parent) |
699 | { |
700 | } |
701 | |
702 | void paintEvent(QPaintEvent *) |
703 | { |
704 | QPainter p(this); |
705 | text.setText(QStringLiteral("test" )); |
706 | p.drawStaticText(x: 0, y: 0, staticText: text); |
707 | |
708 | ctx = QOpenGLContext::currentContext(); |
709 | } |
710 | |
711 | QStaticText text; |
712 | QOpenGLContext *ctx; |
713 | }; |
714 | |
715 | #ifdef QT_BUILD_INTERNAL |
716 | void tst_QOpenGLWidget::staticTextDanglingPointer() |
717 | { |
718 | QWidget w; |
719 | StaticTextPainterWidget *glw = new StaticTextPainterWidget(&w); |
720 | w.resize(w: 640, h: 480); |
721 | glw->resize(w: 320, h: 200); |
722 | w.show(); |
723 | |
724 | QVERIFY(QTest::qWaitForWindowExposed(&w)); |
725 | QStaticTextPrivate *d = QStaticTextPrivate::get(q: &glw->text); |
726 | |
727 | QCOMPARE(d->itemCount, 1); |
728 | QFontEngine *fe = d->items->fontEngine(); |
729 | |
730 | for (int i = QFontEngine::Format_None; i <= QFontEngine::Format_ARGB; ++i) { |
731 | QOpenGLTextureGlyphCache *cache = |
732 | (QOpenGLTextureGlyphCache *) fe->glyphCache(key: glw->ctx, |
733 | format: QFontEngine::GlyphFormat(i), |
734 | transform: QTransform()); |
735 | if (cache != nullptr) |
736 | QCOMPARE(cache->paintEnginePrivate(), nullptr); |
737 | } |
738 | } |
739 | #endif |
740 | |
741 | QTEST_MAIN(tst_QOpenGLWidget) |
742 | |
743 | #include "tst_qopenglwidget.moc" |
744 | |