1/****************************************************************************
2**
3** Copyright (C) 2020 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 <qtest.h>
30
31#if QT_CONFIG(opengl)
32#include <QOffscreenSurface>
33#include <QOpenGLContext>
34#include <QOpenGLFunctions>
35#include <QOpenGLFramebufferObject>
36#endif
37#include <QAnimationDriver>
38
39#include <QQuickWindow>
40#include <QQuickRenderControl>
41#include <QQuickItem>
42#include <QQmlEngine>
43#include <QQmlComponent>
44
45#include "../../shared/util.h"
46
47#include <QtGui/private/qguiapplication_p.h>
48#include <QtGui/qpa/qplatformintegration.h>
49
50class tst_RenderControl : public QQmlDataTest
51{
52 Q_OBJECT
53
54private slots:
55 void initTestCase();
56 void renderAndReadBack();
57};
58
59void tst_RenderControl::initTestCase()
60{
61 QQmlDataTest::initTestCase();
62}
63
64class AnimationDriver : public QAnimationDriver
65{
66public:
67 AnimationDriver(int msPerStep) : m_step(msPerStep) { }
68
69 void advance() override
70 {
71 m_elapsed += m_step;
72 advanceAnimation();
73 }
74
75 qint64 elapsed() const override
76 {
77 return m_elapsed;
78 }
79
80private:
81 int m_step;
82 qint64 m_elapsed = 0;
83};
84
85
86void tst_RenderControl::renderAndReadBack()
87{
88 static const int ANIM_ADVANCE_PER_FRAME = 16; // milliseconds
89 QScopedPointer<AnimationDriver> animDriver(new AnimationDriver(ANIM_ADVANCE_PER_FRAME));
90 animDriver->install();
91
92 // ### Qt 6: migrate this to OpenGL-on-RHI
93#if QT_CONFIG(opengl)
94 if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(cap: QPlatformIntegration::OpenGL))
95 QSKIP("Skipping due to platform not supporting OpenGL at run time");
96
97 QScopedPointer<QOpenGLContext> context(new QOpenGLContext);
98 QVERIFY(context->create());
99 QScopedPointer<QOffscreenSurface> offscreenSurface(new QOffscreenSurface);
100 offscreenSurface->setFormat(context->format());
101 offscreenSurface->create();
102 QVERIFY(context->makeCurrent(offscreenSurface.data()));
103
104 QScopedPointer<QQuickRenderControl> renderControl(new QQuickRenderControl);
105 QScopedPointer<QQuickWindow> quickWindow(new QQuickWindow(renderControl.data()));
106 QScopedPointer<QQmlEngine> qmlEngine(new QQmlEngine);
107
108 QScopedPointer<QQmlComponent> qmlComponent(new QQmlComponent(qmlEngine.data(), testFileUrl(fileName: QLatin1String("rect.qml"))));
109 QVERIFY(!qmlComponent->isLoading());
110 if (qmlComponent->isError()) {
111 for (const QQmlError &error : qmlComponent->errors())
112 qWarning() << error.url() << error.line() << error;
113 }
114 QVERIFY(!qmlComponent->isError());
115
116 QObject *rootObject = qmlComponent->create();
117 if (qmlComponent->isError()) {
118 for (const QQmlError &error : qmlComponent->errors())
119 qWarning() << error.url() << error.line() << error;
120 }
121 QVERIFY(!qmlComponent->isError());
122
123 QQuickItem *rootItem = qobject_cast<QQuickItem *>(object: rootObject);
124 QVERIFY(rootItem);
125 QCOMPARE(rootItem->size(), QSize(200, 200));
126
127 quickWindow->contentItem()->setSize(rootItem->size());
128 quickWindow->setGeometry(posx: 0, posy: 0, w: rootItem->width(), h: rootItem->height());
129
130 rootItem->setParentItem(quickWindow->contentItem());
131
132 renderControl->initialize(gl: context.data());
133
134 // cannot do this test with the 'software' backend of Qt Quick
135 QSGRendererInterface::GraphicsApi api = quickWindow->rendererInterface()->graphicsApi();
136 if (api != QSGRendererInterface::OpenGL)
137 QSKIP("Skipping due to Qt Quick not using the direct OpenGL rendering path");
138
139 QScopedPointer<QOpenGLFramebufferObject> fbo(new QOpenGLFramebufferObject(rootItem->size().toSize(),
140 QOpenGLFramebufferObject::CombinedDepthStencil));
141
142 quickWindow->setRenderTarget(fbo.data());
143
144 for (int frame = 0; frame < 100; ++frame) {
145 // have to process events, e.g. to get queued metacalls delivered
146 QCoreApplication::processEvents();
147
148 if (frame > 0) {
149 // Quick animations will now think that ANIM_ADVANCE_PER_FRAME milliseconds have passed,
150 // even though in reality we have a tight loop that generates frames unthrottled.
151 animDriver->advance();
152 }
153
154 renderControl->polishItems();
155 renderControl->sync();
156 renderControl->render();
157
158 context->functions()->glFlush();
159
160 QImage img = fbo->toImage();
161 QVERIFY(!img.isNull());
162 QCOMPARE(img.size(), rootItem->size());
163
164 const int maxFuzz = 2;
165
166 // The scene is: background, rectangle, text
167 // where rectangle rotates
168
169 QRgb background = img.pixel(x: 5, y: 5);
170 QVERIFY(qAbs(qRed(background) - 70) < maxFuzz);
171 QVERIFY(qAbs(qGreen(background) - 130) < maxFuzz);
172 QVERIFY(qAbs(qBlue(background) - 180) < maxFuzz);
173
174 background = img.pixel(x: 195, y: 195);
175 QVERIFY(qAbs(qRed(background) - 70) < maxFuzz);
176 QVERIFY(qAbs(qGreen(background) - 130) < maxFuzz);
177 QVERIFY(qAbs(qBlue(background) - 180) < maxFuzz);
178
179 // after about 1.25 seconds (animation time, one iteration is 16 ms
180 // thanks to our custom animation driver) the rectangle reaches a 90
181 // degree rotation, that should be frame 76
182 if (frame <= 2 || (frame >= 76 && frame <= 80)) {
183 QRgb c = img.pixel(x: 28, y: 28); // rectangle
184 QVERIFY(qAbs(qRed(c) - 152) < maxFuzz);
185 QVERIFY(qAbs(qGreen(c) - 251) < maxFuzz);
186 QVERIFY(qAbs(qBlue(c) - 152) < maxFuzz);
187 } else {
188 QRgb c = img.pixel(x: 28, y: 28); // background because rectangle got rotated so this pixel is not covered by it
189 QVERIFY(qAbs(qRed(c) - 70) < maxFuzz);
190 QVERIFY(qAbs(qGreen(c) - 130) < maxFuzz);
191 QVERIFY(qAbs(qBlue(c) - 180) < maxFuzz);
192 }
193 }
194
195#else
196 QSKIP("No OpenGL, skipping rendercontrol test");
197#endif
198}
199
200#include "tst_qquickrendercontrol.moc"
201
202QTEST_MAIN(tst_RenderControl)
203

source code of qtdeclarative/tests/auto/quick/qquickrendercontrol/tst_qquickrendercontrol.cpp