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