1/****************************************************************************
2**
3** Copyright (C) 2017 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the examples of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:BSD$
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** BSD License Usage
18** Alternatively, you may use this file under the terms of the BSD license
19** as follows:
20**
21** "Redistribution and use in source and binary forms, with or without
22** modification, are permitted provided that the following conditions are
23** met:
24** * Redistributions of source code must retain the above copyright
25** notice, this list of conditions and the following disclaimer.
26** * Redistributions in binary form must reproduce the above copyright
27** notice, this list of conditions and the following disclaimer in
28** the documentation and/or other materials provided with the
29** distribution.
30** * Neither the name of The Qt Company Ltd nor the names of its
31** contributors may be used to endorse or promote products derived
32** from this software without specific prior written permission.
33**
34**
35** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
36** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
38** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
39** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
42** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
43** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
44** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
45** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
46**
47** $QT_END_LICENSE$
48**
49****************************************************************************/
50
51#include "window_multithreaded.h"
52#include "cuberenderer.h"
53#include <QOpenGLContext>
54#include <QOpenGLFunctions>
55#include <QOpenGLFramebufferObject>
56#include <QOpenGLShaderProgram>
57#include <QOpenGLVertexArrayObject>
58#include <QOpenGLBuffer>
59#include <QOpenGLVertexArrayObject>
60#include <QOffscreenSurface>
61#include <QQmlEngine>
62#include <QQmlComponent>
63#include <QQuickItem>
64#include <QQuickWindow>
65#include <QQuickRenderControl>
66#include <QCoreApplication>
67
68/*
69 This implementation runs the Qt Quick scenegraph's sync and render phases on a
70 separate, dedicated thread. Rendering the cube using our custom OpenGL engine
71 happens on that thread as well. This is similar to the built-in threaded
72 render loop, but does not support all the features. There is no support for
73 getting Animators running on the render thread for example.
74
75 We choose to use QObject's event mechanism to communicate with the QObject
76 living on the render thread. An alternative would be to subclass QThread and
77 reimplement run() with a custom event handling approach, like
78 QSGThreadedRenderLoop does. That would potentially lead to better results but
79 is also more complex.
80*/
81
82static const QEvent::Type INIT = QEvent::Type(QEvent::User + 1);
83static const QEvent::Type RENDER = QEvent::Type(QEvent::User + 2);
84static const QEvent::Type RESIZE = QEvent::Type(QEvent::User + 3);
85static const QEvent::Type STOP = QEvent::Type(QEvent::User + 4);
86
87static const QEvent::Type UPDATE = QEvent::Type(QEvent::User + 5);
88
89QuickRenderer::QuickRenderer()
90 : m_context(nullptr),
91 m_surface(nullptr),
92 m_fbo(nullptr),
93 m_window(nullptr),
94 m_quickWindow(nullptr),
95 m_renderControl(nullptr),
96 m_cubeRenderer(nullptr),
97 m_quit(false)
98{
99}
100
101void QuickRenderer::requestInit()
102{
103 QCoreApplication::postEvent(receiver: this, event: new QEvent(INIT));
104}
105
106void QuickRenderer::requestRender()
107{
108 QCoreApplication::postEvent(receiver: this, event: new QEvent(RENDER));
109}
110
111void QuickRenderer::requestResize()
112{
113 QCoreApplication::postEvent(receiver: this, event: new QEvent(RESIZE));
114}
115
116void QuickRenderer::requestStop()
117{
118 QCoreApplication::postEvent(receiver: this, event: new QEvent(STOP));
119}
120
121bool QuickRenderer::event(QEvent *e)
122{
123 QMutexLocker lock(&m_mutex);
124
125 switch (int(e->type())) {
126 case INIT:
127 init();
128 return true;
129 case RENDER:
130 render(lock: &lock);
131 return true;
132 case RESIZE:
133 if (m_cubeRenderer)
134 m_cubeRenderer->resize(w: m_window->width(), h: m_window->height());
135 return true;
136 case STOP:
137 cleanup();
138 return true;
139 default:
140 return QObject::event(event: e);
141 }
142}
143
144void QuickRenderer::init()
145{
146 m_context->makeCurrent(surface: m_surface);
147
148 // Pass our offscreen surface to the cube renderer just so that it will
149 // have something is can make current during cleanup. QOffscreenSurface,
150 // just like QWindow, must always be created on the gui thread (as it might
151 // be backed by an actual QWindow).
152 m_cubeRenderer = new CubeRenderer(m_surface);
153 m_cubeRenderer->resize(w: m_window->width(), h: m_window->height());
154
155 m_renderControl->initialize(gl: m_context);
156}
157
158void QuickRenderer::cleanup()
159{
160 m_context->makeCurrent(surface: m_surface);
161
162 m_renderControl->invalidate();
163
164 delete m_fbo;
165 m_fbo = nullptr;
166
167 delete m_cubeRenderer;
168 m_cubeRenderer = nullptr;
169
170 m_context->doneCurrent();
171 m_context->moveToThread(thread: QCoreApplication::instance()->thread());
172
173 m_cond.wakeOne();
174}
175
176void QuickRenderer::ensureFbo()
177{
178 if (m_fbo && m_fbo->size() != m_window->size() * m_window->devicePixelRatio()) {
179 delete m_fbo;
180 m_fbo = nullptr;
181 }
182
183 if (!m_fbo) {
184 m_fbo = new QOpenGLFramebufferObject(m_window->size() * m_window->devicePixelRatio(),
185 QOpenGLFramebufferObject::CombinedDepthStencil);
186 m_quickWindow->setRenderTarget(m_fbo);
187 }
188}
189
190void QuickRenderer::render(QMutexLocker *lock)
191{
192 Q_ASSERT(QThread::currentThread() != m_window->thread());
193
194 if (!m_context->makeCurrent(surface: m_surface)) {
195 qWarning(msg: "Failed to make context current on render thread");
196 return;
197 }
198
199 ensureFbo();
200
201 // Synchronization and rendering happens here on the render thread.
202 m_renderControl->sync();
203
204 // The gui thread can now continue.
205 m_cond.wakeOne();
206 lock->unlock();
207
208 // Meanwhile on this thread continue with the actual rendering (into the FBO first).
209 m_renderControl->render();
210 m_context->functions()->glFlush();
211
212 // The cube renderer uses its own context, no need to bother with the state here.
213
214 // Get something onto the screen using our custom OpenGL engine.
215 QMutexLocker quitLock(&m_quitMutex);
216 if (!m_quit)
217 m_cubeRenderer->render(w: m_window, share: m_context, texture: m_fbo->texture());
218}
219
220void QuickRenderer::aboutToQuit()
221{
222 QMutexLocker lock(&m_quitMutex);
223 m_quit = true;
224}
225
226class RenderControl : public QQuickRenderControl
227{
228public:
229 RenderControl(QWindow *w) : m_window(w) { }
230 QWindow *renderWindow(QPoint *offset) override;
231
232private:
233 QWindow *m_window;
234};
235
236WindowMultiThreaded::WindowMultiThreaded()
237 : m_qmlComponent(nullptr),
238 m_rootItem(nullptr),
239 m_quickInitialized(false),
240 m_psrRequested(false)
241{
242 setSurfaceType(QSurface::OpenGLSurface);
243
244 QSurfaceFormat format;
245 // Qt Quick may need a depth and stencil buffer. Always make sure these are available.
246 format.setDepthBufferSize(16);
247 format.setStencilBufferSize(8);
248 setFormat(format);
249
250 m_context = new QOpenGLContext;
251 m_context->setFormat(format);
252 m_context->create();
253
254 m_offscreenSurface = new QOffscreenSurface;
255 // Pass m_context->format(), not format. Format does not specify and color buffer
256 // sizes, while the context, that has just been created, reports a format that has
257 // these values filled in. Pass this to the offscreen surface to make sure it will be
258 // compatible with the context's configuration.
259 m_offscreenSurface->setFormat(m_context->format());
260 m_offscreenSurface->create();
261
262 m_renderControl = new RenderControl(this);
263
264 // Create a QQuickWindow that is associated with out render control. Note that this
265 // window never gets created or shown, meaning that it will never get an underlying
266 // native (platform) window.
267 m_quickWindow = new QQuickWindow(m_renderControl);
268
269 // Create a QML engine.
270 m_qmlEngine = new QQmlEngine;
271 if (!m_qmlEngine->incubationController())
272 m_qmlEngine->setIncubationController(m_quickWindow->incubationController());
273
274 m_quickRenderer = new QuickRenderer;
275 m_quickRenderer->setContext(m_context);
276
277 // These live on the gui thread. Just give access to them on the render thread.
278 m_quickRenderer->setSurface(m_offscreenSurface);
279 m_quickRenderer->setWindow(this);
280 m_quickRenderer->setQuickWindow(m_quickWindow);
281 m_quickRenderer->setRenderControl(m_renderControl);
282
283 m_quickRendererThread = new QThread;
284
285 // Notify the render control that some scenegraph internals have to live on
286 // m_quickRenderThread.
287 m_renderControl->prepareThread(targetThread: m_quickRendererThread);
288
289 // The QOpenGLContext and the QObject representing the rendering logic on
290 // the render thread must live on that thread.
291 m_context->moveToThread(thread: m_quickRendererThread);
292 m_quickRenderer->moveToThread(thread: m_quickRendererThread);
293
294 m_quickRendererThread->start();
295
296 // Now hook up the signals. For simplicy we don't differentiate
297 // between renderRequested (only render is needed, no sync) and
298 // sceneChanged (polish and sync is needed too).
299 connect(sender: m_renderControl, signal: &QQuickRenderControl::renderRequested, receiver: this, slot: &WindowMultiThreaded::requestUpdate);
300 connect(sender: m_renderControl, signal: &QQuickRenderControl::sceneChanged, receiver: this, slot: &WindowMultiThreaded::requestUpdate);
301}
302
303WindowMultiThreaded::~WindowMultiThreaded()
304{
305 // Release resources and move the context ownership back to this thread.
306 m_quickRenderer->mutex()->lock();
307 m_quickRenderer->requestStop();
308 m_quickRenderer->cond()->wait(lockedMutex: m_quickRenderer->mutex());
309 m_quickRenderer->mutex()->unlock();
310
311 m_quickRendererThread->quit();
312 m_quickRendererThread->wait();
313
314 delete m_renderControl;
315 delete m_qmlComponent;
316 delete m_quickWindow;
317 delete m_qmlEngine;
318
319 delete m_offscreenSurface;
320 delete m_context;
321}
322
323void WindowMultiThreaded::requestUpdate()
324{
325 if (m_quickInitialized && !m_psrRequested) {
326 m_psrRequested = true;
327 QCoreApplication::postEvent(receiver: this, event: new QEvent(UPDATE));
328 }
329}
330
331bool WindowMultiThreaded::event(QEvent *e)
332{
333 if (e->type() == UPDATE) {
334 polishSyncAndRender();
335 m_psrRequested = false;
336 return true;
337 } else if (e->type() == QEvent::Close) {
338 // Avoid rendering on the render thread when the window is about to
339 // close. Once a QWindow is closed, the underlying platform window will
340 // go away, even though the QWindow instance itself is still
341 // valid. Operations like swapBuffers() are futile and only result in
342 // warnings afterwards. Prevent this.
343 m_quickRenderer->aboutToQuit();
344 }
345 return QWindow::event(e);
346}
347
348void WindowMultiThreaded::polishSyncAndRender()
349{
350 Q_ASSERT(QThread::currentThread() == thread());
351
352 // Polishing happens on the gui thread.
353 m_renderControl->polishItems();
354 // Sync happens on the render thread with the gui thread (this one) blocked.
355 QMutexLocker lock(m_quickRenderer->mutex());
356 m_quickRenderer->requestRender();
357 // Wait until sync is complete.
358 m_quickRenderer->cond()->wait(lockedMutex: m_quickRenderer->mutex());
359 // Rendering happens on the render thread without blocking the gui (main)
360 // thread. This is good because the blocking swap (waiting for vsync)
361 // happens on the render thread, not blocking other work.
362}
363
364void WindowMultiThreaded::run()
365{
366 disconnect(sender: m_qmlComponent, signal: &QQmlComponent::statusChanged, receiver: this, slot: &WindowMultiThreaded::run);
367
368 if (m_qmlComponent->isError()) {
369 const QList<QQmlError> errorList = m_qmlComponent->errors();
370 for (const QQmlError &error : errorList)
371 qWarning() << error.url() << error.line() << error;
372 return;
373 }
374
375 QObject *rootObject = m_qmlComponent->create();
376 if (m_qmlComponent->isError()) {
377 const QList<QQmlError> errorList = m_qmlComponent->errors();
378 for (const QQmlError &error : errorList)
379 qWarning() << error.url() << error.line() << error;
380 return;
381 }
382
383 m_rootItem = qobject_cast<QQuickItem *>(object: rootObject);
384 if (!m_rootItem) {
385 qWarning(msg: "run: Not a QQuickItem");
386 delete rootObject;
387 return;
388 }
389
390 // The root item is ready. Associate it with the window.
391 m_rootItem->setParentItem(m_quickWindow->contentItem());
392
393 // Update item and rendering related geometries.
394 updateSizes();
395
396 m_quickInitialized = true;
397
398 // Initialize the render thread and perform the first polish/sync/render.
399 m_quickRenderer->requestInit();
400 polishSyncAndRender();
401}
402
403void WindowMultiThreaded::updateSizes()
404{
405 // Behave like SizeRootObjectToView.
406 m_rootItem->setWidth(width());
407 m_rootItem->setHeight(height());
408
409 m_quickWindow->setGeometry(posx: 0, posy: 0, w: width(), h: height());
410}
411
412void WindowMultiThreaded::startQuick(const QString &filename)
413{
414 m_qmlComponent = new QQmlComponent(m_qmlEngine, QUrl(filename));
415 if (m_qmlComponent->isLoading())
416 connect(sender: m_qmlComponent, signal: &QQmlComponent::statusChanged, receiver: this, slot: &WindowMultiThreaded::run);
417 else
418 run();
419}
420
421void WindowMultiThreaded::exposeEvent(QExposeEvent *)
422{
423 if (isExposed()) {
424 if (!m_quickInitialized)
425 startQuick(QStringLiteral("qrc:/rendercontrol/demo.qml"));
426 }
427}
428
429void WindowMultiThreaded::resizeEvent(QResizeEvent *)
430{
431 // If this is a resize after the scene is up and running, recreate the fbo and the
432 // Quick item and scene.
433 if (m_rootItem) {
434 updateSizes();
435 m_quickRenderer->requestResize();
436 polishSyncAndRender();
437 }
438}
439
440void WindowMultiThreaded::mousePressEvent(QMouseEvent *e)
441{
442 // Use the constructor taking localPos and screenPos. That puts localPos into the
443 // event's localPos and windowPos, and screenPos into the event's screenPos. This way
444 // the windowPos in e is ignored and is replaced by localPos. This is necessary
445 // because QQuickWindow thinks of itself as a top-level window always.
446 QMouseEvent mappedEvent(e->type(), e->localPos(), e->screenPos(), e->button(), e->buttons(), e->modifiers());
447 QCoreApplication::sendEvent(receiver: m_quickWindow, event: &mappedEvent);
448}
449
450void WindowMultiThreaded::mouseReleaseEvent(QMouseEvent *e)
451{
452 QMouseEvent mappedEvent(e->type(), e->localPos(), e->screenPos(), e->button(), e->buttons(), e->modifiers());
453 QCoreApplication::sendEvent(receiver: m_quickWindow, event: &mappedEvent);
454}
455

source code of qtdeclarative/examples/quick/rendercontrol/window_multithreaded.cpp