1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2015 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 "scene3ditem_p.h" |
41 | |
42 | #include <Qt3DCore/qt3dcore_global.h> |
43 | #include <Qt3DCore/qentity.h> |
44 | #include <Qt3DCore/QAspectEngine> |
45 | |
46 | #if QT_CONFIG(qt3d_input) |
47 | #include <Qt3DInput/QInputAspect> |
48 | #include <Qt3DInput/qinputsettings.h> |
49 | #endif |
50 | |
51 | #if QT_CONFIG(qt3d_logic) |
52 | #include <Qt3DLogic/qlogicaspect.h> |
53 | #endif |
54 | |
55 | #if QT_CONFIG(qt3d_animation) |
56 | #include <Qt3DAnimation/qanimationaspect.h> |
57 | #endif |
58 | |
59 | #include <Qt3DRender/QRenderAspect> |
60 | #include <Qt3DRender/qcamera.h> |
61 | #include <Qt3DRender/qrendersurfaceselector.h> |
62 | |
63 | #include <QtGui/qguiapplication.h> |
64 | #include <QtGui/qoffscreensurface.h> |
65 | #include <QtQuick/qquickwindow.h> |
66 | #include <QtQuick/qquickrendercontrol.h> |
67 | |
68 | #include <Qt3DRender/private/qrendersurfaceselector_p.h> |
69 | #include <Qt3DRender/private/qrenderaspect_p.h> |
70 | #include <Qt3DRender/private/rendersettings_p.h> |
71 | #include <scene3dlogging_p.h> |
72 | #include <scene3drenderer_p.h> |
73 | #include <scene3dsgnode_p.h> |
74 | #include <scene3dview_p.h> |
75 | |
76 | #include <Qt3DCore/private/qaspectengine_p.h> |
77 | #include <Qt3DCore/private/qaspectmanager_p.h> |
78 | #include <QThread> |
79 | |
80 | QT_BEGIN_NAMESPACE |
81 | |
82 | namespace Qt3DRender { |
83 | |
84 | class AspectEngineDestroyer : public QObject |
85 | { |
86 | Q_OBJECT |
87 | |
88 | public: |
89 | AspectEngineDestroyer() |
90 | : QObject() |
91 | {} |
92 | |
93 | ~AspectEngineDestroyer() |
94 | { |
95 | } |
96 | |
97 | void reset(int targetCount) |
98 | { |
99 | m_allowed = 0; |
100 | m_targetAllowed = targetCount; |
101 | } |
102 | |
103 | Qt3DCore::QAspectEngine *aspectEngine() const |
104 | { |
105 | if (children().empty()) |
106 | return nullptr; |
107 | return qobject_cast<Qt3DCore::QAspectEngine *>(object: children().first()); |
108 | } |
109 | |
110 | bool releaseRootEntity() const { return m_releaseRootEntity; } |
111 | void setReleaseRootEntity(bool release) { m_releaseRootEntity = release; } |
112 | |
113 | void allowRelease() |
114 | { |
115 | ++m_allowed; |
116 | const bool shouldSelfDestruct = m_allowed == m_targetAllowed; |
117 | if (QThread::currentThread() == thread()) { |
118 | // Force Backend Tree to be cleaned up |
119 | Qt3DCore::QAspectEngine *engine = aspectEngine(); |
120 | // If used in the regular Scene3D, take this opportunity to release the root |
121 | // Entity which will release the backend entities of Qt3D |
122 | if (m_releaseRootEntity && engine && engine->rootEntity()) |
123 | engine->setRootEntity(Qt3DCore::QEntityPtr()); |
124 | if (shouldSelfDestruct) |
125 | delete this; |
126 | } else { |
127 | if (shouldSelfDestruct) |
128 | deleteLater(); |
129 | } |
130 | } |
131 | |
132 | void setSGNodeAlive(bool alive) { m_sgNodeAlive = alive; } |
133 | bool sgNodeAlive() const { return m_sgNodeAlive;} |
134 | |
135 | private: |
136 | int m_allowed = 0; |
137 | int m_targetAllowed = 0; |
138 | bool m_sgNodeAlive = false; |
139 | bool m_releaseRootEntity = true; |
140 | }; |
141 | |
142 | /*! |
143 | \class Qt3DRender::Scene3DItem |
144 | \internal |
145 | |
146 | \brief The Scene3DItem class is a QQuickItem subclass used to integrate |
147 | a Qt3D scene into a QtQuick 2 scene. |
148 | |
149 | The Scene3DItem class renders a Qt3D scene, provided by a Qt3DCore::QEntity |
150 | into a multisampled Framebuffer object that is later blitted into a |
151 | non-multisampled Framebuffer object to be then rendered through the use of a |
152 | Qt3DCore::Scene3DSGNode with premultiplied alpha. |
153 | */ |
154 | |
155 | /*! |
156 | \qmltype Scene3D |
157 | \inherits Item |
158 | \inqmlmodule QtQuick.Scene3D |
159 | |
160 | \preliminary |
161 | |
162 | \brief The Scene3D type is used to integrate a Qt3D scene into a QtQuick 2 |
163 | scene. |
164 | |
165 | The Scene3D type renders a Qt3D scene, provided by an \l Entity, into a |
166 | multisampled Framebuffer object. This object is later blitted into a |
167 | non-multisampled Framebuffer object, which is then rendered with |
168 | premultiplied alpha. If multisampling is not required, it can be avoided |
169 | by setting the \l multisample property to \c false. In this case |
170 | Scene3D will render directly into the non-multisampled Framebuffer object. |
171 | |
172 | If the scene to be rendered includes non-opaque materials, you may need to |
173 | modify those materials with custom blend arguments in order for them to be |
174 | rendered correctly. For example, if working with a \l PhongAlphaMaterial and |
175 | a scene with an opaque clear color, you will likely want to add: |
176 | |
177 | \qml |
178 | sourceAlphaArg: BlendEquationArguments.Zero |
179 | destinationAlphaArg: BlendEquationArguments.One |
180 | \endqml |
181 | |
182 | to that material. |
183 | |
184 | It is not recommended to instantiate more than a single Scene3D instance |
185 | per application. The reason for this is that a Scene3D instance |
186 | instantiates the entire Qt 3D engine (memory managers, thread pool, render |
187 | ...) under the scene. You should instead look into using \l Scene3DView |
188 | instances in conjunction with a single Scene3D instance. |
189 | |
190 | When using Scene3D with Scene3DViews the following conditions are expected: |
191 | \list |
192 | \li The compositingMode is set to FBO |
193 | \li The Scene3D is sized to occupy the full window size |
194 | \li The Scene3D instance is instantiated prior to any Scene3DView |
195 | \li The Scene3D entity property is left unset |
196 | \endlist |
197 | |
198 | \note Åšetting the visibility of the Scene3D element to false will halt the |
199 | Qt 3D simulation loop. This means that binding the visible property to an |
200 | expression that depends on property updates driven by the Qt 3D simulation |
201 | loop (FrameAction) will never reavaluates. |
202 | */ |
203 | Scene3DItem::Scene3DItem(QQuickItem *parent) |
204 | : QQuickItem(parent) |
205 | , m_entity(nullptr) |
206 | , m_viewHolderEntity(nullptr) |
207 | , m_viewHolderFG(nullptr) |
208 | , m_aspectEngine(nullptr) |
209 | , m_aspectToDelete(nullptr) |
210 | , m_lastManagerNode(nullptr) |
211 | , m_aspectEngineDestroyer() |
212 | , m_multisample(true) |
213 | , m_dirty(true) |
214 | , m_dirtyViews(false) |
215 | , m_clearsWindowByDefault(true) |
216 | , m_disableClearWindow(false) |
217 | , m_wasFrameProcessed(false) |
218 | , m_wasSGUpdated(false) |
219 | , m_cameraAspectRatioMode(AutomaticAspectRatio) |
220 | , m_compositingMode(FBO) |
221 | , m_dummySurface(nullptr) |
222 | , m_framesToRender(ms_framesNeededToFlushPipeline) |
223 | { |
224 | setFlag(flag: QQuickItem::ItemHasContents, enabled: true); |
225 | setAcceptedMouseButtons(Qt::MouseButtonMask); |
226 | setAcceptHoverEvents(true); |
227 | // TO DO: register the event source in the main thread |
228 | |
229 | // Give a default size so that if nothing is specified by the user |
230 | // we still won't get ignored by the QtQuick SG when in Underlay mode |
231 | setWidth(1); |
232 | setHeight(1); |
233 | } |
234 | |
235 | Scene3DItem::~Scene3DItem() |
236 | { |
237 | // The SceneGraph is non deterministic in the order in which it will |
238 | // destroy the QSGNode that were created by the item. This unfortunately |
239 | // makes it difficult to know when it is safe to destroy the QAspectEngine. |
240 | // To track this we use the AspectEngineDestroyer. It allows keeping the |
241 | // AspectEngine alive and deleting later when we know that both Scene3DItem |
242 | // and Scene3DRenderer have been destroyed. |
243 | |
244 | delete m_aspectToDelete; |
245 | |
246 | if (m_aspectEngineDestroyer) |
247 | m_aspectEngineDestroyer->allowRelease(); |
248 | |
249 | if (m_dummySurface) |
250 | m_dummySurface->deleteLater(); |
251 | } |
252 | |
253 | /*! |
254 | \qmlproperty list<string> Scene3D::aspects |
255 | |
256 | The list of aspects that should be registered for the 3D scene. |
257 | |
258 | For example, if the scene makes use of FrameAction, the \c "logic" aspect should be included in the list. |
259 | |
260 | The \c "render" aspect is hardwired and does not need to be explicitly listed. |
261 | */ |
262 | QStringList Scene3DItem::aspects() const |
263 | { |
264 | return m_aspects; |
265 | } |
266 | |
267 | /*! |
268 | \qmlproperty Entity Scene3D::entity |
269 | |
270 | \default |
271 | |
272 | The root entity of the 3D scene to be displayed. |
273 | */ |
274 | Qt3DCore::QEntity *Scene3DItem::entity() const |
275 | { |
276 | return m_entity; |
277 | } |
278 | |
279 | void Scene3DItem::applyAspects() |
280 | { |
281 | if (!m_aspectEngine) |
282 | return; |
283 | |
284 | // Aspects are owned by the aspect engine |
285 | for (const QString &aspect : qAsConst(t&: m_aspects)) { |
286 | if (aspect == QLatin1String("render" )) // This one is hardwired anyway |
287 | continue; |
288 | if (aspect == QLatin1String("input" )) { |
289 | #if QT_CONFIG(qt3d_input) |
290 | m_aspectEngine->registerAspect(aspect: new Qt3DInput::QInputAspect); |
291 | continue; |
292 | #else |
293 | qFatal("Scene3D requested the Qt 3D input aspect but Qt 3D wasn't configured to build the Qt 3D Input aspect" ); |
294 | #endif |
295 | } |
296 | if (aspect == QLatin1String("logic" )) { |
297 | #if QT_CONFIG(qt3d_logic) |
298 | m_aspectEngine->registerAspect(aspect: new Qt3DLogic::QLogicAspect); |
299 | continue; |
300 | #else |
301 | qFatal("Scene3D requested the Qt 3D logic aspect but Qt 3D wasn't configured to build the Qt 3D Logic aspect" ); |
302 | #endif |
303 | } |
304 | if (aspect == QLatin1String("animation" )) { |
305 | #if QT_CONFIG(qt3d_animation) |
306 | m_aspectEngine->registerAspect(aspect: new Qt3DAnimation::QAnimationAspect); |
307 | continue; |
308 | #else |
309 | qFatal("Scene3D requested the Qt 3D animation aspect but Qt 3D wasn't configured to build the Qt 3D Animation aspect" ); |
310 | #endif |
311 | } |
312 | m_aspectEngine->registerAspect(name: aspect); |
313 | } |
314 | } |
315 | |
316 | void Scene3DItem::setAspects(const QStringList &aspects) |
317 | { |
318 | if (!m_aspects.isEmpty()) { |
319 | qWarning() << "Aspects already set on the Scene3D, ignoring" ; |
320 | return; |
321 | } |
322 | |
323 | m_aspects = aspects; |
324 | applyAspects(); |
325 | |
326 | emit aspectsChanged(); |
327 | } |
328 | |
329 | void Scene3DItem::setEntity(Qt3DCore::QEntity *entity) |
330 | { |
331 | if (entity == m_entity) |
332 | return; |
333 | |
334 | m_entity = entity; |
335 | emit entityChanged(); |
336 | } |
337 | |
338 | void Scene3DItem::setCameraAspectRatioMode(CameraAspectRatioMode mode) |
339 | { |
340 | if (m_cameraAspectRatioMode == mode) |
341 | return; |
342 | |
343 | m_cameraAspectRatioMode = mode; |
344 | setCameraAspectModeHelper(); |
345 | emit cameraAspectRatioModeChanged(mode); |
346 | } |
347 | |
348 | void Scene3DItem::setHoverEnabled(bool enabled) |
349 | { |
350 | if (enabled != acceptHoverEvents()) { |
351 | setAcceptHoverEvents(enabled); |
352 | emit hoverEnabledChanged(); |
353 | } |
354 | } |
355 | |
356 | /*! |
357 | \qmlproperty enumeration Scene3D::compositingMode |
358 | |
359 | \value FBO |
360 | Scene is rendered into a Frame Buffer Object which can be costly on |
361 | some platform and hardware but allows a greater amount of |
362 | flexibility. Automatic aspect ratio. This is the compositing mode to |
363 | choose if your Scene3D element shouldn't occupy the entire screen |
364 | and if you optionally plan on having it resized or animated. In this |
365 | mode, the position of the Scene3D in the QML file controls its |
366 | stacking order with regard to the other Qt Quick elements. |
367 | |
368 | \value Underlay |
369 | Suitable for full screen 3D scenes where using an FBO might be too |
370 | resource intensive. Scene3D behaves as a QtQuick underlay. |
371 | Please note that when using this mode, the size of the Scene3D and |
372 | its transformations are ignored and the rendering will occupy the |
373 | whole screen. The position of the Scene3D in the QML file won't have |
374 | any effect either. The Qt 3D content will be drawn prior to any Qt |
375 | Quick content. Care has to be taken not to overdraw and hide the Qt |
376 | 3D content by overlapping Qt Quick content. |
377 | Additionally when using this mode, the window clearBeforeRendering |
378 | will be set to false automatically. |
379 | |
380 | The default value is \c FBO. |
381 | \since 5.14 |
382 | */ |
383 | void Scene3DItem::setCompositingMode(Scene3DItem::CompositingMode mode) |
384 | { |
385 | if (m_compositingMode == mode) |
386 | return; |
387 | m_compositingMode = mode; |
388 | emit compositingModeChanged(); |
389 | |
390 | QQuickItem::update(); |
391 | } |
392 | |
393 | /*! |
394 | \qmlproperty enumeration Scene3D::cameraAspectRatioMode |
395 | |
396 | \value Scene3D.AutomaticAspectRatio |
397 | Automatic aspect ratio. |
398 | |
399 | \value Scene3D.UserAspectRatio |
400 | User defined aspect ratio. |
401 | \brief \TODO |
402 | */ |
403 | Scene3DItem::CameraAspectRatioMode Scene3DItem::cameraAspectRatioMode() const |
404 | { |
405 | return m_cameraAspectRatioMode; |
406 | } |
407 | |
408 | Scene3DItem::CompositingMode Scene3DItem::compositingMode() const |
409 | { |
410 | return m_compositingMode; |
411 | } |
412 | |
413 | // MainThread called by Scene3DView |
414 | void Scene3DItem::addView(Scene3DView *view) |
415 | { |
416 | if (m_views.contains(t: view)) |
417 | return; |
418 | |
419 | Qt3DRender::QFrameGraphNode *viewFG = view->viewFrameGraph(); |
420 | Qt3DCore::QEntity *subtreeRoot = view->viewSubtree(); |
421 | |
422 | if (m_viewHolderEntity == nullptr) { |
423 | m_viewHolderEntity = new Qt3DCore::QEntity; |
424 | |
425 | if (m_entity != nullptr) { |
426 | qCWarning(Scene3D) << "Scene3DView is not supported if the Scene3D entity property has been set" ; |
427 | } |
428 | |
429 | Qt3DRender::QRenderSettings *settings = new Qt3DRender::QRenderSettings(); |
430 | Qt3DRender::QRenderSurfaceSelector *surfaceSelector = new Qt3DRender::QRenderSurfaceSelector(); |
431 | m_viewHolderFG = surfaceSelector; |
432 | surfaceSelector->setSurface(window()); |
433 | |
434 | // Copy setting properties from first View |
435 | QVector<Qt3DRender::QRenderSettings *> viewRenderSettings = subtreeRoot->componentsOfType<Qt3DRender::QRenderSettings>(); |
436 | if (viewRenderSettings.size() > 0) { |
437 | Qt3DRender::QRenderSettings *viewRenderSetting = viewRenderSettings.first(); |
438 | settings->setRenderPolicy(viewRenderSetting->renderPolicy()); |
439 | settings->pickingSettings()->setPickMethod(viewRenderSetting->pickingSettings()->pickMethod()); |
440 | settings->pickingSettings()->setPickResultMode(viewRenderSetting->pickingSettings()->pickResultMode()); |
441 | } |
442 | settings->setActiveFrameGraph(m_viewHolderFG); |
443 | m_viewHolderEntity->addComponent(comp: settings); |
444 | |
445 | setEntity(m_viewHolderEntity); |
446 | } |
447 | |
448 | // Parent FG and Subtree |
449 | viewFG->setParent(m_viewHolderFG); |
450 | subtreeRoot->setParent(m_viewHolderEntity); |
451 | |
452 | m_views.push_back(t: view); |
453 | m_dirtyViews |= true; |
454 | } |
455 | |
456 | // MainThread called by Scene3DView |
457 | void Scene3DItem::removeView(Scene3DView *view) |
458 | { |
459 | if (!m_views.contains(t: view)) |
460 | return; |
461 | |
462 | Qt3DRender::QFrameGraphNode *viewFG = view->viewFrameGraph(); |
463 | Qt3DCore::QEntity *subtreeRoot = view->viewSubtree(); |
464 | |
465 | // Unparent FG and Subtree |
466 | viewFG->setParent(Q_NODE_NULLPTR); |
467 | subtreeRoot->setParent(Q_NODE_NULLPTR); |
468 | |
469 | m_views.removeOne(t: view); |
470 | m_dirtyViews |= true; |
471 | } |
472 | |
473 | void Scene3DItem::applyRootEntityChange() |
474 | { |
475 | if (m_aspectEngine->rootEntity().data() != m_entity) { |
476 | |
477 | Qt3DCore::QEntityPtr entityPtr; |
478 | // We must reuse the QEntityPtr of the old AspectEngine |
479 | // otherwise it will delete the Entity once it gets destroyed |
480 | if (m_aspectToDelete) |
481 | entityPtr = m_aspectToDelete->rootEntity(); |
482 | else |
483 | entityPtr = Qt3DCore::QEntityPtr(m_entity); |
484 | |
485 | m_aspectEngine->setRootEntity(entityPtr); |
486 | |
487 | /* If we changed window, the old aspect engine must be deleted only after we have set |
488 | the root entity for the new one so that it doesn't delete the root node. */ |
489 | if (m_aspectToDelete) { |
490 | delete m_aspectToDelete; |
491 | m_aspectToDelete = nullptr; |
492 | } |
493 | |
494 | // Set the render surface |
495 | if (!m_entity) |
496 | return; |
497 | |
498 | setWindowSurface(entity()); |
499 | |
500 | if (m_cameraAspectRatioMode == AutomaticAspectRatio) { |
501 | // Set aspect ratio of first camera to match the window |
502 | QList<Qt3DRender::QCamera *> cameras |
503 | = m_entity->findChildren<Qt3DRender::QCamera *>(); |
504 | if (cameras.isEmpty()) { |
505 | qCDebug(Scene3D) << "No camera found and automatic aspect ratio requested" ; |
506 | } else { |
507 | m_camera = cameras.first(); |
508 | setCameraAspectModeHelper(); |
509 | } |
510 | } |
511 | |
512 | #if QT_CONFIG(qt3d_input) |
513 | // Set ourselves up as a source of input events for the input aspect |
514 | Qt3DInput::QInputSettings *inputSettings = m_entity->findChild<Qt3DInput::QInputSettings *>(); |
515 | if (inputSettings) { |
516 | inputSettings->setEventSource(this); |
517 | } else { |
518 | qCDebug(Scene3D) << "No Input Settings found, keyboard and mouse events won't be handled" ; |
519 | } |
520 | #endif |
521 | } |
522 | } |
523 | |
524 | bool Scene3DItem::needsRender(QRenderAspect *renderAspect) |
525 | { |
526 | // We need the dirty flag which is connected to the change arbiter |
527 | // receiving updates to know whether something in the scene has changed |
528 | |
529 | // Ideally we would use shouldRender() alone but given that it becomes true |
530 | // only after the arbiter has sync the changes and might be reset before |
531 | // process jobs is completed, we cannot fully rely on it. It would require |
532 | // splitting processFrame in 2 parts. |
533 | |
534 | // We only use it for cases where Qt3D render may require several loops of |
535 | // the simulation to fully process a frame (e.g shaders are loaded in frame |
536 | // n and we can only build render commands for the new shader at frame n + |
537 | // This is where renderer->shouldRender() comes into play as it knows |
538 | // whether some states remain dirty or not (even after processFrame is |
539 | // called) |
540 | |
541 | auto renderAspectPriv = static_cast<QRenderAspectPrivate*>(QRenderAspectPrivate::get(q: renderAspect)); |
542 | const bool dirty = m_dirty |
543 | || (renderAspectPriv |
544 | && renderAspectPriv->m_renderer |
545 | && renderAspectPriv->m_renderer->shouldRender()); |
546 | |
547 | if (m_dirty) { |
548 | --m_framesToRender; |
549 | if (m_framesToRender <= 0) |
550 | m_dirty = false; |
551 | } |
552 | return dirty || m_framesToRender > 0; |
553 | } |
554 | |
555 | // This function is triggered in the context of the Main Thread |
556 | // when afterAnimating is emitted |
557 | |
558 | // The QtQuick SG proceeds like indicated below: |
559 | // afterAnimating (Main Thread) |
560 | // beforeSynchronizing (SG Thread and MainThread locked) |
561 | // afterSynchronizing (SG Thread and MainThread locked) |
562 | // beforeRendering (SG Thread) |
563 | |
564 | // Note: we connect to afterAnimating rather than beforeSynchronizing as a |
565 | // direct connection on beforeSynchronizing is executed within the SG Render |
566 | // Thread context. This is executed before the RenderThread is asked to |
567 | // synchronize and render |
568 | // Note: we might still not be done rendering when this is called but |
569 | // processFrame will block and wait for renderer to have been finished |
570 | bool Scene3DItem::prepareQt3DFrame() |
571 | { |
572 | static bool dontRenderWhenHidden = !qgetenv(varName: "QT3D_SCENE3D_STOP_RENDER_HIDDEN" ).isEmpty(); |
573 | |
574 | // If we are not visible, don't processFrame changes as we would end up |
575 | // waiting forever for the scene to be rendered which won't happen |
576 | // if the Scene3D item is not visible |
577 | if (!isVisible() && dontRenderWhenHidden) |
578 | return false; |
579 | if (!m_aspectEngine) |
580 | return false; |
581 | Q_ASSERT(QThread::currentThread() == thread()); |
582 | |
583 | // Since we are in manual mode, trigger jobs for the next frame |
584 | Qt3DCore::QAspectEnginePrivate *aspectEnginePriv = static_cast<Qt3DCore::QAspectEnginePrivate *>(QObjectPrivate::get(o: m_aspectEngine)); |
585 | if (!aspectEnginePriv->m_initialized) |
586 | return false; |
587 | |
588 | Q_ASSERT(m_aspectEngine->runMode() == Qt3DCore::QAspectEngine::Manual); |
589 | m_aspectEngine->processFrame(); |
590 | // The above essentially sets the number of RV for the RenderQueue and |
591 | // processes the jobs for the frame (it's blocking) When |
592 | // Scene3DRender::updatePaintNode is called, following this step, we know |
593 | // that the RenderQueue target count has been set and that everything |
594 | // should be ready for rendering |
595 | |
596 | // processFrame() must absolutely be followed by a single call to |
597 | // render |
598 | // At startup, we have no garantee that the QtQuick Render Thread doesn't |
599 | // start rendering before this function has been called |
600 | // We add in a safety to skip such frames as this could otherwise |
601 | // make Qt3D enter a locked state |
602 | |
603 | // Note: it's too early to request an update at this point as |
604 | // beforeSync() triggered by afterAnimating is considered |
605 | // to be as being part of the current frame update |
606 | return true; |
607 | } |
608 | |
609 | void Scene3DItem::requestUpdate() |
610 | { |
611 | // When using the FBO mode, only the QQuickItem needs to be updated |
612 | // When using the Underlay mode, the whole windows needs updating |
613 | const bool usesFBO = m_compositingMode == FBO; |
614 | if (usesFBO) { |
615 | QQuickItem::update(); |
616 | for (Scene3DView *view : m_views) |
617 | view->update(); |
618 | } else { |
619 | window()->update(); |
620 | } |
621 | } |
622 | |
623 | void Scene3DItem::updateWindowSurface() |
624 | { |
625 | if (!m_entity || !m_dummySurface) |
626 | return; |
627 | Qt3DRender::QRenderSurfaceSelector *surfaceSelector = |
628 | Qt3DRender::QRenderSurfaceSelectorPrivate::find(rootObject: entity()); |
629 | if (surfaceSelector) { |
630 | if (QWindow *rw = QQuickRenderControl::renderWindowFor(win: this->window())) { |
631 | m_dummySurface->deleteLater(); |
632 | createDummySurface(window: rw, surfaceSelector); |
633 | } |
634 | } |
635 | } |
636 | |
637 | void Scene3DItem::setWindowSurface(QObject *rootObject) |
638 | { |
639 | Qt3DRender::QRenderSurfaceSelector *surfaceSelector = Qt3DRender::QRenderSurfaceSelectorPrivate::find(rootObject); |
640 | |
641 | // Set the item's window surface if it appears |
642 | // the surface wasn't set on the surfaceSelector |
643 | if (surfaceSelector && !surfaceSelector->surface()) { |
644 | // We may not have a real, exposed QQuickWindow when the Quick rendering |
645 | // is redirected via QQuickRenderControl (f.ex. QQuickWidget). |
646 | if (QWindow *rw = QQuickRenderControl::renderWindowFor(win: this->window())) { |
647 | createDummySurface(window: rw, surfaceSelector); |
648 | } else { |
649 | surfaceSelector->setSurface(this->window()); |
650 | } |
651 | } |
652 | } |
653 | |
654 | void Scene3DItem::createDummySurface(QWindow *rw, Qt3DRender::QRenderSurfaceSelector *surfaceSelector) |
655 | { |
656 | // rw is the top-level window that is backed by a native window. Do |
657 | // not use that though since we must not clash with e.g. the widget |
658 | // backingstore compositor in the gui thread. |
659 | m_dummySurface = new QOffscreenSurface; |
660 | m_dummySurface->setParent(qGuiApp); // parent to something suitably long-living |
661 | m_dummySurface->setFormat(rw->format()); |
662 | m_dummySurface->setScreen(rw->screen()); |
663 | m_dummySurface->create(); |
664 | surfaceSelector->setSurface(m_dummySurface); |
665 | } |
666 | /*! |
667 | \qmlmethod void Scene3D::setItemAreaAndDevicePixelRatio(size area, real devicePixelRatio) |
668 | |
669 | Sets the item area to \a area and the pixel ratio to \a devicePixelRatio. |
670 | */ |
671 | void Scene3DItem::setItemAreaAndDevicePixelRatio(QSize area, qreal devicePixelRatio) |
672 | { |
673 | Qt3DRender::QRenderSurfaceSelector *surfaceSelector |
674 | = Qt3DRender::QRenderSurfaceSelectorPrivate::find(rootObject: entity()); |
675 | if (surfaceSelector) { |
676 | surfaceSelector->setExternalRenderTargetSize(area); |
677 | surfaceSelector->setSurfacePixelRatio(devicePixelRatio); |
678 | } |
679 | } |
680 | |
681 | /*! |
682 | \qmlproperty bool Scene3D::hoverEnabled |
683 | |
684 | \c true if hover events are accepted. |
685 | */ |
686 | bool Scene3DItem::isHoverEnabled() const |
687 | { |
688 | return acceptHoverEvents(); |
689 | } |
690 | |
691 | void Scene3DItem::setCameraAspectModeHelper() |
692 | { |
693 | if (m_compositingMode == FBO) { |
694 | switch (m_cameraAspectRatioMode) { |
695 | case AutomaticAspectRatio: |
696 | connect(sender: this, signal: &Scene3DItem::widthChanged, receiver: this, slot: &Scene3DItem::updateCameraAspectRatio); |
697 | connect(sender: this, signal: &Scene3DItem::heightChanged, receiver: this, slot: &Scene3DItem::updateCameraAspectRatio); |
698 | // Update the aspect ratio the first time the surface is set |
699 | updateCameraAspectRatio(); |
700 | break; |
701 | case UserAspectRatio: |
702 | disconnect(sender: this, signal: &Scene3DItem::widthChanged, receiver: this, slot: &Scene3DItem::updateCameraAspectRatio); |
703 | disconnect(sender: this, signal: &Scene3DItem::heightChanged, receiver: this, slot: &Scene3DItem::updateCameraAspectRatio); |
704 | break; |
705 | } |
706 | } else { |
707 | // In Underlay mode, we rely on the window for aspect ratio and not the size of the Scene3DItem |
708 | switch (m_cameraAspectRatioMode) { |
709 | case AutomaticAspectRatio: |
710 | connect(sender: window(), signal: &QWindow::widthChanged, receiver: this, slot: &Scene3DItem::updateCameraAspectRatio); |
711 | connect(sender: window(), signal: &QWindow::heightChanged, receiver: this, slot: &Scene3DItem::updateCameraAspectRatio); |
712 | // Update the aspect ratio the first time the surface is set |
713 | updateCameraAspectRatio(); |
714 | break; |
715 | case UserAspectRatio: |
716 | disconnect(sender: window(), signal: &QWindow::widthChanged, receiver: this, slot: &Scene3DItem::updateCameraAspectRatio); |
717 | disconnect(sender: window(), signal: &QWindow::heightChanged, receiver: this, slot: &Scene3DItem::updateCameraAspectRatio); |
718 | break; |
719 | } |
720 | } |
721 | } |
722 | |
723 | void Scene3DItem::updateCameraAspectRatio() |
724 | { |
725 | if (m_camera) { |
726 | if (m_compositingMode == FBO) |
727 | m_camera->setAspectRatio(static_cast<float>(width()) / |
728 | static_cast<float>(height())); |
729 | else |
730 | m_camera->setAspectRatio(static_cast<float>(window()->width()) / |
731 | static_cast<float>(window()->height())); |
732 | } |
733 | } |
734 | |
735 | /*! |
736 | \qmlproperty bool Scene3D::multisample |
737 | |
738 | \c true if a multisample render buffer is requested. |
739 | |
740 | By default multisampling is enabled. If the OpenGL implementation has no |
741 | support for multisample renderbuffers or framebuffer blits, the request to |
742 | use multisampling is ignored. |
743 | |
744 | \note Refrain from changing the value frequently as it involves expensive |
745 | and potentially slow initialization of framebuffers and other OpenGL |
746 | resources. |
747 | */ |
748 | bool Scene3DItem::multisample() const |
749 | { |
750 | return m_multisample; |
751 | } |
752 | |
753 | void Scene3DItem::setMultisample(bool enable) |
754 | { |
755 | if (m_multisample != enable) { |
756 | m_multisample = enable; |
757 | emit multisampleChanged(); |
758 | update(); |
759 | } |
760 | } |
761 | |
762 | // We want to tie the Scene3DRenderer's lifetime to the QSGNode associated with |
763 | // Scene3DItem. This ensures that when the SceneGraph tree gets destroyed, we |
764 | // also shutdown Qt3D properly |
765 | // Everything this class does happens in the QSGRenderThread |
766 | class Scene3DManagerNode : public QSGNode |
767 | { |
768 | public: |
769 | explicit Scene3DManagerNode(Qt3DCore::QAspectEngine *aspectEngine, |
770 | AspectEngineDestroyer *destroyer) |
771 | : m_aspectEngine(aspectEngine) |
772 | , m_destroyer(destroyer) |
773 | , m_renderAspect(new QRenderAspect(QRenderAspect::Synchronous)) |
774 | , m_renderer(new Scene3DRenderer()) |
775 | { |
776 | m_destroyer->setSGNodeAlive(true); |
777 | } |
778 | |
779 | ~Scene3DManagerNode() |
780 | { |
781 | // Stop the Qt3D Simulation Loop |
782 | auto engineD = Qt3DCore::QAspectEnginePrivate::get(engine: m_aspectEngine); |
783 | engineD->exitSimulationLoop(); |
784 | |
785 | // Shutdown GL renderer |
786 | static_cast<QRenderAspectPrivate*>(QRenderAspectPrivate::get(q: m_renderAspect))->renderShutdown(); |
787 | delete m_renderer; |
788 | |
789 | m_destroyer->setSGNodeAlive(false); |
790 | |
791 | // Allow AspectEngine destruction |
792 | m_destroyer->allowRelease(); |
793 | } |
794 | |
795 | void init() |
796 | { |
797 | m_aspectEngine->registerAspect(aspect: m_renderAspect); |
798 | m_renderer->init(aspectEngine: m_aspectEngine, renderAspect: m_renderAspect); |
799 | m_wasInitialized = true; |
800 | } |
801 | |
802 | inline bool isInitialized() const { return m_wasInitialized; } |
803 | inline QRenderAspect *renderAspect() const { return m_renderAspect; } |
804 | inline Scene3DRenderer *renderer() const { return m_renderer; } |
805 | private: |
806 | Qt3DCore::QAspectEngine *m_aspectEngine; |
807 | AspectEngineDestroyer *m_destroyer; |
808 | QRenderAspect *m_renderAspect; |
809 | Scene3DRenderer *m_renderer; |
810 | bool m_wasInitialized = false; |
811 | }; |
812 | |
813 | |
814 | // QtQuick SG |
815 | // beforeSynchronize // SG Thread (main thread blocked) |
816 | // updatePaintNode (-> Scene3DRenderer::beforeSynchronize) // SG Thread (main thread blocked) |
817 | // beforeRenderering (-> Scene3DRenderer::beforeSynchronize) // SG Thread (main thread unblocked) |
818 | // afterRenderering // SG Thread (main thread unblocked) |
819 | // afterAnimating (-> Scene3DItem::synchronize()) // Main Thread (SG Thread is not yet at beforeSynchronize ) |
820 | |
821 | // main thread (afterAnimating) |
822 | void Scene3DItem::synchronize() |
823 | { |
824 | // Request updates for the next frame |
825 | requestUpdate(); |
826 | |
827 | if (!window() || !m_wasSGUpdated || |
828 | (!m_aspectEngineDestroyer || !m_aspectEngineDestroyer->sgNodeAlive())) { |
829 | m_wasFrameProcessed = false; |
830 | return; |
831 | } |
832 | |
833 | // Set root Entity on the aspectEngine |
834 | applyRootEntityChange(); |
835 | |
836 | // Update size of the QSurfaceSelector if needed |
837 | setItemAreaAndDevicePixelRatio(area: boundingRect().size().toSize(), |
838 | devicePixelRatio: window()->effectiveDevicePixelRatio()); |
839 | |
840 | // Let Qt3D process the frame and launch jobs |
841 | m_wasFrameProcessed = prepareQt3DFrame(); |
842 | |
843 | m_wasSGUpdated = false; |
844 | } |
845 | |
846 | // The synchronization point between the main thread and the render thread |
847 | // before any rendering |
848 | QSGNode *Scene3DItem::updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNodeData *) |
849 | { |
850 | Scene3DManagerNode *managerNode = static_cast<Scene3DManagerNode *>(node); |
851 | |
852 | // In case we have no GL context, return early |
853 | // m_wasSGUpdated will not be set to true and nothing will take place |
854 | if (!QOpenGLContext::currentContext()) { |
855 | QQuickItem::update(); |
856 | return node; |
857 | } |
858 | |
859 | // Scene3DManagerNode gets automatically destroyed on Window changed, SceneGraph invalidation |
860 | if (!managerNode) { |
861 | // Did we have a Scene3DManagerNode in the past? |
862 | if (m_lastManagerNode != nullptr) { |
863 | // If so we need to recreate a new AspectEngine as node was destroyed by sceneGraph |
864 | qCWarning(Scene3D) << "Renderer for Scene3DItem has requested a reset due to the item " |
865 | "moving to another window" ; |
866 | QObject::disconnect(m_windowConnection); |
867 | // Note: AspectEngine can only be deleted once we have set the root |
868 | // entity on the new instance |
869 | m_aspectEngine->setParent(nullptr); |
870 | m_aspectToDelete = m_aspectEngine; |
871 | m_aspectEngine = nullptr; |
872 | } |
873 | |
874 | // Create or Recreate AspectEngine |
875 | if (m_aspectEngine == nullptr) { |
876 | // Use manual drive mode when using Scene3D |
877 | delete m_aspectEngineDestroyer; |
878 | m_aspectEngineDestroyer = new AspectEngineDestroyer(); |
879 | m_aspectEngine = new Qt3DCore::QAspectEngine(m_aspectEngineDestroyer); |
880 | m_aspectEngine->setRunMode(Qt3DCore::QAspectEngine::Manual); |
881 | applyAspects(); |
882 | |
883 | // Needs to belong in the same thread as the item which is the same as |
884 | // the original QAspectEngine |
885 | m_aspectEngineDestroyer->moveToThread(thread: thread()); |
886 | |
887 | // To destroy AspectEngine |
888 | m_aspectEngineDestroyer->reset(targetCount: 2); |
889 | } |
890 | |
891 | // Create new instance and record a pointer (which should only be used |
892 | // to check if we have had a previous manager node) |
893 | managerNode = new Scene3DManagerNode(m_aspectEngine, |
894 | m_aspectEngineDestroyer); |
895 | m_lastManagerNode = managerNode; |
896 | |
897 | // Before Synchronizing is in the SG Thread, we want synchronize to be triggered |
898 | // in the context of the main thread so we use afterAnimating instead |
899 | m_windowConnection = QObject::connect(sender: window(), signal: &QQuickWindow::afterAnimating, |
900 | receiver: this, slot: &Scene3DItem::synchronize, type: Qt::DirectConnection); |
901 | } |
902 | |
903 | Scene3DRenderer *renderer = managerNode->renderer(); |
904 | QRenderAspect *renderAspect = managerNode->renderAspect(); |
905 | |
906 | // If the render aspect wasn't created yet, do so now |
907 | if (!managerNode->isInitialized()) { |
908 | auto *rw = QQuickRenderControl::renderWindowFor(win: window()); |
909 | |
910 | // When using a RenderControl, the AspectEngineDestroyer shouldn't release the root entity |
911 | // as it could be reused if render control was moved to another window |
912 | if (rw) |
913 | m_aspectEngineDestroyer->setReleaseRootEntity(false); |
914 | |
915 | auto renderAspectPriv = static_cast<QRenderAspectPrivate*>(QRenderAspectPrivate::get(q: renderAspect)); |
916 | renderAspectPriv->m_screen = (rw ? rw->screen() : window()->screen()); |
917 | updateWindowSurface(); |
918 | managerNode->init(); |
919 | // Note: ChangeArbiter is only set after aspect was registered |
920 | QObject::connect( |
921 | sender: renderAspectPriv->m_aspectManager->changeArbiter(), |
922 | signal: &Qt3DCore::QChangeArbiter::receivedChange, context: this, |
923 | slot: [this] { |
924 | m_dirty = true; |
925 | m_framesToRender = ms_framesNeededToFlushPipeline; |
926 | }, |
927 | type: Qt::DirectConnection); |
928 | // Give the window a nudge to trigger an update. |
929 | QMetaObject::invokeMethod(obj: window(), member: "requestUpdate" , type: Qt::QueuedConnection); |
930 | } |
931 | |
932 | const bool usesFBO = m_compositingMode == FBO; |
933 | const bool hasScene3DViews = !m_views.empty(); |
934 | Scene3DSGNode *fboNode = static_cast<Scene3DSGNode *>(managerNode->firstChild()); |
935 | |
936 | // When usin Scene3DViews or Scene3D in Underlay mode |
937 | // we shouldn't be managing a Scene3DSGNode |
938 | if (!usesFBO || hasScene3DViews) { |
939 | if (fboNode != nullptr) { |
940 | managerNode->removeChildNode(node: fboNode); |
941 | delete fboNode; |
942 | fboNode = nullptr; |
943 | renderer->setSGNode(fboNode); |
944 | } |
945 | } else { |
946 | // Regular Scene3D only case |
947 | // Create SGNode if using FBO and no Scene3DViews |
948 | if (fboNode == nullptr) { |
949 | fboNode = new Scene3DSGNode(); |
950 | renderer->setSGNode(fboNode); |
951 | managerNode->appendChildNode(node: fboNode); |
952 | } |
953 | fboNode->setRect(rect: boundingRect()); |
954 | } |
955 | |
956 | if (usesFBO) { |
957 | // Reset clear flag if we've set it to false it's still set to that |
958 | if (m_disableClearWindow && !window()->clearBeforeRendering()) |
959 | window()->setClearBeforeRendering(m_clearsWindowByDefault); |
960 | m_disableClearWindow = false; |
961 | } else { |
962 | // Record clearBeforeRendering value before we force it to false |
963 | m_clearsWindowByDefault = window()->clearBeforeRendering(); |
964 | m_disableClearWindow = true; |
965 | if (m_clearsWindowByDefault) |
966 | window()->setClearBeforeRendering(false); |
967 | } |
968 | |
969 | renderer->setBoundingSize(boundingRect().size().toSize()); |
970 | renderer->setMultisample(m_multisample); |
971 | // Ensure Renderer is working on current window |
972 | renderer->setWindow(window()); |
973 | // Set compositing mode on renderer |
974 | renderer->setCompositingMode(m_compositingMode); |
975 | // Set whether we want the Renderer to be allowed to render or not |
976 | const bool skipFrame = !needsRender(renderAspect); |
977 | renderer->setSkipFrame(skipFrame); |
978 | renderer->allowRender(); |
979 | |
980 | // Make renderer aware of any Scene3DView we are dealing with |
981 | if (m_dirtyViews) { |
982 | const bool usesFBO = m_compositingMode == FBO; |
983 | // Scene3DViews checks |
984 | if (entity() != m_viewHolderEntity) { |
985 | qCWarning(Scene3D) << "Scene3DView is not supported if the Scene3D entity property has been set" ; |
986 | } |
987 | if (!usesFBO) { |
988 | qCWarning(Scene3D) << "Scene3DView is only supported when Scene3D compositingMode is set to FBO" ; |
989 | } |
990 | // The Scene3DRender will take care of providing the texture containing the 3D scene |
991 | renderer->setScene3DViews(m_views); |
992 | m_dirtyViews = false; |
993 | } |
994 | |
995 | // Let the renderer prepare anything it needs to prior to the rendering |
996 | if (m_wasFrameProcessed) |
997 | renderer->beforeSynchronize(); |
998 | |
999 | // Force window->beforeRendering to be triggered |
1000 | managerNode->markDirty(bits: QSGNode::DirtyForceUpdate); |
1001 | |
1002 | m_wasSGUpdated = true; |
1003 | |
1004 | return managerNode; |
1005 | } |
1006 | |
1007 | void Scene3DItem::mousePressEvent(QMouseEvent *event) |
1008 | { |
1009 | Q_UNUSED(event); |
1010 | //Prevent subsequent move and release events being disregarded my the default event->ignore() from QQuickItem |
1011 | } |
1012 | |
1013 | } // namespace Qt3DRender |
1014 | |
1015 | QT_END_NAMESPACE |
1016 | |
1017 | #include "scene3ditem.moc" |
1018 | |