1/****************************************************************************
2**
3** Copyright (C) 2016 Klaralvdalens Datakonsult AB (KDAB).
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the Qt3D module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
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 Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "scene3drenderer_p.h"
41
42#include <Qt3DCore/qaspectengine.h>
43#include <Qt3DRender/qrenderaspect.h>
44#include <QtCore/qthread.h>
45#include <qopenglcontext.h>
46#include <qopenglframebufferobject.h>
47#include <QtQuick/qquickwindow.h>
48
49#include <Qt3DRender/private/qrenderaspect_p.h>
50#include <Qt3DRender/private/abstractrenderer_p.h>
51#include <Qt3DCore/private/qaspectengine_p.h>
52#include <Qt3DCore/private/qaspectmanager_p.h>
53#include <Qt3DCore/private/qchangearbiter_p.h>
54#include <Qt3DCore/private/qservicelocator_p.h>
55
56#include <scene3ditem_p.h>
57#include <scene3dlogging_p.h>
58#include <scene3dsgnode_p.h>
59#include <scene3dview_p.h>
60
61QT_BEGIN_NAMESPACE
62
63namespace Qt3DRender {
64
65class ContextSaver
66{
67public:
68 explicit ContextSaver(QOpenGLContext *context = QOpenGLContext::currentContext())
69 : m_context(context),
70 m_surface(context ? context->surface() : nullptr)
71 {
72 }
73
74 ~ContextSaver()
75 {
76 if (m_context && m_context->surface() != m_surface)
77 m_context->makeCurrent(surface: m_surface);
78 }
79
80 QOpenGLContext *context() const { return m_context; }
81 QSurface *surface() const { return m_surface; }
82
83private:
84 QOpenGLContext * const m_context;
85 QSurface * const m_surface;
86};
87
88/*!
89 \class Qt3DRender::Scene3DRenderer
90 \internal
91
92 \brief The Scene3DRenderer class takes care of rendering a Qt3D scene
93 within a Framebuffer object to be used by the QtQuick 2 renderer.
94
95 The Scene3DRenderer class renders a Qt3D scene as provided by a Scene3DItem.
96 It owns the aspectEngine even though it doesn't instantiate it.
97
98 The render loop goes as follows:
99 \list
100 \li The main thread runs, drives Animations, etc. and causes changes to be
101 reported to the Qt3D change arbiter. The first change reported will cause
102 the scene3drenderer to be marked dirty.
103 \li The QtQuick render thread starts a new frame, synchronizes the scene
104 graph and emits afterSynchronizing. This will trigger some preparational
105 steps for rendering and mark the QSGNode dirty if the Scene3DRenderer is
106 dirty.
107 \li The QtQuick render loop emits beforeRendering. If we're marked dirty or
108 if the renderPolicy is set to Always, we'll ask the Qt3D renderer aspect to
109 render. That call is blocking. If the aspect jobs are not done, yet, the
110 renderer will exit early and we skip a frame.
111 \endlist
112
113 The shutdown procedure is a two steps process that goes as follow:
114
115 \list
116 \li The window is closed
117
118 \li This triggers the windowsChanged signal which the Scene3DRenderer
119 uses to perform the necessary cleanups in the QSGRenderThread (destroys
120 DebugLogger ...) with the shutdown slot (queued connection).
121
122 \li The destroyed signal of the window is also connected to the
123 Scene3DRenderer. When triggered in the context of the main thread, the
124 cleanup slot is called.
125 \endlist
126
127 There is an alternate shutdown procedure in case the QQuickItem is
128 destroyed with an active window which can happen in the case where the
129 Scene3D is used with a QtQuick Loader
130
131 In that case the shutdown procedure goes the same except that the destroyed
132 signal of the window is not called. Therefore the cleanup method is invoked
133 to properly destroy the aspect engine.
134 */
135Scene3DRenderer::Scene3DRenderer()
136 : QObject()
137 , m_aspectEngine(nullptr)
138 , m_renderAspect(nullptr)
139 , m_multisampledFBO(nullptr)
140 , m_finalFBO(nullptr)
141 , m_texture(nullptr)
142 , m_node(nullptr)
143 , m_window(nullptr)
144 , m_multisample(false) // this value is not used, will be synced from the Scene3DItem instead
145 , m_lastMultisample(false)
146 , m_needsShutdown(true)
147 , m_forceRecreate(false)
148 , m_shouldRender(false)
149 , m_dirtyViews(false)
150 , m_skipFrame(false)
151 , m_allowRendering(0)
152 , m_compositingMode(Scene3DItem::FBO)
153{
154
155}
156
157void Scene3DRenderer::init(Qt3DCore::QAspectEngine *aspectEngine,
158 QRenderAspect *renderAspect)
159{
160 m_aspectEngine = aspectEngine;
161 m_renderAspect = renderAspect;
162 m_needsShutdown = true;
163
164 Q_ASSERT(QOpenGLContext::currentContext());
165 ContextSaver saver;
166 static_cast<QRenderAspectPrivate*>(QRenderAspectPrivate::get(q: m_renderAspect))->renderInitialize(context: saver.context());
167}
168
169void Scene3DRenderer::setWindow(QQuickWindow *window)
170{
171 if (window == m_window)
172 return;
173
174 QObject::disconnect(receiver: m_window);
175 m_window = window;
176
177 if (m_window) {
178 QObject::connect(sender: m_window, signal: &QQuickWindow::beforeRendering, receiver: this, slot: &Scene3DRenderer::render, type: Qt::DirectConnection);
179 } else {
180 shutdown();
181 }
182}
183
184Scene3DRenderer::~Scene3DRenderer()
185{
186 qCDebug(Scene3D) << Q_FUNC_INFO << QThread::currentThread();
187 shutdown();
188}
189
190
191QOpenGLFramebufferObject *Scene3DRenderer::createMultisampledFramebufferObject(const QSize &size)
192{
193 QOpenGLFramebufferObjectFormat format;
194 format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
195 int samples = QSurfaceFormat::defaultFormat().samples();
196 if (samples == -1)
197 samples = 4;
198 format.setSamples(samples);
199 return new QOpenGLFramebufferObject(size, format);
200}
201
202QOpenGLFramebufferObject *Scene3DRenderer::createFramebufferObject(const QSize &size)
203{
204 QOpenGLFramebufferObjectFormat format;
205 format.setAttachment(QOpenGLFramebufferObject::Depth);
206 return new QOpenGLFramebufferObject(size, format);
207}
208
209// Executed in the QtQuick render thread (which may even be the gui/main with QQuickWidget / RenderControl).
210void Scene3DRenderer::shutdown()
211{
212 if (!m_needsShutdown)
213 return;
214 m_needsShutdown = false;
215 m_finalFBO.reset();
216 m_multisampledFBO.reset();
217}
218
219// Render Thread, GUI locked
220void Scene3DRenderer::beforeSynchronize()
221{
222 if (m_window) {
223 // Only render if we are sure aspectManager->processFrame was called prior
224 // We could otherwise enter a deadlock state
225 if (!m_allowRendering.tryAcquire(n: std::max(a: m_allowRendering.available(), b: 1)))
226 return;
227
228 // In the case of OnDemand rendering, we still need to get to this
229 // point to ensure we have processed jobs for all aspects.
230 // We also still need to call render() to allow proceeding with the
231 // next frame. However it won't be performing any 3d rendering at all
232 // so we do it here and return early. This prevents a costly QtQuick
233 // SceneGraph update for nothing
234 if (m_skipFrame) {
235 m_skipFrame = false;
236 ContextSaver saver;
237 static_cast<QRenderAspectPrivate*>(QRenderAspectPrivate::get(q: m_renderAspect))->renderSynchronous(swapBuffers: false);
238 return;
239 }
240
241 m_shouldRender = true;
242
243 // Check size / multisampling
244 const QSize currentSize = m_boundingRectSize * m_window->effectiveDevicePixelRatio();
245 const bool sizeHasChanged = currentSize != m_lastSize;
246 const bool multisampleHasChanged = m_multisample != m_lastMultisample;
247 const bool forceRecreate = sizeHasChanged || multisampleHasChanged;
248 // Store the current size as a comparison
249 // point for the next frame
250 m_lastSize = currentSize;
251 m_lastMultisample = m_multisample;
252
253 // Rebuild FBO if size/multisampling has changed
254 const bool usesFBO = m_compositingMode == Scene3DItem::FBO;
255 if (usesFBO) {
256 // Rebuild FBO and textures if never created or a resize has occurred
257 if ((m_multisampledFBO.isNull() || forceRecreate) && m_multisample) {
258 m_multisampledFBO.reset(other: createMultisampledFramebufferObject(size: m_lastSize));
259 if (m_multisampledFBO->format().samples() == 0 || !QOpenGLFramebufferObject::hasOpenGLFramebufferBlit()) {
260 m_multisample = false;
261 m_multisampledFBO.reset(other: nullptr);
262 }
263 }
264
265 const bool generateNewTexture = m_finalFBO.isNull() || forceRecreate;
266 if (generateNewTexture) {
267 m_finalFBO.reset(other: createFramebufferObject(size: m_lastSize));
268 const GLuint textureId = m_finalFBO->texture();
269 m_texture.reset(other: m_window->createTextureFromNativeObject(type: QQuickWindow::NativeObjectTexture,
270 nativeObjectPtr: &textureId, nativeLayout: 0,
271 size: m_finalFBO->size(),
272 options: QQuickWindow::TextureHasAlphaChannel));
273 }
274
275 // We can render either the Scene3D or the Scene3DView but not both
276 // at the same time
277 Q_ASSERT((m_node == nullptr || m_views.empty()) ||
278 (m_node != nullptr && m_views.empty()) ||
279 (m_node == nullptr && !m_views.empty()));
280
281 // Set texture on node
282 if (m_node && (!m_node->texture() || generateNewTexture))
283 m_node->setTexture(m_texture.data());
284
285 // Set textures on Scene3DView
286 if (m_dirtyViews || generateNewTexture) {
287 for (Scene3DView *view : qAsConst(t&: m_views))
288 if (!view->texture() || generateNewTexture)
289 view->setTexture(m_texture.data());
290 m_dirtyViews = false;
291 }
292 }
293
294 // Mark SGNodes as dirty so that QQuick will trigger some rendering
295 if (m_node)
296 m_node->markDirty(bits: QSGNode::DirtyMaterial);
297
298 for (Scene3DView *view : qAsConst(t&: m_views))
299 view->markSGNodeDirty();
300 }
301}
302
303void Scene3DRenderer::allowRender()
304{
305 m_allowRendering.release(n: 1);
306}
307
308void Scene3DRenderer::setCompositingMode(Scene3DItem::CompositingMode mode)
309{
310 m_compositingMode = mode;
311}
312
313void Scene3DRenderer::setSkipFrame(bool skip)
314{
315 m_skipFrame = skip;
316}
317
318void Scene3DRenderer::setMultisample(bool multisample)
319{
320 m_multisample = multisample;
321}
322
323void Scene3DRenderer::setBoundingSize(const QSize &size)
324{
325 m_boundingRectSize = size;
326}
327
328// Main Thread, Render Thread locked
329void Scene3DRenderer::setScene3DViews(const QVector<Scene3DView *> views)
330{
331 m_views = views;
332 m_dirtyViews = true;
333}
334
335void Scene3DRenderer::setSGNode(Scene3DSGNode *node)
336{
337 m_node = node;
338}
339
340// Render Thread, Main Thread is unlocked at this point
341void Scene3DRenderer::render()
342{
343 QMutexLocker l(&m_windowMutex);
344 // Lock to ensure the window doesn't change while we are rendering
345 if (!m_window || !m_shouldRender)
346 return;
347 m_shouldRender = false;
348
349 ContextSaver saver;
350
351 // The OpenGL state may be dirty from the previous QtQuick nodes, so reset
352 // it here to give Qt3D the clean state it expects
353 m_window->resetOpenGLState();
354
355 // Create and bind FBO if using the FBO compositing mode
356 const bool usesFBO = m_compositingMode == Scene3DItem::FBO;
357 if (usesFBO) {
358 // Bind FBO
359 if (m_multisample) //Only try to use MSAA when available
360 m_multisampledFBO->bind();
361 else
362 m_finalFBO->bind();
363 }
364
365 // Render Qt3D Scene
366 static_cast<QRenderAspectPrivate*>(QRenderAspectPrivate::get(q: m_renderAspect))->renderSynchronous(swapBuffers: usesFBO);
367
368 // We may have called doneCurrent() so restore the context if the rendering surface was changed
369 // Note: keep in mind that the ContextSave also restores the surface when destroyed
370 if (saver.context()->surface() != saver.surface())
371 saver.context()->makeCurrent(surface: saver.surface());
372
373 if (usesFBO) {
374 if (m_multisample) {
375 // Blit multisampled FBO with non multisampled FBO with texture attachment
376 const QRect dstRect(QPoint(0, 0), m_finalFBO->size());
377 const QRect srcRect(QPoint(0, 0), m_multisampledFBO->size());
378 QOpenGLFramebufferObject::blitFramebuffer(target: m_finalFBO.data(), targetRect: dstRect,
379 source: m_multisampledFBO.data(), sourceRect: srcRect,
380 GL_COLOR_BUFFER_BIT,
381 GL_NEAREST,
382 readColorAttachmentIndex: 0, drawColorAttachmentIndex: 0,
383 restorePolicy: QOpenGLFramebufferObject::DontRestoreFramebufferBinding);
384 }
385
386 // Restore QtQuick FBO
387 QOpenGLFramebufferObject::bindDefault();
388
389 // Only show the node once Qt3D has rendered to it
390 // Avoids showing garbage on the first frame
391 if (m_node)
392 m_node->show();
393 }
394
395 // Reset the state used by the Qt Quick scenegraph to avoid any
396 // interference when rendering the rest of the UI.
397 m_window->resetOpenGLState();
398}
399
400} // namespace Qt3DRender
401
402QT_END_NAMESPACE
403

source code of qt3d/src/quick3d/imports/scene3d/scene3drenderer.cpp