Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qquick3dviewport.cpp
Go to the documentation of this file.
1// Copyright (C) 2019 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5
20
21#include <QtQuick3DRuntimeRender/private/qssgrenderlayer_p.h>
22#include <QtQuick3DRuntimeRender/private/qssglayerrenderdata_p.h>
23
24#include <QtQuick3DUtils/private/qssgassert_p.h>
25#include <QtQuick3DUtils/private/qssgfrustum_p.h>
26
27#include <qsgtextureprovider.h>
28#include <QSGSimpleTextureNode>
29#include <QSGRendererInterface>
30#include <QQuickWindow>
31#include <QtQuick/private/qquickitem_p.h>
32#include <QtQuick/private/qquickpointerhandler_p.h>
33
34#include <QtQml>
35
36#include <QtGui/private/qeventpoint_p.h>
37
38#include <QtCore/private/qnumeric_p.h>
39#include <QtCore/qpointer.h>
40
41#include <optional>
42
44
45Q_STATIC_LOGGING_CATEGORY(lcEv, "qt.quick3d.event")
46Q_STATIC_LOGGING_CATEGORY(lcPick, "qt.quick3d.pick")
47
48static bool isforceInputHandlingSet()
49{
50 static const bool v = (qEnvironmentVariableIntValue("QT_QUICK3D_FORCE_INPUT_HANDLING") > 0);
51 return v;
52}
53
55{
56 static void removeAll() {
57 for (const auto &o : std::as_const(owners)) {
58 if (!o.isNull())
59 o->setSceneTransform(nullptr);
60 }
61 owners.clear();
62 }
63
64 void setOnDeliveryAgent(QQuickDeliveryAgent *da) {
65 da->setSceneTransform(this);
66 owners.append(da);
67 }
68
69 /*
70 Transforms viewport coordinates to 2D scene coordinates.
71 Returns the point in targetItem corresponding to \a viewportPoint,
72 assuming that targetItem is mapped onto sceneParentNode.
73 If it's no longer a "hit" on sceneParentNode, returns the last-good point.
74 */
75 QPointF map(const QPointF &viewportPoint) override {
76 QPointF point = viewportPoint;
77 // Despite the name, the input coordinates are the window viewport coordinates
78 // so unless the View3D is the same size of the Window, we need to translate
79 // to the View3D coordinates before doing any picking.
80 if (viewport)
81 point = viewport->mapFromScene(viewportPoint);
82 point.rx() *= scaleX;
83 point.ry() *= scaleY;
84 std::optional<QSSGRenderRay> rayResult = renderer->getRayFromViewportPos(point);
85 if (rayResult.has_value()) {
86 const auto pickResults = renderer->syncPickOne(rayResult.value(), sceneParentNode);
87 if (!pickResults.isEmpty()) {
88 const auto pickResult = pickResults.first();
89 auto ret = pickResult.m_localUVCoords.toPointF();
90 if (!uvCoordsArePixels) {
91 ret = QPointF(targetItem->x() + ret.x() * targetItem->width(),
92 targetItem->y() - ret.y() * targetItem->height() + targetItem->height());
93 }
94 const bool outOfModel = pickResult.m_localUVCoords.isNull();
95 qCDebug(lcEv) << viewportPoint << "->" << (outOfModel ? "OOM" : "") << ret << "@" << pickResult.m_scenePosition
96 << "UV" << pickResult.m_localUVCoords << "dist" << qSqrt(pickResult.m_distanceSq);
97 if (outOfModel) {
98 return lastGoodMapping;
99 } else {
100 lastGoodMapping = ret;
101 return ret;
102 }
103 }
104 }
105 return QPointF();
106 }
107
110 QSSGRenderNode *sceneParentNode = nullptr;
114 bool uvCoordsArePixels = false; // if false, they are in the range 0..1
116
118};
119
121
123{
125public:
126 static void extensionAppend(QQmlListProperty<QQuick3DObject> *list, QQuick3DObject *extension)
127 {
128 QSSG_ASSERT(list && extension, return);
129
130 if (QQuick3DViewport *that = qobject_cast<QQuick3DViewport *>(list->object)) {
131 if (const auto idx = that->m_extensions.indexOf(extension); idx == -1) {
132 if (!extension->parentItem())
133 extension->setParentItem(that->m_sceneRoot);
134 that->m_extensions.push_back(extension);
135 that->m_extensionListDirty = true;
136 }
137 }
138 }
139 static QQuick3DObject *extensionAt(QQmlListProperty<QQuick3DObject> *list, qsizetype index)
140 {
141 QQuick3DObject *ret = nullptr;
142 QSSG_ASSERT(list, return ret);
143
144 if (QQuick3DViewport *that = qobject_cast<QQuick3DViewport *>(list->object)) {
145 if (that->m_extensions.size() > index)
146 ret = that->m_extensions.at(index);
147 }
148
149 return ret;
150 }
151 static qsizetype extensionCount(QQmlListProperty<QQuick3DObject> *list)
152 {
153 qsizetype ret = -1;
154 QSSG_ASSERT(list, return ret);
155
156 if (QQuick3DViewport *that = qobject_cast<QQuick3DViewport *>(list->object))
157 ret = that->m_extensions.size();
158
159 return ret;
160 }
161 static void extensionClear(QQmlListProperty<QQuick3DObject> *list)
162 {
163 QSSG_ASSERT(list, return);
164
165 if (QQuick3DViewport *that = qobject_cast<QQuick3DViewport *>(list->object)) {
166 that->m_extensions.clear();
167 that->m_extensionListDirty = true;
168 }
169 }
170 static void extensionReplace(QQmlListProperty<QQuick3DObject> *list, qsizetype idx, QQuick3DObject *o)
171 {
172 QSSG_ASSERT(list, return);
173
174 if (QQuick3DViewport *that = qobject_cast<QQuick3DViewport *>(list->object)) {
175 if (that->m_extensions.size() > idx && idx > -1) {
176 that->m_extensions.replace(idx, o);
177 that->m_extensionListDirty = true;
178 }
179 }
180 }
181 static void extensionRemoveLast(QQmlListProperty<QQuick3DObject> *list)
182 {
183 QSSG_ASSERT(list, return);
184
185 if (QQuick3DViewport *that = qobject_cast<QQuick3DViewport *>(list->object)) {
186 that->m_extensions.removeLast();
187 that->m_extensionListDirty = true;
188 }
189 }
190};
191
192/*!
193 \qmltype View3D
194 \inherits Item
195 \inqmlmodule QtQuick3D
196 \brief Provides a viewport on which to render a 3D scene.
197
198 View3D provides a 2D surface on which a 3D scene can be rendered. This
199 surface is a Qt Quick \l Item and can be placed in a Qt Quick scene.
200
201 There are two ways to define the 3D scene that is visualized on the View3D:
202 If you define a hierarchy of \l{Node}{Node-based} items as children of
203 the View3D directly, then this will become the implicit scene of the View3D.
204
205 It is also possible to reference an existing scene by using the \l importScene
206 property and setting it to the root \l Node of the scene you want to visualize.
207 This \l Node does not have to be an ancestor of the View3D, and you can have
208 multiple View3Ds that import the same scene.
209
210 This is demonstrated in \l {Qt Quick 3D - View3D example}{View3D example}.
211
212 If the View3D both has child \l{Node}{Nodes} and the \l importScene property is
213 set simultaneously, then both scenes will be rendered as if they were sibling
214 subtrees in the same scene.
215
216 To control how a scene is rendered, you can set the \l environment
217 property. The type \l SceneEnvironment has a number of visual properties
218 that can be adjusted, such as background color, tone mapping, anti-aliasing
219 and more. \l ExtendedSceneEnvironment in the \c{QtQuick3D.Helpers} module
220 extends \l SceneEnvironment with even more features, adding common
221 post-processing effects.
222
223 In addition, in order for anything to be rendered in the View3D, the scene
224 needs a \l Camera. If there is only a single \l Camera in the scene, then
225 this will automatically be picked. Otherwise, the \l camera property can
226 be used to select the camera. The \l Camera decides which parts of the scene
227 are visible, and how they are projected onto the 2D surface.
228
229 By default, the 3D scene will first be rendered into an off-screen buffer and
230 then composited with the rest of the Qt Quick scene when it is done. This provides
231 the maximum level of compatibility, but may have performance implications on some
232 graphics hardware. If this is the case, the \l renderMode property can be used to
233 switch how the View3D is rendered into the window.
234
235 A View3D with the default Offscreen \l renderMode is implicitly a
236 \l{QSGTextureProvider}{texture provider} as well. This means that \l
237 ShaderEffect or \l{QtQuick3D::Texture::sourceItem}{Texture.sourceItem} can reference
238 the View3D directly as long as all items live within the same
239 \l{QQuickWindow}{window}. Like with any other \l Item, it is also possible
240 to switch the View3D, or one of its ancestors, into a texture-based
241 \l{QtQuick::Item::layer.enabled}{item layer}.
242
243 \sa {Qt Quick 3D - View3D example}
244*/
245
246QQuick3DViewport::QQuick3DViewport(QQuickItem *parent)
247 : QQuickItem(parent)
248{
249 setFlag(ItemHasContents);
250 m_camera = nullptr;
251 m_sceneRoot = new QQuick3DSceneRootNode(this);
252 m_renderStats = new QQuick3DRenderStats();
253 QQuick3DSceneManager *sceneManager = new QQuick3DSceneManager();
254 QQuick3DObjectPrivate::get(m_sceneRoot)->refSceneManager(*sceneManager);
255 Q_ASSERT(sceneManager == QQuick3DObjectPrivate::get(m_sceneRoot)->sceneManager);
256 connect(sceneManager, &QQuick3DSceneManager::needsUpdate,
257 this, &QQuickItem::update);
258
259 // Overrides the internal input handling to always be true
260 // instead of potentially updated after a sync (see updatePaintNode)
261 if (isforceInputHandlingSet()) {
262 m_enableInputProcessing = true;
263 updateInputProcessing();
264 forceActiveFocus();
265 }
266}
267
268QQuick3DViewport::~QQuick3DViewport()
269{
270 // With the threaded render loop m_directRenderer must be destroyed on the
271 // render thread at the proper time, not here. That's handled in
272 // releaseResources() + upon sceneGraphInvalidated. However with a
273 // QQuickRenderControl-based window on the main thread there is a good
274 // chance that this viewport (and so our sceneGraphInvalidated signal
275 // connection) is destroyed before the window and the rendercontrol. So act
276 // here then.
277 if (m_directRenderer && m_directRenderer->thread() == thread()) {
278 delete m_directRenderer;
279 m_directRenderer = nullptr;
280 }
281
282 // If the quick window still exists, make sure to disconnect any of the direct
283 // connections to this View3D
284 if (auto qw = window())
285 disconnect(qw, nullptr, this, nullptr);
286
287 auto sceneManager = QQuick3DObjectPrivate::get(m_sceneRoot)->sceneManager;
288 if (sceneManager) {
289 sceneManager->setParent(nullptr);
290 if (auto wa = sceneManager->wattached)
291 wa->queueForCleanup(sceneManager);
292 }
293
294 delete m_sceneRoot;
295 m_sceneRoot = nullptr;
296
297 delete m_builtInEnvironment;
298
299 // m_renderStats is tightly coupled with the render thread, so can't delete while we
300 // might still be rendering.
301 m_renderStats->deleteLater();
302
303 if (!window() && sceneManager && sceneManager->wattached)
304 QMetaObject::invokeMethod(sceneManager->wattached, &QQuick3DWindowAttachment::evaluateEol, Qt::QueuedConnection);
305}
306
307static void ssgn_append(QQmlListProperty<QObject> *property, QObject *obj)
308{
309 if (!obj)
310 return;
311 QQuick3DViewport *view3d = static_cast<QQuick3DViewport *>(property->object);
312
313 if (QQuick3DObject *sceneObject = qmlobject_cast<QQuick3DObject *>(obj)) {
314 QQmlListProperty<QObject> itemProperty = QQuick3DObjectPrivate::get(view3d->scene())->data();
315 itemProperty.append(&itemProperty, sceneObject);
316 } else {
317 QQuickItemPrivate::data_append(property, obj);
318 }
319}
320
321static qsizetype ssgn_count(QQmlListProperty<QObject> *property)
322{
323 QQuick3DViewport *view3d = static_cast<QQuick3DViewport *>(property->object);
324 if (!view3d || !view3d->scene() || !QQuick3DObjectPrivate::get(view3d->scene())->data().count)
325 return 0;
326 QQmlListProperty<QObject> itemProperty = QQuick3DObjectPrivate::get(view3d->scene())->data();
327 return itemProperty.count(&itemProperty);
328}
329
330static QObject *ssgn_at(QQmlListProperty<QObject> *property, qsizetype i)
331{
332 QQuick3DViewport *view3d = static_cast<QQuick3DViewport *>(property->object);
333 QQmlListProperty<QObject> itemProperty = QQuick3DObjectPrivate::get(view3d->scene())->data();
334 return itemProperty.at(&itemProperty, i);
335}
336
337static void ssgn_clear(QQmlListProperty<QObject> *property)
338{
339 QQuick3DViewport *view3d = static_cast<QQuick3DViewport *>(property->object);
340 QQmlListProperty<QObject> itemProperty = QQuick3DObjectPrivate::get(view3d->scene())->data();
341 return itemProperty.clear(&itemProperty);
342}
343
344
345QQmlListProperty<QObject> QQuick3DViewport::data()
346{
347 return QQmlListProperty<QObject>(this,
348 nullptr,
349 ssgn_append,
350 ssgn_count,
351 ssgn_at,
352 ssgn_clear);
353}
354
355/*!
356 \qmlproperty QtQuick3D::Camera QtQuick3D::View3D::camera
357
358 This property specifies which \l Camera is used to render the scene. If this
359 property is not set, then the first enabled camera in the scene will be used.
360
361 \note It is strongly recommended to set this property explicitly rather than relying on
362 automatic camera selection. When multiple cameras are present in the scene, automatic
363 selection does not guarantee which camera will be chosen. When \l{Node::}{layers}
364 are used, explicitly specifying the camera also avoids unnecessary re-evaluation of
365 scene nodes.
366
367 \note If this property contains a camera that's not \l {Node::visible}{visible} then
368 no further attempts to find a camera will be done.
369
370 \sa PerspectiveCamera, OrthographicCamera, FrustumCamera, CustomCamera
371*/
372QQuick3DCamera *QQuick3DViewport::camera() const
373{
374 return m_camera;
375}
376
377/*!
378 \qmlproperty QtQuick3D::SceneEnvironment QtQuick3D::View3D::environment
379
380 This property specifies the SceneEnvironment used to render the scene.
381
382 \note Setting this property to \c null will reset the SceneEnvironment to the default.
383
384 \sa SceneEnvironment
385*/
386QQuick3DSceneEnvironment *QQuick3DViewport::environment() const
387{
388 if (!m_environment) {
389 if (!m_builtInEnvironment) {
390 m_builtInEnvironment = new QQuick3DSceneEnvironment;
391 // Check that we are on the "correct" thread, and move the environment to the
392 // correct thread if not. This can happen when environment() is called from the
393 // sync and no scene environment has been set.
394 if (QThread::currentThread() != m_sceneRoot->thread())
395 m_builtInEnvironment->moveToThread(m_sceneRoot->thread());
396 m_builtInEnvironment->setParentItem(m_sceneRoot);
397 }
398
399 return m_builtInEnvironment;
400 }
401
402 return m_environment;
403}
404
405/*!
406 \qmlproperty QtQuick3D::Node QtQuick3D::View3D::scene
407 \readonly
408
409 Returns the root \l Node of the View3D's scene.
410
411 To define the 3D scene that is visualized in the View3D:
412
413 \list
414 \li Define a hierarchy of \l{Node}{Node-based} items as children of
415 the View3D directly, then this will become the implicit \l scene of the
416 View3D.
417 \li Reference an existing scene by using the \l importScene property and
418 set it to the root \l Node of the scene you want to visualize. This
419 \l Node does not have to be an ancestor of the View3D, and you can have
420 multiple View3Ds that import the same scene.
421 \endlist
422
423 \sa importScene
424*/
425QQuick3DNode *QQuick3DViewport::scene() const
426{
427 return m_sceneRoot;
428}
429
430/*!
431 \qmlproperty QtQuick3D::Node QtQuick3D::View3D::importScene
432
433 This property defines the reference node of the scene to render to the
434 viewport. The node does not have to be a child of the View3D. This
435 referenced node becomes a sibling with child nodes of View3D, if there are
436 any.
437
438 \note Scenes can only be shared between View3D items that are in the same
439 \l{QQuickWindow}{window}.
440
441 \note When sharing scenes between multiple View3D items the imported scene should
442 be imported in whole, that is, importing a subtree of a scene is not supported.
443 If multiple View3Ds need to show different parts of the same shared scene,
444 consider using \l {Node::}{layers} instead.
445
446 \note This property can only be set once, and subsequent changes will have
447 no effect.
448
449 You can also define a hierarchy of \l{Node}{Node-based} items as children of
450 the View3D directly, then this will become the implicit scene of the
451 View3D.
452
453 To return the current scene of the View3D, use the \l scene property.
454
455 \sa Node
456*/
457QQuick3DNode *QQuick3DViewport::importScene() const
458{
459 return m_importScene;
460}
461
462/*!
463 \qmlproperty enumeration QtQuick3D::View3D::renderMode
464
465 This property determines how the View3D is combined with the other parts of the
466 Qt Quick scene.
467
468 By default, the scene will be rendered into an off-screen buffer as an intermediate
469 step. This off-screen buffer is then rendered into the window (or render target) like any
470 other Qt Quick \l Item.
471
472 For most users, there will be no need to change the render mode, and this property can
473 safely be ignored. But on some graphics hardware, the use of an off-screen buffer can be
474 a performance bottleneck. If this is the case, it might be worth experimenting with other
475 modes.
476
477 \value View3D.Offscreen The scene is rendered into an off-screen buffer as an intermediate
478 step. This off-screen buffer is then composited with the rest of the Qt Quick scene.
479
480 \value View3D.Underlay The scene is rendered directly to the window before the rest of
481 the Qt Quick scene is rendered. With this mode, the View3D cannot be placed on top of
482 other Qt Quick items.
483
484 \value View3D.Overlay The scene is rendered directly to the window after Qt Quick is
485 rendered. With this mode, the View3D will always be on top of other Qt Quick items.
486
487 \value View3D.Inline The View3D's scene graph is embedded into the main scene graph,
488 and the same ordering semantics are applied as to any other Qt Quick \l Item. As this
489 mode can lead to subtle issues, depending on the contents of the scene, due to
490 injecting depth-based 3D content into a 2D scene graph, it is not recommended to be
491 used, unless a specific need arises.
492
493 The default is \c{View3D.Offscreen}.
494
495 \note When changing the render mode, it is important to note that \c{View3D.Offscreen} (the
496 default) is the only mode which guarantees perfect graphics fidelity. The other modes
497 all have limitations that can cause visual glitches, so it is important to check that
498 the visual output still looks correct when changing this property.
499
500 \note When using the Underlay, Overlay, or Inline modes, it can be useful, and in some
501 cases, required, to disable the Qt Quick scene graph's depth buffer writes via
502 QQuickGraphicsConfiguration::setDepthBufferFor2D() before showing the QQuickWindow or
503 QQuickView hosting the View3D item.
504*/
505QQuick3DViewport::RenderMode QQuick3DViewport::renderMode() const
506{
507 return m_renderMode;
508}
509
510/*!
511 \qmlproperty enumeration QtQuick3D::View3D::renderFormat
512 \since 6.4
513
514 This property determines the backing texture's format. Applicable only when
515 the View3D is rendering to a texture, for example because the \l renderMode
516 is \c{View3D.Offscreen}.
517
518 The default is \c{ShaderEffectSource.RGBA8}.
519
520 If the format is not supported by the underlying graphics driver at run
521 time, RGBA8 is used.
522
523 \list
524 \li ShaderEffectSource.RGBA8
525 \li ShaderEffectSource.RGBA16F
526 \li ShaderEffectSource.RGBA32F
527 \endlist
528
529 \sa QtQuick::ShaderEffectSource::format, QtQuick::Item::layer.format
530 */
531#if QT_CONFIG(quick_shadereffect)
532QQuickShaderEffectSource::Format QQuick3DViewport::renderFormat() const
533{
534 return m_renderFormat;
535}
536#endif
537
538/*!
539 \qmlproperty QtQuick3D::RenderStats QtQuick3D::View3D::renderStats
540 \readonly
541
542 This property provides statistics about the rendering of a frame, such as
543 \l {RenderStats::fps}{fps}, \l {RenderStats::frameTime}{frameTime},
544 \l {RenderStats::renderTime}{renderTime}, \l {RenderStats::syncTime}{syncTime},
545 and \l {RenderStats::maxFrameTime}{maxFrameTime}.
546*/
547QQuick3DRenderStats *QQuick3DViewport::renderStats() const
548{
549 return m_renderStats;
550}
551
552QQuick3DSceneRenderer *QQuick3DViewport::createRenderer() const
553{
554 QQuick3DSceneRenderer *renderer = nullptr;
555
556 if (QQuickWindow *qw = window()) {
557 auto wa = QQuick3DSceneManager::getOrSetWindowAttachment(*qw);
558 auto rci = wa->rci();
559 if (!rci) {
560 QSGRendererInterface *rif = qw->rendererInterface();
561 if (QSSG_GUARD(QSGRendererInterface::isApiRhiBased(rif->graphicsApi()))) {
562 QRhi *rhi = static_cast<QRhi *>(rif->getResource(qw, QSGRendererInterface::RhiResource));
563 QSSG_CHECK_X(rhi != nullptr, "No QRhi from QQuickWindow, this cannot happen");
564 // The RenderContextInterface, and the objects owned by it (such
565 // as, the BufferManager) are always per-QQuickWindow, and so per
566 // scenegraph render thread. Hence the association with window.
567 // Multiple View3Ds in the same window can use the same rendering
568 // infrastructure (so e.g. the same QSSGBufferManager), but two
569 // View3D objects in different windows must not, except for certain
570 // components that do not work with and own native graphics
571 // resources (most notably, QSSGShaderLibraryManager - but this
572 // distinction is handled internally by QSSGRenderContextInterface).
573 rci = std::make_shared<QSSGRenderContextInterface>(rhi);
574 wa->setRci(rci);
575
576 // Use DirectConnection to stay on the render thread, if there is one.
577 connect(wa, &QQuick3DWindowAttachment::releaseCachedResources, this,
578 &QQuick3DViewport::onReleaseCachedResources, Qt::DirectConnection);
579
580 } else {
581 qWarning("The Qt Quick scene is using a rendering method that is not based on QRhi and a 3D graphics API. "
582 "Qt Quick 3D is not functional in such an environment. The View3D item is not going to display anything.");
583 }
584 }
585
586 if (rci) {
587 renderer = new QQuick3DSceneRenderer(rci);
588 Q_QUICK3D_PROFILE_ASSIGN_ID(this, renderer);
589 }
590 }
591
592 return renderer;
593}
594
595bool QQuick3DViewport::isTextureProvider() const
596{
597 // We can only be a texture provider if we are rendering to a texture first
598 if (m_renderMode == QQuick3DViewport::Offscreen)
599 return true;
600
601 return false;
602}
603
604QSGTextureProvider *QQuick3DViewport::textureProvider() const
605{
606 // When Item::layer::enabled == true, QQuickItem will be a texture
607 // provider. In this case we should prefer to return the layer rather
608 // than the fbo texture.
609 if (QQuickItem::isTextureProvider())
610 return QQuickItem::textureProvider();
611
612 // We can only be a texture provider if we are rendering to a texture first
613 if (m_renderMode != QQuick3DViewport::Offscreen)
614 return nullptr;
615
616 QQuickWindow *w = window();
617 if (!w) {
618 qWarning("QSSGView3D::textureProvider: can only be queried on the rendering thread of an exposed window");
619 return nullptr;
620 }
621
622 if (!m_node)
623 m_node = new SGFramebufferObjectNode;
624 return m_node;
625}
626
627class CleanupJob : public QRunnable
628{
629public:
630 CleanupJob(QQuick3DSGDirectRenderer *renderer) : m_renderer(renderer) { }
631 void run() override { delete m_renderer; }
632private:
633 QQuick3DSGDirectRenderer *m_renderer;
634};
635
636void QQuick3DViewport::releaseResources()
637{
638 if (m_directRenderer) {
639 window()->scheduleRenderJob(new CleanupJob(m_directRenderer), QQuickWindow::BeforeSynchronizingStage);
640 m_directRenderer = nullptr;
641 }
642
643 m_node = nullptr;
644}
645
646void QQuick3DViewport::cleanupDirectRenderer()
647{
648 delete m_directRenderer;
649 m_directRenderer = nullptr;
650}
651
652void QQuick3DViewport::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
653{
654 QQuickItem::geometryChange(newGeometry, oldGeometry);
655
656 if (newGeometry.size() != oldGeometry.size())
657 update();
658}
659
660QSGNode *QQuick3DViewport::updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNodeData *)
661{
662 // When changing render modes
663 if (m_renderModeDirty) {
664 if (node) {
665 delete node;
666 node = nullptr;
667 m_node = nullptr;
668 m_renderNode = nullptr;
669 }
670 if (m_directRenderer) {
671 delete m_directRenderer;
672 m_directRenderer = nullptr;
673 }
674 }
675
676 m_renderModeDirty = false;
677
678 switch (m_renderMode) {
679 // Direct rendering
680 case Underlay:
681 Q_FALLTHROUGH();
682 case Overlay:
683 setupDirectRenderer(m_renderMode);
684 node = nullptr;
685 break;
686 case Offscreen:
687 node = setupOffscreenRenderer(node);
688 break;
689 case Inline:
690 // QSGRenderNode-based rendering
691 node = setupInlineRenderer(node);
692 break;
693 }
694
695 if (!isforceInputHandlingSet()) {
696 // Implicitly enable internal input processing if any item2ds are present.
697 const auto inputHandlingEnabled =
698 QQuick3DObjectPrivate::get(m_sceneRoot)->sceneManager->inputHandlingEnabled;
699 const auto enable = inputHandlingEnabled > 0;
700 if (m_enableInputProcessing != enable) {
701 m_enableInputProcessing = enable;
702 QMetaObject::invokeMethod(this, "updateInputProcessing", Qt::QueuedConnection);
703 }
704 }
705
706 return node;
707}
708
709void QQuick3DViewport::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
710{
711 if (change == ItemSceneChange) {
712 if (value.window) {
713 // TODO: if we want to support multiple windows, there has to be a scene manager for
714 // every window.
715 QQuick3DObjectPrivate::get(m_sceneRoot)->sceneManager->setWindow(value.window);
716 if (m_importScene)
717 QQuick3DObjectPrivate::get(m_importScene)->sceneManager->setWindow(value.window);
718 m_renderStats->setWindow(value.window);
719 }
720 } else if (change == ItemVisibleHasChanged && isVisible()) {
721 m_visibilityChanged = true;
722 update();
723 }
724}
725
726bool QQuick3DViewport::event(QEvent *event)
727{
728 if (m_enableInputProcessing && event->isPointerEvent())
729 return internalPick(static_cast<QPointerEvent *>(event));
730 else
731 return QQuickItem::event(event);
732}
733
734void QQuick3DViewport::componentComplete()
735{
736 QQuickItem::componentComplete();
737 Q_QUICK3D_PROFILE_REGISTER(this);
738}
739
740void QQuick3DViewport::setCamera(QQuick3DCamera *camera)
741{
742 if (m_camera == camera)
743 return;
744
745 if (camera && !camera->parentItem())
746 camera->setParentItem(m_sceneRoot);
747 if (camera)
748 camera->updateGlobalVariables(QRect(0, 0, width(), height()));
749
750 QQuick3DObjectPrivate::attachWatcherPriv(m_sceneRoot, this, &QQuick3DViewport::setCamera, camera, m_camera);
751
752 m_camera = camera;
753 emit cameraChanged();
754 update();
755}
756
757void QQuick3DViewport::setMultiViewCameras(QQuick3DCamera **firstCamera, int count)
758{
759 m_multiViewCameras.clear();
760 bool sendChangeSignal = false;
761 for (int i = 0; i < count; ++i) {
762 QQuick3DCamera *camera = *(firstCamera + i);
763 if (camera) {
764 if (!camera->parentItem())
765 camera->setParentItem(m_sceneRoot);
766 camera->updateGlobalVariables(QRect(0, 0, width(), height()));
767 }
768 if (i == 0) {
769 if (m_camera != camera) {
770 m_camera = camera;
771 sendChangeSignal = true;
772 }
773 }
774
775 m_multiViewCameras.append(camera);
776
777 // ### do we need attachWatcher stuff? the Xr-provided cameras cannot disappear, although the XrActor (the owner) might
778 }
779
780 if (sendChangeSignal)
781 emit cameraChanged();
782
783 update();
784}
785
786void QQuick3DViewport::setEnvironment(QQuick3DSceneEnvironment *environment)
787{
788 if (m_environment == environment)
789 return;
790
791 m_environment = environment;
792 if (m_environment && !m_environment->parentItem())
793 m_environment->setParentItem(m_sceneRoot);
794
795 QQuick3DObjectPrivate::attachWatcherPriv(m_sceneRoot, this, &QQuick3DViewport::setEnvironment, environment, m_environment);
796
797 emit environmentChanged();
798 update();
799}
800
801void QQuick3DViewport::setImportScene(QQuick3DNode *inScene)
802{
803 // ### We may need consider the case where there is
804 // already a scene tree here
805 // FIXME : Only the first importScene is an effective one
806 if (m_importScene)
807 return;
808
809 // FIXME : Check self-import or cross-import
810 // Currently it does not work since importScene qml parsed in a reverse order.
811 QQuick3DNode *scene = inScene;
812 while (scene) {
813 if (m_sceneRoot == scene) {
814 qmlWarning(this) << "Cannot allow self-import or cross-import!";
815 return;
816 }
817
818 QQuick3DSceneRootNode *rn = qobject_cast<QQuick3DSceneRootNode *>(scene);
819 scene = rn ? rn->view3D()->importScene() : nullptr;
820 }
821
822 m_importScene = inScene;
823 if (m_importScene)
824 updateSceneManagerForImportScene();
825
826 emit importSceneChanged();
827 update();
828}
829
830void QQuick3DViewport::setRenderMode(QQuick3DViewport::RenderMode renderMode)
831{
832 if (m_renderMode == renderMode)
833 return;
834
835 m_renderMode = renderMode;
836 m_renderModeDirty = true;
837 emit renderModeChanged();
838 update();
839}
840
841#if QT_CONFIG(quick_shadereffect)
842void QQuick3DViewport::setRenderFormat(QQuickShaderEffectSource::Format format)
843{
844 if (m_renderFormat == format)
845 return;
846
847 m_renderFormat = format;
848 m_renderModeDirty = true;
849 emit renderFormatChanged();
850 update();
851}
852#endif
853
854/*!
855 \qmlproperty int QtQuick3D::View3D::explicitTextureWidth
856 \since 6.7
857
858 The width, in pixels, of the item's associated texture. Relevant when a
859 fixed texture size is desired that does not depend on the item's size. This
860 size has no effect on the geometry of the item (its size and placement
861 within the scene), which means the texture's content will appear scaled up
862 or down (and possibly stretched) onto the item's area.
863
864 By default the value is \c 0. A value of 0 means that texture's size
865 follows the item's size. (\c{texture size in pixels} = \c{item's logical
866 size} * \c{device pixel ratio}).
867
868 \note This property is relevant only when \l renderMode is set to \c
869 Offscreen. Its value is ignored otherwise.
870
871 \sa explicitTextureHeight, effectiveTextureSize, DebugView
872*/
873int QQuick3DViewport::explicitTextureWidth() const
874{
875 return m_explicitTextureWidth;
876}
877
878void QQuick3DViewport::setExplicitTextureWidth(int width)
879{
880 if (m_explicitTextureWidth == width)
881 return;
882
883 m_explicitTextureWidth = width;
884 emit explicitTextureWidthChanged();
885 update();
886}
887
888/*!
889 \qmlproperty int QtQuick3D::View3D::explicitTextureHeight
890 \since 6.7
891
892 The height, in pixels, of the item's associated texture. Relevant when a
893 fixed texture size is desired that does not depend on the item's size. This
894 size has no effect on the geometry of the item (its size and placement
895 within the scene), which means the texture's content will appear scaled up
896 or down (and possibly stretched) onto the item's area.
897
898 By default the value is \c 0. A value of 0 means that texture's size
899 follows the item's size. (\c{texture size in pixels} = \c{item's logical
900 size} * \c{device pixel ratio}).
901
902 \note This property is relevant only when \l renderMode is set to \c
903 Offscreen. Its value is ignored otherwise.
904
905 \sa explicitTextureWidth, effectiveTextureSize, DebugView
906*/
907int QQuick3DViewport::explicitTextureHeight() const
908{
909 return m_explicitTextureHeight;
910}
911
912void QQuick3DViewport::setExplicitTextureHeight(int height)
913{
914 if (m_explicitTextureHeight == height)
915 return;
916
917 m_explicitTextureHeight = height;
918 emit explicitTextureHeightChanged();
919 update();
920}
921
922/*!
923 \qmlproperty size QtQuick3D::View3D::effectiveTextureSize
924 \since 6.7
925
926 This property exposes the size, in pixels, of the underlying color (and
927 depth/stencil) buffers. It is provided for use on the GUI (main) thread, in
928 QML bindings or JavaScript.
929
930 This is a read-only property.
931
932 \note This property is relevant only when \l renderMode is set to
933 \c Offscreen.
934
935 \sa explicitTextureWidth, explicitTextureHeight, DebugView
936*/
937QSize QQuick3DViewport::effectiveTextureSize() const
938{
939 return m_effectiveTextureSize;
940}
941
942
943/*!
944 \qmlmethod vector3d View3D::mapFrom3DScene(vector3d scenePos)
945
946 Transforms \a scenePos from scene space (3D) into view space (2D).
947
948 The returned x- and y-values will be be in view coordinates, with the top-left
949 corner at [0, 0] and the bottom-right corner at [width, height]. The returned
950 z-value contains the distance from the near clip plane of the frustum (clipNear) to
951 \a scenePos in scene coordinates. If the distance is negative, that means the \a scenePos
952 is behind the active camera. If \a scenePos cannot be mapped to a position in the scene,
953 a position of [0, 0, 0] is returned.
954
955 This function requires that \l camera is assigned to the view.
956
957 \sa mapTo3DScene(), {Camera::mapToViewport()}{Camera.mapToViewport()}
958*/
959QVector3D QQuick3DViewport::mapFrom3DScene(const QVector3D &scenePos) const
960{
961 if (!m_camera) {
962 qmlWarning(this) << "Cannot resolve view position without a camera assigned!";
963 return QVector3D(0, 0, 0);
964 }
965
966 qreal _width = width();
967 qreal _height = height();
968 if (_width == 0 || _height == 0)
969 return QVector3D(0, 0, 0);
970
971 const QVector3D normalizedPos = m_camera->mapToViewport(scenePos, _width, _height);
972 return normalizedPos * QVector3D(float(_width), float(_height), 1);
973}
974
975/*!
976 \qmlmethod vector3d View3D::mapTo3DScene(vector3d viewPos)
977
978 Transforms \a viewPos from view space (2D) into scene space (3D).
979
980 The x- and y-values of \a viewPos should be in view coordinates, with the top-left
981 corner at [0, 0] and the bottom-right corner at [width, height]. The z-value is
982 interpreted as the distance from the near clip plane of the frustum (clipNear) in
983 scene coordinates.
984
985 If \a viewPos cannot successfully be mapped to a position in the scene, a position of
986 [0, 0, 0] is returned.
987
988 This function requires that a \l camera is assigned to the view.
989
990 \sa mapFrom3DScene(), {Camera::mapFromViewport}{Camera.mapFromViewport()}
991*/
992QVector3D QQuick3DViewport::mapTo3DScene(const QVector3D &viewPos) const
993{
994 if (!m_camera) {
995 qmlWarning(this) << "Cannot resolve scene position without a camera assigned!";
996 return QVector3D(0, 0, 0);
997 }
998
999 qreal _width = width();
1000 qreal _height = height();
1001 if (_width == 0 || _height == 0)
1002 return QVector3D(0, 0, 0);
1003
1004 const QVector3D normalizedPos = viewPos / QVector3D(float(_width), float(_height), 1);
1005 return m_camera->mapFromViewport(normalizedPos, _width, _height);
1006}
1007
1008/*!
1009 \qmlmethod pickResult View3D::pick(float x, float y)
1010
1011 This method will "shoot" a ray into the scene from view coordinates \a x and \a y
1012 and return information about the nearest intersection with an object in the scene.
1013
1014 This can, for instance, be called with mouse coordinates to find the object under the mouse cursor.
1015
1016 Triangles that the material's \l {Material::cullMode}{cull mode} discards
1017 when rendering are not picked either, so only geometry that is actually
1018 visible along the ray is reported.
1019
1020 \sa pickAll(), pickSubset(), pickInRect()
1021*/
1022QQuick3DPickResult QQuick3DViewport::pick(float x, float y) const
1023{
1024 QQuick3DSceneRenderer *renderer = getRenderer();
1025 if (!renderer)
1026 return QQuick3DPickResult();
1027
1028 const QPointF position(qreal(x) * window()->effectiveDevicePixelRatio() * m_widthMultiplier,
1029 qreal(y) * window()->effectiveDevicePixelRatio() * m_heightMultiplier);
1030 std::optional<QSSGRenderRay> rayResult = renderer->getRayFromViewportPos(position);
1031 if (!rayResult.has_value())
1032 return QQuick3DPickResult();
1033
1034 const auto resultList = renderer->syncPick(rayResult.value());
1035 return getNearestPickResult(resultList);
1036}
1037
1038/*!
1039 \qmlmethod pickResult View3D::pick(float x, float y, Model model)
1040
1041 This method will "shoot" a ray into the scene from view coordinates \a x and \a y
1042 and return information about the intersection between the ray and the specified \a model.
1043
1044 This can, for instance, be called with mouse coordinates to find the object under the mouse cursor.
1045
1046 \since 6.8
1047
1048 \sa pickAll(), pickSubset(), pickInRect()
1049*/
1050QQuick3DPickResult QQuick3DViewport::pick(float x, float y, QQuick3DModel *model) const
1051{
1052 QQuick3DSceneRenderer *renderer = getRenderer();
1053 if (!renderer)
1054 return QQuick3DPickResult();
1055
1056 const QPointF position(qreal(x) * window()->effectiveDevicePixelRatio(),
1057 qreal(y) * window()->effectiveDevicePixelRatio());
1058 std::optional<QSSGRenderRay> rayResult = renderer->getRayFromViewportPos(position);
1059
1060 if (!rayResult.has_value())
1061 return QQuick3DPickResult();
1062
1063 const auto renderNode = static_cast<QSSGRenderNode *>(QQuick3DObjectPrivate::get(model)->spatialNode);
1064 const auto resultList = renderer->syncPickOne(rayResult.value(), renderNode);
1065 return getNearestPickResult(resultList);
1066}
1067
1068/*!
1069 \qmlmethod List<pickResult> View3D::pickSubset(float x, float y, list<Model> models)
1070
1071 This method will "shoot" a ray into the scene from view coordinates \a x and \a y
1072 and return information about the intersections with the passed in list of \a models.
1073 This will only check against the list of models passed in.
1074 The returned list is sorted by distance from the camera with the nearest
1075 intersections appearing first and the furthest appearing last.
1076
1077 This can, for instance, be called with mouse coordinates to find the object under the mouse cursor.
1078
1079 Works with both property list<Model> and dynamic JavaScript arrays of models.
1080
1081 \since 6.8
1082
1083 \sa pick(), pickAll(), pickInRect()
1084*/
1085QList<QQuick3DPickResult> QQuick3DViewport::pickSubset(float x, float y, const QJSValue &models) const
1086{
1087 QQuick3DSceneRenderer *renderer = getRenderer();
1088 if (!renderer)
1089 return {};
1090
1091 QVarLengthArray<QSSGRenderNode*> renderNodes;
1092 // Check for regular JavaScript array
1093 if (models.isArray()) {
1094 const auto length = models.property(QStringLiteral("length")).toInt();
1095 if (length == 0)
1096 return {};
1097
1098 for (int i = 0; i < length; ++i) {
1099 const auto isQObject = models.property(i).isQObject();
1100 if (!isQObject) {
1101 qmlWarning(this) << "Type provided for picking is not a QObject. Needs to be of type QQuick3DModel.";
1102 continue;
1103 }
1104 const auto obj = models.property(i).toQObject();
1105 const auto model = qobject_cast<QQuick3DModel *>(obj);
1106 if (!model) {
1107 qmlWarning(this) << "Type " << obj->metaObject()->className() << " is not supported for picking. Needs to be of type QQuick3DModel.";
1108 continue;
1109 }
1110 const auto priv = QQuick3DObjectPrivate::get(model);
1111 if (priv && priv->spatialNode) {
1112 renderNodes.push_back(static_cast<QSSGRenderNode*>(priv->spatialNode));
1113 }
1114 }
1115 } else {
1116 // Check for property list<Model>
1117 const auto subsetVariant = models.toVariant();
1118 if (!subsetVariant.isValid() || !subsetVariant.canConvert<QQmlListReference>())
1119 return {};
1120
1121 const auto list = subsetVariant.value<QQmlListReference>();
1122
1123 // Only support array of models
1124 if (list.listElementType()->className() != QQuick3DModel::staticMetaObject.className()) {
1125 qmlWarning(this) << "Type " << list.listElementType()->className() << " is not supported for picking. Needs to be of type QQuick3DModel.";
1126 return {};
1127 }
1128 for (int i = 0; i < list.count(); ++i) {
1129 auto model = static_cast<QQuick3DModel *>(list.at(i));
1130 if (!model)
1131 continue;
1132 auto priv = QQuick3DObjectPrivate::get(model);
1133 if (priv && priv->spatialNode) {
1134 renderNodes.push_back(static_cast<QSSGRenderNode*>(priv->spatialNode));
1135 }
1136 }
1137 }
1138
1139 if (renderNodes.empty())
1140 return {};
1141
1142 const QPointF position(qreal(x) * window()->effectiveDevicePixelRatio(),
1143 qreal(y) * window()->effectiveDevicePixelRatio());
1144 std::optional<QSSGRenderRay> rayResult = renderer->getRayFromViewportPos(position);
1145 if (!rayResult.has_value())
1146 return {};
1147
1148 const auto resultList = renderer->syncPickSubset(rayResult.value(), renderNodes);
1149
1150 QList<QQuick3DPickResult> processedResultList;
1151 processedResultList.reserve(resultList.size());
1152 for (const auto &result : resultList)
1153 processedResultList.append(processPickResult(result));
1154
1155 return processedResultList;
1156}
1157
1158/*!
1159 \qmlmethod List<pickResult> View3D::pickAll(float x, float y)
1160
1161 This method will "shoot" a ray into the scene from view coordinates \a x and \a y
1162 and return a list of information about intersections with objects in the scene.
1163 The returned list is sorted by distance from the camera with the nearest
1164 intersections appearing first and the furthest appearing last.
1165
1166 This can, for instance, be called with mouse coordinates to find the object under the mouse cursor.
1167
1168 \since 6.2
1169
1170 \sa pick(), pickSubset(), pickInRect()
1171*/
1172QList<QQuick3DPickResult> QQuick3DViewport::pickAll(float x, float y) const
1173{
1174 QQuick3DSceneRenderer *renderer = getRenderer();
1175 if (!renderer)
1176 return QList<QQuick3DPickResult>();
1177
1178 const QPointF position(qreal(x) * window()->effectiveDevicePixelRatio() * m_widthMultiplier,
1179 qreal(y) * window()->effectiveDevicePixelRatio() * m_heightMultiplier);
1180 std::optional<QSSGRenderRay> rayResult = renderer->getRayFromViewportPos(position);
1181 if (!rayResult.has_value())
1182 return QList<QQuick3DPickResult>();
1183
1184 const auto resultList = renderer->syncPickAll(rayResult.value());
1185 QList<QQuick3DPickResult> processedResultList;
1186 processedResultList.reserve(resultList.size());
1187 for (const auto &result : resultList)
1188 processedResultList.append(processPickResult(result));
1189
1190 return processedResultList;
1191}
1192
1193/*!
1194 \qmlmethod pickResult View3D::rayPick(vector3d origin, vector3d direction)
1195
1196 This method will "shoot" a ray into the scene starting at \a origin and in
1197 \a direction and return information about the nearest intersection with an
1198 object in the scene.
1199
1200 This can, for instance, be called with the position and forward vector of
1201 any object in a scene to see what object is in front of an item. This
1202 makes it possible to do picking from any point in the scene.
1203
1204 Triangles that the material's \l {Material::cullMode}{cull mode} discards
1205 when rendering are not picked either, so only geometry that is actually
1206 visible along the ray is reported.
1207
1208 \since 6.2
1209*/
1210QQuick3DPickResult QQuick3DViewport::rayPick(const QVector3D &origin, const QVector3D &direction) const
1211{
1212 QQuick3DSceneRenderer *renderer = getRenderer();
1213 if (!renderer)
1214 return QQuick3DPickResult();
1215
1216 const QSSGRenderRay ray(origin, direction);
1217 const auto resultList = renderer->syncPick(ray);
1218 return getNearestPickResult(resultList);
1219}
1220
1221/*!
1222 \qmlmethod List<pickResult> View3D::rayPickAll(vector3d origin, vector3d direction)
1223
1224 This method will "shoot" a ray into the scene starting at \a origin and in
1225 \a direction and return a list of information about the nearest intersections with
1226 objects in the scene.
1227 The list is presorted by distance from the origin along the direction
1228 vector with the nearest intersections appearing first and the furthest
1229 appearing last.
1230
1231 This can, for instance, be called with the position and forward vector of
1232 any object in a scene to see what objects are in front of an item. This
1233 makes it possible to do picking from any point in the scene.
1234
1235 \since 6.2
1236*/
1237QList<QQuick3DPickResult> QQuick3DViewport::rayPickAll(const QVector3D &origin, const QVector3D &direction) const
1238{
1239 QQuick3DSceneRenderer *renderer = getRenderer();
1240 if (!renderer)
1241 return QList<QQuick3DPickResult>();
1242
1243 const QSSGRenderRay ray(origin, direction);
1244
1245 const auto resultList = renderer->syncPickAll(ray);
1246 QList<QQuick3DPickResult> processedResultList;
1247 processedResultList.reserve(resultList.size());
1248 for (const auto &result : resultList) {
1249 auto processedResult = processPickResult(result);
1250 if (processedResult.hitType() != QQuick3DPickResultEnums::HitType::Null)
1251 processedResultList.append(processedResult);
1252 }
1253
1254 return processedResultList;
1255}
1256
1257/*!
1258 \qmlmethod pickResult View3D::rayPick(vector3d origin, vector3d direction, Model model)
1259
1260 This method will "shoot" a ray into the scene starting at \a origin and in
1261 \a direction and return information about the intersection between the ray and the specified \a model.
1262
1263 \since 6.11
1264*/
1265QQuick3DPickResult QQuick3DViewport::rayPick(const QVector3D &origin, const QVector3D &direction, QQuick3DModel *model) const
1266{
1267 QQuick3DSceneRenderer *renderer = getRenderer();
1268 if (!renderer)
1269 return QQuick3DPickResult();
1270
1271 const QSSGRenderRay ray(origin, direction);
1272
1273 const auto renderNode = static_cast<QSSGRenderNode *>(QQuick3DObjectPrivate::get(model)->spatialNode);
1274 const auto resultList = renderer->syncPickOne(ray, renderNode);
1275 return getNearestPickResult(resultList);
1276}
1277
1278/*!
1279 \qmlmethod pickResult View3D::closestPointPick(vector3d origin, float radius, Model model)
1280
1281 This method will find the point on the surface of \a model that is nearest to \a origin, within a distance of
1282 \a radius. If \a model is \c null, the closest object within \a radius will be found.
1283
1284 If no such object exists, \c null is returned.
1285
1286 \since 6.11
1287*/
1288QQuick3DPickResult QQuick3DViewport::closestPointPick(const QVector3D &origin, float radius, QQuick3DModel *model) const
1289{
1290 QQuick3DSceneRenderer *renderer = getRenderer();
1291
1292 if (!renderer)
1293 return QQuick3DPickResult();
1294 QSSGRenderNode *renderNode = nullptr;
1295 if (model) {
1296 renderNode = static_cast<QSSGRenderNode *>(QQuick3DObjectPrivate::get(model)->spatialNode);
1297 if (Q_UNLIKELY(!renderNode))
1298 return QQuick3DPickResult{};
1299 }
1300
1301 const auto pickResult = renderer->syncPickClosestPoint(origin, radius * radius, renderNode);
1302 if (!pickResult.has_value())
1303 return QQuick3DPickResult{};
1304 return processPickResult(pickResult.value());
1305}
1306
1307/*!
1308 \qmlmethod list<Object3D> View3D::pickInRect(point start, point end)
1309 \since 6.11
1310
1311 This method picks all objects within a rectangular region defined by
1312 \a start and \a end points in view coordinates.
1313
1314 Returns a list of objects that are within the specified rectangle. This
1315 can be used for marquee selection, where the user drags a rectangle to
1316 select multiple objects.
1317
1318 \sa pick(), pickAll(), pickSubset()
1319*/
1320QList<QQuick3DObject *> QQuick3DViewport::pickInRect(const QPointF &start, const QPointF &end) const
1321{
1322 const qreal minX = qMin(start.x(), end.x());
1323 const qreal maxX = qMax(start.x(), end.x());
1324 const qreal minY = qMin(start.y(), end.y());
1325 const qreal maxY = qMax(start.y(), end.y());
1326
1327 const qreal ndc[4] = {
1328 2.0f * minX / width() - 1.0f, // left
1329 2.0f * maxX / width() - 1.0f, // right
1330 // flip Y-coordinates
1331 1.0f - 2.0f * maxY / height(), // bottom
1332 1.0f - 2.0f * minY / height(), // top
1333 };
1334
1335 const float near = 0.0f;
1336 const float far = 1.0f;
1337 enum { L, R, B, T }; // left, right, bottom, top
1338 const QVector4D ndcCorners[8] = {
1339 // Near plane
1340 QVector4D(ndc[L], ndc[B], near, 1.0f), // 0: bottom-left | n0
1341 QVector4D(ndc[R], ndc[B], near, 1.0f), // 1: bottom-right | n1
1342 QVector4D(ndc[L], ndc[T], near, 1.0f), // 3: top-left | n2
1343 QVector4D(ndc[R], ndc[T], near, 1.0f), // 2: top-right | n3
1344 // Far plane
1345 QVector4D(ndc[L], ndc[B], far, 1.0f), // 4: bottom-left | f0
1346 QVector4D(ndc[R], ndc[B], far, 1.0f), // 5: bottom-right | f1
1347 QVector4D(ndc[L], ndc[T], far, 1.0f), // 7: top-left | f2
1348 QVector4D(ndc[R], ndc[T], far, 1.0f), // 6: top-right | f3
1349 };
1350
1351 QMatrix4x4 viewProjection;
1352 if (this->camera()) {
1353 if (auto camera = static_cast<QSSGRenderCamera *>(QQuick3DObjectPrivate::get(this->camera())->spatialNode))
1354 camera->calculateViewProjectionMatrix(camera->localTransform, camera->projection, viewProjection);
1355 }
1356 QMatrix4x4 viewProjectionInverted = viewProjection.inverted();
1357
1358 QVector3D worldCorners[8];
1359 for (int i = 0; i < 8; ++i) {
1360 QVector4D worldPoint = viewProjectionInverted * ndcCorners[i];
1361 worldCorners[i] = worldPoint.toVector3D() / worldPoint.w(); // perspective divide
1362 }
1363
1364 // Frustum
1365 //
1366 // f2 ------------f3
1367 // | |
1368 // | |
1369 // | |
1370 // | |
1371 // f0 ------------f1
1372 //
1373 // n2 -------- n3
1374 // | |
1375 // | camera |
1376 // | |
1377 // n0 -------- n1
1378 //
1379 // CCW winding if inside the frustum and looking toward the face
1380 const QSSGFrustum frustum {
1381 QSSGPlane(worldCorners[0], worldCorners[4], worldCorners[6]), // L [n0, f0, f2]
1382 QSSGPlane(worldCorners[1], worldCorners[3], worldCorners[7]), // R [n1, n3, f3]
1383 QSSGPlane(worldCorners[0], worldCorners[1], worldCorners[5]), // B [n0, n1, f1]
1384 QSSGPlane(worldCorners[2], worldCorners[6], worldCorners[7]), // T [n2, f2, f3]
1385 QSSGPlane(worldCorners[0], worldCorners[2], worldCorners[3]), // N [n0, n2, n3]
1386 QSSGPlane(worldCorners[5], worldCorners[7], worldCorners[6]), // F [f1, f3, f2]
1387 };
1388
1389 QList<QQuick3DObject *> ret;
1390 if (QQuick3DSceneRenderer *renderer = getRenderer()) {
1391 auto nodes = renderer->syncPickInFrustum(frustum);
1392 for (const auto *node : std::as_const(nodes)) {
1393 if (QQuick3DObject *m = findFrontendNode(node))
1394 ret.append(m);
1395 }
1396 }
1397
1398 return ret;
1399}
1400
1401void QQuick3DViewport::processPointerEventFromRay(const QVector3D &origin, const QVector3D &direction, QPointerEvent *event) const
1402{
1403 internalPick(event, origin, direction);
1404}
1405
1406// Note: we have enough information to implement Capability::Hover and Capability::ZPosition,
1407// but those properties are not currently available in QTouchEvent/QEventPoint
1408
1409namespace {
1410class SyntheticTouchDevice : public QPointingDevice
1411{
1412public:
1413 SyntheticTouchDevice(QObject *parent = nullptr)
1414 : QPointingDevice(QLatin1StringView("QtQuick3D Touch Synthesizer"),
1415 0,
1416 DeviceType::TouchScreen,
1417 PointerType::Finger,
1418 Capability::Position,
1419 10, 0,
1420 QString(), QPointingDeviceUniqueId(),
1421 parent)
1422 {
1423 }
1424};
1425}
1426
1427/*!
1428 \qmlmethod void View3D::setTouchpoint(Item target, point position, int pointId, bool pressed)
1429
1430 Sends a synthetic touch event to \a target, moving the touch point with ID \a pointId to \a position,
1431 with \a pressed determining if the point is pressed.
1432 Also sends the appropriate touch release event if \a pointId was previously active on a different
1433 item.
1434
1435 \since 6.8
1436 */
1437
1438void QQuick3DViewport::setTouchpoint(QQuickItem *target, const QPointF &position, int pointId, bool pressed)
1439{
1440 if (pointId >= m_touchState.size())
1441 m_touchState.resize(pointId + 1);
1442 auto prevState = m_touchState[pointId];
1443
1444 const bool sameTarget = prevState.target == target;
1445 const bool wasPressed = prevState.isPressed;
1446
1447 const bool isPress = pressed && (!sameTarget || !wasPressed);
1448 const bool isRelease = !pressed && wasPressed && sameTarget;
1449
1450 // Hover if we're not active, and we weren't previously active.
1451 // We assume that we always get a non-active for a target when we release.
1452 // This function sends a release events if the target is changed.
1453 if (!sameTarget && wasPressed)
1454 qWarning("QQuick3DViewport::setTouchpoint missing release event");
1455
1456 if (!pressed && !wasPressed) {
1457 // This would be a hover event: skipping
1458 return;
1459 }
1460
1461 m_touchState[pointId] = { target, position, pressed };
1462
1463 if (!m_syntheticTouchDevice)
1464 m_syntheticTouchDevice = new SyntheticTouchDevice(this);
1465
1466 QPointingDevicePrivate *devPriv = QPointingDevicePrivate::get(m_syntheticTouchDevice);
1467
1468 auto makePoint = [devPriv](int id, QEventPoint::State pointState, QPointF pos, quint64 timestamp) -> QEventPoint {
1469 auto epd = devPriv->pointById(id);
1470 auto &ep = epd->eventPoint;
1471 if (pointState != QEventPoint::State::Stationary)
1472 ep.setAccepted(false);
1473
1474 auto res = QMutableEventPoint::withTimeStamp(timestamp, id, pointState, pos, pos, pos);
1475 QMutableEventPoint::update(res, ep);
1476
1477 if (pointState == QEventPoint::State::Pressed)
1478 QMutableEventPoint::setGlobalPressPosition(res, pos);
1479 else if (ep.state() != QEventPoint::State::Unknown)
1480 QMutableEventPoint::setGlobalPressPosition(res, ep.globalPressPosition());
1481
1482 return res;
1483 };
1484
1485 auto sendTouchEvent = [&](QQuickItem *t, const QPointF &position, int pointId, QEventPoint::State pointState, quint64 timestamp) -> void {
1486 QList<QEventPoint> points;
1487 bool otherPoint = false; // Does the event have another point already?
1488 for (int i = 0; i < m_touchState.size(); ++i) {
1489 const auto &ts = m_touchState[i];
1490 if (ts.target != t)
1491 continue;
1492 if (i == pointId) {
1493 auto newPoint = makePoint(i, pointState, position, timestamp);
1494 points << newPoint;
1495 } else if (ts.isPressed) {
1496 otherPoint = true;
1497 points << makePoint(i, QEventPoint::Stationary, ts.position, timestamp);
1498 }
1499 }
1500
1501 QEvent::Type type;
1502 if (pointState == QEventPoint::Pressed && !otherPoint)
1503 type = QEvent::Type::TouchBegin;
1504 else if (pointState == QEventPoint::Released && !otherPoint)
1505 type = QEvent::Type::TouchEnd;
1506 else
1507 type = QEvent::Type::TouchUpdate;
1508
1509 QTouchEvent ev(type, m_syntheticTouchDevice, {}, points);
1510 ev.setTimestamp(timestamp);
1511
1512 if (t) {
1513 // Actually send event:
1514 auto da = QQuickItemPrivate::get(t)->deliveryAgent();
1515 bool handled = da->event(&ev);
1516 Q_UNUSED(handled);
1517 }
1518
1519 // Duplicate logic from QQuickWindowPrivate::clearGrabbers
1520 if (ev.isEndEvent()) {
1521 for (auto &point : ev.points()) {
1522 if (point.state() == QEventPoint::State::Released) {
1523 ev.setExclusiveGrabber(point, nullptr);
1524 ev.clearPassiveGrabbers(point);
1525 }
1526 }
1527 }
1528 };
1529
1530 auto timestamp = QDateTime::currentMSecsSinceEpoch();
1531
1532 // Send a release event to the previous target
1533 if (prevState.target && !sameTarget)
1534 sendTouchEvent(prevState.target, prevState.position, pointId, QEventPoint::Released, timestamp);
1535
1536 // Now send an event for the new state
1537 QEventPoint::State newState = isPress ? QEventPoint::Pressed : isRelease ? QEventPoint::Released : QEventPoint::Updated;
1538 sendTouchEvent(target, position, pointId, newState, timestamp);
1539}
1540
1541QQuick3DLightmapBaker *QQuick3DViewport::maybeLightmapBaker()
1542{
1543 return m_lightmapBaker;
1544}
1545
1546QQuick3DLightmapBaker *QQuick3DViewport::lightmapBaker()
1547{
1548 if (!m_lightmapBaker)
1549 m_lightmapBaker= new QQuick3DLightmapBaker(this);
1550
1551 return m_lightmapBaker;
1552}
1553
1554/*!
1555 \internal
1556*/
1557void QQuick3DViewport::bakeLightmap()
1558{
1559 QQuick3DSceneRenderer *renderer = getRenderer();
1560 if (!renderer || !renderer->m_layer->renderData)
1561 return;
1562
1563 const bool currentlyBaking = renderer->m_layer->renderData->lightmapBaker != nullptr;
1564
1565 if (!currentlyBaking)
1566 lightmapBaker()->bake();
1567}
1568
1569/*!
1570 \internal
1571*/
1572void QQuick3DViewport::denoiseLightmap()
1573{
1574 QQuick3DSceneRenderer *renderer = getRenderer();
1575 if (!renderer || !renderer->m_layer->renderData)
1576 return;
1577
1578 const bool currentlyBaking = renderer->m_layer->renderData->lightmapBaker != nullptr;
1579
1580 if (!currentlyBaking)
1581 lightmapBaker()->denoise();
1582}
1583
1584
1585void QQuick3DViewport::setGlobalPickingEnabled(bool isEnabled)
1586{
1587 QQuick3DSceneRenderer *renderer = getRenderer();
1588 if (!renderer)
1589 return;
1590
1591 renderer->setGlobalPickingEnabled(isEnabled);
1592}
1593
1594void QQuick3DViewport::invalidateSceneGraph()
1595{
1596 m_node = nullptr;
1597 m_renderNode = nullptr;
1598}
1599
1600QQuick3DSceneRenderer *QQuick3DViewport::getRenderer() const
1601{
1602 QQuick3DSceneRenderer *renderer = nullptr;
1603 if (m_node) {
1604 renderer = m_node->renderer;
1605 } else if (m_renderNode) {
1606 renderer = m_renderNode->renderer;
1607 } else if (m_directRenderer) {
1608 renderer = m_directRenderer->renderer();
1609 }
1610 return renderer;
1611}
1612
1613void QQuick3DViewport::updateDynamicTextures()
1614{
1615 // Update QSGDynamicTextures that are used for source textures and Quick items
1616 // Must be called on the render thread.
1617
1618 const auto &sceneManager = QQuick3DObjectPrivate::get(m_sceneRoot)->sceneManager;
1619 for (auto *texture : std::as_const(sceneManager->qsgDynamicTextures))
1620 texture->updateTexture();
1621
1622 QQuick3DNode *scene = m_importScene;
1623 while (scene) {
1624 const auto &importSm = QQuick3DObjectPrivate::get(scene)->sceneManager;
1625 if (importSm != sceneManager) {
1626 for (auto *texture : std::as_const(importSm->qsgDynamicTextures))
1627 texture->updateTexture();
1628 }
1629
1630 // if importScene has another import
1631 QQuick3DSceneRootNode *rn = qobject_cast<QQuick3DSceneRootNode *>(scene);
1632 scene = rn ? rn->view3D()->importScene() : nullptr;
1633 }
1634}
1635
1636QSGNode *QQuick3DViewport::setupOffscreenRenderer(QSGNode *node)
1637{
1638 SGFramebufferObjectNode *n = static_cast<SGFramebufferObjectNode *>(node);
1639
1640 if (!n) {
1641 if (!m_node)
1642 m_node = new SGFramebufferObjectNode;
1643 n = m_node;
1644 }
1645
1646 if (!n->renderer) {
1647 n->window = window();
1648 n->renderer = createRenderer();
1649 if (!n->renderer)
1650 return nullptr;
1651 n->renderer->fboNode = n;
1652 n->quickFbo = this;
1653 connect(window(), SIGNAL(screenChanged(QScreen*)), n, SLOT(handleScreenChange()));
1654 }
1655
1656 const qreal dpr = window()->effectiveDevicePixelRatio();
1657 const QSize minFboSize = QQuickItemPrivate::get(this)->sceneGraphContext()->minimumFBOSize();
1658 QSize desiredFboSize = QSize(m_explicitTextureWidth, m_explicitTextureHeight);
1659 if (desiredFboSize.isEmpty()) {
1660 desiredFboSize = QSize(width(), height()) * dpr;
1661 n->devicePixelRatio = dpr;
1662 // 1:1 mapping between the backing texture and the on-screen quad
1663 m_widthMultiplier = 1.0f;
1664 m_heightMultiplier = 1.0f;
1665 } else {
1666 QSize itemPixelSize = QSize(width(), height()) * dpr;
1667 // not 1:1 maping between the backing texture and the on-screen quad
1668 m_widthMultiplier = desiredFboSize.width() / float(itemPixelSize.width());
1669 m_heightMultiplier = desiredFboSize.height() / float(itemPixelSize.height());
1670 n->devicePixelRatio = 1.0;
1671 }
1672 desiredFboSize.setWidth(qMax(minFboSize.width(), desiredFboSize.width()));
1673 desiredFboSize.setHeight(qMax(minFboSize.height(), desiredFboSize.height()));
1674
1675 if (desiredFboSize != m_effectiveTextureSize) {
1676 m_effectiveTextureSize = desiredFboSize;
1677 emit effectiveTextureSizeChanged();
1678 }
1679
1680 n->setFiltering(smooth() ? QSGTexture::Linear : QSGTexture::Nearest);
1681 n->setRect(0, 0, width(), height());
1682 if (checkIsVisible() && isComponentComplete()) {
1683 n->renderer->synchronize(this, desiredFboSize, n->devicePixelRatio);
1684 if (n->renderer->m_textureNeedsFlip)
1685 n->setTextureCoordinatesTransform(QSGSimpleTextureNode::MirrorVertically);
1686 updateDynamicTextures();
1687 n->scheduleRender();
1688 }
1689
1690 return n;
1691}
1692
1693QSGNode *QQuick3DViewport::setupInlineRenderer(QSGNode *node)
1694{
1695 QQuick3DSGRenderNode *n = static_cast<QQuick3DSGRenderNode *>(node);
1696 if (!n) {
1697 if (!m_renderNode)
1698 m_renderNode = new QQuick3DSGRenderNode;
1699 n = m_renderNode;
1700 }
1701
1702 if (!n->renderer) {
1703 n->window = window();
1704 n->renderer = createRenderer();
1705 if (!n->renderer)
1706 return nullptr;
1707 }
1708
1709 if (!m_effectiveTextureSize.isEmpty()) {
1710 m_effectiveTextureSize = QSize();
1711 emit effectiveTextureSizeChanged();
1712 }
1713
1714 const QSize targetSize = window()->effectiveDevicePixelRatio() * QSize(width(), height());
1715
1716 // checkIsVisible, not isVisible, because, for example, a
1717 // { visible: false; layer.enabled: true } item still needs
1718 // to function normally.
1719 if (checkIsVisible() && isComponentComplete()) {
1720 n->renderer->synchronize(this, targetSize, window()->effectiveDevicePixelRatio());
1721 updateDynamicTextures();
1722 n->markDirty(QSGNode::DirtyMaterial);
1723 }
1724
1725 return n;
1726}
1727
1728
1729void QQuick3DViewport::setupDirectRenderer(RenderMode mode)
1730{
1731 auto renderMode = (mode == Underlay) ? QQuick3DSGDirectRenderer::Underlay
1732 : QQuick3DSGDirectRenderer::Overlay;
1733 if (!m_directRenderer) {
1734 QQuick3DSceneRenderer *sceneRenderer = createRenderer();
1735 if (!sceneRenderer)
1736 return;
1737 m_directRenderer = new QQuick3DSGDirectRenderer(sceneRenderer, window(), renderMode);
1738 connect(window(), &QQuickWindow::sceneGraphInvalidated, this, &QQuick3DViewport::cleanupDirectRenderer, Qt::DirectConnection);
1739 }
1740
1741 if (!m_effectiveTextureSize.isEmpty()) {
1742 m_effectiveTextureSize = QSize();
1743 emit effectiveTextureSizeChanged();
1744 }
1745
1746 const QSizeF targetSize = window()->effectiveDevicePixelRatio() * QSizeF(width(), height());
1747 m_directRenderer->setViewport(QRectF(window()->effectiveDevicePixelRatio() * mapToScene(QPointF(0, 0)), targetSize));
1748 m_directRenderer->setVisibility(isVisible());
1749 if (isVisible()) {
1750 m_directRenderer->preSynchronize();
1751 m_directRenderer->renderer()->synchronize(this, targetSize.toSize(), window()->effectiveDevicePixelRatio());
1752 updateDynamicTextures();
1753 m_directRenderer->requestRender();
1754 }
1755}
1756
1757// This is used for offscreen mode since we need to check if
1758// this item is used by an effect but hidden
1759bool QQuick3DViewport::checkIsVisible() const
1760{
1761 auto childPrivate = QQuickItemPrivate::get(this);
1762 return (childPrivate->explicitVisible ||
1763 (childPrivate->extra.isAllocated() && childPrivate->extra->effectRefCount));
1764
1765}
1766
1767/*!
1768 \internal
1769
1770 This method processes a pickResult on an object with the objective of checking
1771 if there are any QQuickItem based subscenes that the QPointerEvent needs to be
1772 forwarded to (3D -> 2D). If such a subscene is found, the event will be mapped
1773 to the correct cordinate system, and added to the visitedSubscenes map for later
1774 event delivery.
1775*/
1776void QQuick3DViewport::processPickedObject(const QSSGRenderPickResult &pickResult,
1777 int pointIndex,
1778 QPointerEvent *event,
1779 QFlatMap<QQuickItem *, SubsceneInfo> &visitedSubscenes) const
1780{
1781 QQuickItem *subsceneRootItem = nullptr;
1782 QPointF subscenePosition;
1783 const auto backendObject = pickResult.m_hitObject;
1784 const auto frontendObject = findFrontendNode(backendObject);
1785 if (!frontendObject)
1786 return;
1787
1788 // Figure out if there are any QQuickItem based scenes we need to forward
1789 // the event to, and if the are found, determine how to translate the UV Coords
1790 // in the pickResult based on what type the object containing the scene is.
1791 auto frontendObjectPrivate = QQuick3DObjectPrivate::get(frontendObject);
1792 if (frontendObjectPrivate->type == QQuick3DObjectPrivate::Type::Item2D) {
1793 // Item2D, this is the case where there is just an embedded Qt Quick 2D Item
1794 // rendered directly to the scene.
1795 auto item2D = qobject_cast<QQuick3DItem2D *>(frontendObject);
1796 if (item2D)
1797 subsceneRootItem = item2D->contentItem();
1798 if (!subsceneRootItem || subsceneRootItem->childItems().isEmpty())
1799 return; // ignore empty 2D subscenes
1800
1801 // In this case the "UV" coordinates are in pixels in the subscene root item's coordinate system.
1802 subscenePosition = pickResult.m_localUVCoords.toPointF();
1803
1804 // The following code will account for custom input masking, as well any
1805 // transformations that might have been applied to the Item
1806 if (!subsceneRootItem->childAt(subscenePosition.x(), subscenePosition.y()))
1807 return;
1808 } else if (frontendObjectPrivate->type == QQuick3DObjectPrivate::Type::Model) {
1809 // Model
1810 int materialSubset = pickResult.m_subset;
1811 const auto backendModel = static_cast<const QSSGRenderModel *>(backendObject);
1812 // Get material
1813 if (backendModel->materials.size() < (pickResult.m_subset + 1))
1814 materialSubset = backendModel->materials.size() - 1;
1815 if (materialSubset < 0)
1816 return;
1817 const auto backendMaterial = backendModel->materials.at(materialSubset);
1818 const auto frontendMaterial = static_cast<QQuick3DMaterial*>(findFrontendNode(backendMaterial));
1819 subsceneRootItem = getSubSceneRootItem(frontendMaterial);
1820
1821 if (subsceneRootItem) {
1822 // In this case the pick result really is using UV coordinates.
1823 subscenePosition = QPointF(subsceneRootItem->x() + pickResult.m_localUVCoords.x() * subsceneRootItem->width(),
1824 subsceneRootItem->y() - pickResult.m_localUVCoords.y() * subsceneRootItem->height() + subsceneRootItem->height());
1825 }
1826 }
1827
1828 // Add the new event (item and position) to the visitedSubscene map.
1829 if (subsceneRootItem) {
1830 SubsceneInfo &subscene = visitedSubscenes[subsceneRootItem]; // create if not found
1831 subscene.obj = frontendObject;
1832 if (subscene.eventPointScenePositions.size() != event->pointCount()) {
1833 // ensure capacity, and use an out-of-scene position rather than 0,0 by default
1834 constexpr QPointF inf(-qt_inf(), -qt_inf());
1835 subscene.eventPointScenePositions.resize(event->pointCount(), inf);
1836 }
1837 subscene.eventPointScenePositions[pointIndex] = subscenePosition;
1838 }
1839}
1840
1841/*!
1842 \internal
1843
1844 This method will try and find a QQuickItem based by looking for sourceItem()
1845 properties on the primary color channels for the various material types.
1846
1847 \note for CustomMaterial there is just a best effort given where the first
1848 associated Texture with a sourceItem property is used.
1849*/
1850
1851QQuickItem *QQuick3DViewport::getSubSceneRootItem(QQuick3DMaterial *material) const
1852{
1853 if (!material)
1854 return nullptr;
1855
1856 QQuickItem *subsceneRootItem = nullptr;
1857 const auto frontendMaterialPrivate = QQuick3DObjectPrivate::get(material);
1858
1859 if (frontendMaterialPrivate->type == QQuick3DObjectPrivate::Type::DefaultMaterial) {
1860 // Default Material
1861 const auto defaultMaterial = qobject_cast<QQuick3DDefaultMaterial *>(material);
1862 if (defaultMaterial) {
1863 // Just check for a diffuseMap for now
1864 if (defaultMaterial->diffuseMap() && defaultMaterial->diffuseMap()->sourceItem())
1865 subsceneRootItem = defaultMaterial->diffuseMap()->sourceItem();
1866 }
1867
1868 } else if (frontendMaterialPrivate->type == QQuick3DObjectPrivate::Type::PrincipledMaterial) {
1869 // Principled Material
1870 const auto principledMaterial = qobject_cast<QQuick3DPrincipledMaterial *>(material);
1871 if (principledMaterial) {
1872 // Just check for a baseColorMap for now
1873 if (principledMaterial->baseColorMap() && principledMaterial->baseColorMap()->sourceItem())
1874 subsceneRootItem = principledMaterial->baseColorMap()->sourceItem();
1875 }
1876 } else if (frontendMaterialPrivate->type == QQuick3DObjectPrivate::Type::SpecularGlossyMaterial) {
1877 // SpecularGlossy Material
1878 const auto specularGlossyMaterial = qobject_cast<QQuick3DSpecularGlossyMaterial *>(material);
1879 if (specularGlossyMaterial) {
1880 // Just check for a albedoMap for now
1881 if (specularGlossyMaterial->albedoMap() && specularGlossyMaterial->albedoMap()->sourceItem())
1882 subsceneRootItem = specularGlossyMaterial->albedoMap()->sourceItem();
1883 }
1884 } else if (frontendMaterialPrivate->type == QQuick3DObjectPrivate::Type::CustomMaterial) {
1885 // Custom Material
1886 const auto customMaterial = qobject_cast<QQuick3DCustomMaterial *>(material);
1887 if (customMaterial) {
1888 // This case is a bit harder because we can not know how the textures will be used
1889 const auto &texturesInputs = customMaterial->m_dynamicTextureMaps;
1890 for (const auto &textureInput : texturesInputs) {
1891 if (auto texture = textureInput->texture()) {
1892 if (texture->sourceItem()) {
1893 subsceneRootItem = texture->sourceItem();
1894 break;
1895 }
1896 }
1897 }
1898 }
1899 }
1900 return subsceneRootItem;
1901}
1902
1903
1904/*!
1905 \internal
1906*/
1907QQuick3DPickResult QQuick3DViewport::getNearestPickResult(const QVarLengthArray<QSSGRenderPickResult, 20> &pickResults) const
1908{
1909 for (const auto &result : pickResults) {
1910 auto pickResult = processPickResult(result);
1911 if (pickResult.hitType() != QQuick3DPickResultEnums::HitType::Null)
1912 return pickResult;
1913 }
1914
1915 return QQuick3DPickResult();
1916}
1917
1918/*!
1919 \internal
1920 This method is responsible for going through the visitedSubscenes map and forwarding
1921 the event with corrected coordinates to each subscene.
1922
1923*/
1924bool QQuick3DViewport::forwardEventToSubscenes(QPointerEvent *event,
1925 bool useRayPicking,
1926 QQuick3DSceneRenderer *renderer,
1927 const QFlatMap<QQuickItem *, SubsceneInfo> &visitedSubscenes) const
1928{
1929 // Now deliver the entire event (all points) to each relevant subscene.
1930 // Maybe only some points fall inside, but QQuickDeliveryAgentPrivate::deliverMatchingPointsToItem()
1931 // makes reduced-subset touch events that contain only the relevant points, when necessary.
1932 bool ret = false;
1933
1934 QVarLengthArray<QPointF, 16> originalScenePositions;
1935 originalScenePositions.resize(event->pointCount());
1936 for (int pointIndex = 0; pointIndex < event->pointCount(); ++pointIndex)
1937 originalScenePositions[pointIndex] = event->point(pointIndex).scenePosition();
1938 for (const auto &subscene : visitedSubscenes) {
1939 QQuickItem *subsceneRoot = subscene.first;
1940 const auto &subsceneInfo = subscene.second;
1941 Q_ASSERT(subsceneInfo.eventPointScenePositions.size() == event->pointCount());
1942 auto da = QQuickItemPrivate::get(subsceneRoot)->deliveryAgent();
1943 for (int pointIndex = 0; pointIndex < event->pointCount(); ++pointIndex) {
1944 const auto &pt = subsceneInfo.eventPointScenePositions.at(pointIndex);
1945 // By tradition, QGuiApplicationPrivate::processTouchEvent() has set the local position to the scene position,
1946 // and Qt Quick expects it to arrive that way: then QQuickDeliveryAgentPrivate::translateTouchEvent()
1947 // copies it into the scene position before localizing.
1948 // That might be silly, we might change it eventually, but gotta stay consistent for now.
1949 QEventPoint &ep = event->point(pointIndex);
1950 QMutableEventPoint::setPosition(ep, pt);
1951 QMutableEventPoint::setScenePosition(ep, pt);
1952 }
1953
1954 if (event->isBeginEvent())
1955 da->setSceneTransform(nullptr);
1956 if (da->event(event)) {
1957 ret = true;
1958 if (QQuickDeliveryAgentPrivate::anyPointGrabbed(event) && !useRayPicking) {
1959 // In case any QEventPoint was grabbed, the relevant QQuickDeliveryAgent needs to know
1960 // how to repeat the picking/coordinate transformation for each update,
1961 // because delivery will bypass internalPick() due to the grab, and it's
1962 // more efficient to avoid whole-scene picking each time anyway.
1963 auto frontendObjectPrivate = QQuick3DObjectPrivate::get(subsceneInfo.obj);
1964 const bool item2Dcase = (frontendObjectPrivate->type == QQuick3DObjectPrivate::Type::Item2D);
1965 ViewportTransformHelper *transform = new ViewportTransformHelper;
1966 transform->viewport = const_cast<QQuick3DViewport *>(this);
1967 transform->renderer = renderer;
1968 transform->sceneParentNode = static_cast<QSSGRenderNode*>(frontendObjectPrivate->spatialNode);
1969 transform->targetItem = subsceneRoot;
1970 transform->scaleX = window()->effectiveDevicePixelRatio() * m_widthMultiplier;
1971 transform->scaleY = window()->effectiveDevicePixelRatio() * m_heightMultiplier;
1972 transform->uvCoordsArePixels = item2Dcase;
1973 transform->setOnDeliveryAgent(da);
1974 qCDebug(lcPick) << event->type() << "created ViewportTransformHelper on" << da;
1975 }
1976 } else if (event->type() != QEvent::HoverMove) {
1977 qCDebug(lcPick) << subsceneRoot << "didn't want" << event;
1978 }
1979 event->setAccepted(false); // reject implicit grab and let it keep propagating
1980 }
1981 if (visitedSubscenes.isEmpty()) {
1982 event->setAccepted(false);
1983 } else {
1984 for (int pointIndex = 0; pointIndex < event->pointCount(); ++pointIndex)
1985 QMutableEventPoint::setScenePosition(event->point(pointIndex), originalScenePositions.at(pointIndex));
1986 }
1987
1988 // Normally this would occur in QQuickWindowPrivate::clearGrabbers(...) but
1989 // for ray based input, input never goes through QQuickWindow (since events
1990 // are generated from within scene space and not window/screen space).
1991 if (event->isEndEvent() && useRayPicking) {
1992 if (event->isSinglePointEvent()) {
1993 if (static_cast<QSinglePointEvent *>(event)->buttons() == Qt::NoButton) {
1994 auto &firstPt = event->point(0);
1995 event->setExclusiveGrabber(firstPt, nullptr);
1996 event->clearPassiveGrabbers(firstPt);
1997 }
1998 } else {
1999 for (auto &point : event->points()) {
2000 if (point.state() == QEventPoint::State::Released) {
2001 event->setExclusiveGrabber(point, nullptr);
2002 event->clearPassiveGrabbers(point);
2003 }
2004 }
2005 }
2006 }
2007
2008 return ret;
2009}
2010
2011
2012bool QQuick3DViewport::internalPick(QPointerEvent *event, const QVector3D &origin, const QVector3D &direction) const
2013{
2014 QQuick3DSceneRenderer *renderer = getRenderer();
2015 if (!renderer || !event)
2016 return false;
2017
2018 QFlatMap<QQuickItem*, SubsceneInfo> visitedSubscenes;
2019 const bool useRayPicking = !direction.isNull();
2020
2021 for (int pointIndex = 0; pointIndex < event->pointCount(); ++pointIndex) {
2022 auto &eventPoint = event->point(pointIndex);
2023 QVarLengthArray<QSSGRenderPickResult, 20> pickResults;
2024 if (Q_UNLIKELY(useRayPicking))
2025 pickResults = getPickResults(renderer, origin, direction);
2026 else
2027 pickResults = getPickResults(renderer, eventPoint);
2028
2029 if (!pickResults.isEmpty())
2030 for (const auto &pickResult : pickResults)
2031 processPickedObject(pickResult, pointIndex, event, visitedSubscenes);
2032 else
2033 eventPoint.setAccepted(false); // let it fall through the viewport to Items underneath
2034 }
2035
2036 return forwardEventToSubscenes(event, useRayPicking, renderer, visitedSubscenes);
2037}
2038
2039bool QQuick3DViewport::singlePointPick(QSinglePointEvent *event, const QVector3D &origin, const QVector3D &direction)
2040{
2041 QQuick3DSceneRenderer *renderer = getRenderer();
2042 if (!renderer || !event)
2043 return false;
2044
2045 QSSGRenderRay ray(origin, direction);
2046
2047 Q_ASSERT(event->pointCount() == 1);
2048 QPointF originalPosition = event->point(0).scenePosition();
2049
2050 auto pickResults = renderer->syncPickAll(ray);
2051
2052 bool delivered = false;
2053
2054 constexpr float jitterLimit = 2.5; // TODO: add property for this?
2055 bool withinJitterLimit = false;
2056
2057 for (const auto &pickResult : pickResults) {
2058 auto [item, position] = getItemAndPosition(pickResult);
2059 if (!item)
2060 break;
2061 if (item == m_prevMouseItem && (position - m_prevMousePos).manhattanLength() < jitterLimit && !event->button()) {
2062 withinJitterLimit = true;
2063 break;
2064 }
2065 auto da = QQuickItemPrivate::get(item)->deliveryAgent();
2066 QEventPoint &ep = event->point(0);
2067 QMutableEventPoint::setPosition(ep, position);
2068 QMutableEventPoint::setScenePosition(ep, position);
2069 if (da->event(event)) {
2070 delivered = true;
2071 if (event->type() == QEvent::MouseButtonPress) {
2072 m_prevMouseItem = item;
2073 m_prevMousePos = position;
2074 withinJitterLimit = true;
2075 }
2076 break;
2077 }
2078 }
2079
2080 QMutableEventPoint::setScenePosition(event->point(0), originalPosition);
2081 if (!withinJitterLimit)
2082 m_prevMouseItem = nullptr;
2083
2084 // Normally this would occur in QQuickWindowPrivate::clearGrabbers(...) but
2085 // for ray based input, input never goes through QQuickWindow (since events
2086 // are generated from within scene space and not window/screen space).
2087 if (event->isEndEvent()) {
2088 if (event->buttons() == Qt::NoButton) {
2089 auto &firstPt = event->point(0);
2090 event->setExclusiveGrabber(firstPt, nullptr);
2091 event->clearPassiveGrabbers(firstPt);
2092 }
2093 }
2094
2095 return delivered;
2096}
2097
2098QPair<QQuickItem *, QPointF> QQuick3DViewport::getItemAndPosition(const QSSGRenderPickResult &pickResult) const
2099{
2100 QQuickItem *subsceneRootItem = nullptr;
2101 QPointF subscenePosition;
2102 const auto backendObject = pickResult.m_hitObject;
2103 const auto frontendObject = findFrontendNode(backendObject);
2104 if (!frontendObject)
2105 return {};
2106 auto frontendObjectPrivate = QQuick3DObjectPrivate::get(frontendObject);
2107 if (frontendObjectPrivate->type == QQuick3DObjectPrivate::Type::Item2D) {
2108 // Item2D, this is the case where there is just an embedded Qt Quick 2D Item
2109 // rendered directly to the scene.
2110 auto item2D = qobject_cast<QQuick3DItem2D *>(frontendObject);
2111 if (item2D)
2112 subsceneRootItem = item2D->contentItem();
2113 if (!subsceneRootItem || subsceneRootItem->childItems().isEmpty())
2114 return {}; // ignore empty 2D subscenes
2115
2116 // In this case the "UV" coordinates are in pixels in the subscene root item's coordinate system.
2117 subscenePosition = pickResult.m_localUVCoords.toPointF();
2118
2119 // The following code will account for custom input masking, as well any
2120 // transformations that might have been applied to the Item
2121 if (!subsceneRootItem->childAt(subscenePosition.x(), subscenePosition.y()))
2122 return {};
2123 } else if (frontendObjectPrivate->type == QQuick3DObjectPrivate::Type::Model) {
2124 // Model
2125 int materialSubset = pickResult.m_subset;
2126 const auto backendModel = static_cast<const QSSGRenderModel *>(backendObject);
2127 // Get material
2128 if (backendModel->materials.size() < (pickResult.m_subset + 1))
2129 materialSubset = backendModel->materials.size() - 1;
2130 if (materialSubset < 0)
2131 return {};
2132 const auto backendMaterial = backendModel->materials.at(materialSubset);
2133 const auto frontendMaterial = static_cast<QQuick3DMaterial *>(findFrontendNode(backendMaterial));
2134 subsceneRootItem = getSubSceneRootItem(frontendMaterial);
2135
2136 if (subsceneRootItem) {
2137 // In this case the pick result really is using UV coordinates.
2138 subscenePosition = QPointF(subsceneRootItem->x() + pickResult.m_localUVCoords.x() * subsceneRootItem->width(),
2139 subsceneRootItem->y() - pickResult.m_localUVCoords.y() * subsceneRootItem->height() + subsceneRootItem->height());
2140 }
2141 }
2142 return {subsceneRootItem, subscenePosition};
2143}
2144
2145QVarLengthArray<QSSGRenderPickResult, 20> QQuick3DViewport::getPickResults(QQuick3DSceneRenderer *renderer,
2146 const QVector3D &origin,
2147 const QVector3D &direction) const
2148{
2149 const QSSGRenderRay ray(origin, direction);
2150 return renderer->syncPickAll(ray);
2151}
2152
2153QVarLengthArray<QSSGRenderPickResult, 20> QQuick3DViewport::getPickResults(QQuick3DSceneRenderer *renderer, const QEventPoint &eventPoint) const
2154{
2155 QVarLengthArray<QSSGRenderPickResult, 20> pickResults;
2156 QPointF realPosition = eventPoint.position() * window()->effectiveDevicePixelRatio();
2157 // correct when mapping is not 1:1 due to explicit backing texture size
2158 realPosition.rx() *= m_widthMultiplier;
2159 realPosition.ry() *= m_heightMultiplier;
2160 std::optional<QSSGRenderRay> rayResult = renderer->getRayFromViewportPos(realPosition);
2161 if (rayResult.has_value())
2162 pickResults = renderer->syncPickAll(rayResult.value());
2163 return pickResults;
2164}
2165
2166/*!
2167 \internal
2168
2169 This provides a way to lookup frontendNodes with a backend node taking into consideration both
2170 the scene and the importScene
2171*/
2172QQuick3DObject *QQuick3DViewport::findFrontendNode(const QSSGRenderGraphObject *backendObject) const
2173{
2174 if (!backendObject)
2175 return nullptr;
2176
2177 const auto sceneManager = QQuick3DObjectPrivate::get(m_sceneRoot)->sceneManager;
2178 QQuick3DObject *frontendObject = sceneManager->lookUpNode(backendObject);
2179 if (!frontendObject && m_importScene) {
2180 const auto importSceneManager = QQuick3DObjectPrivate::get(m_importScene)->sceneManager;
2181 frontendObject = importSceneManager->lookUpNode(backendObject);
2182 }
2183 return frontendObject;
2184}
2185
2186QQuick3DPickResult QQuick3DViewport::processPickResult(const QSSGRenderPickResult &pickResult) const
2187{
2188 if (!pickResult.m_hitObject)
2189 return QQuick3DPickResult();
2190
2191 QQuick3DObject *frontendObject = findFrontendNode(pickResult.m_hitObject);
2192
2193 QQuick3DModel *model = qobject_cast<QQuick3DModel *>(frontendObject);
2194 if (model) {
2195 auto itemAndPosition = getItemAndPosition(pickResult);
2196 return QQuick3DPickResult(model,
2197 ::sqrtf(pickResult.m_distanceSq),
2198 pickResult.m_localUVCoords,
2199 pickResult.m_scenePosition,
2200 pickResult.m_localPosition,
2201 pickResult.m_faceNormal,
2202 pickResult.m_sceneNormal,
2203 pickResult.m_instanceIndex,
2204 itemAndPosition.first);
2205 }
2206
2207 QQuick3DItem2D *frontend2DItem = qobject_cast<QQuick3DItem2D *>(frontendObject);
2208 if (frontend2DItem && frontend2DItem->contentItem()) {
2209 // Check if the pick is inside the content item (since the ray just intersected on the items plane)
2210 const QPointF subscenePosition = pickResult.m_localUVCoords.toPointF();
2211 const auto child = frontend2DItem->contentItem()->childAt(subscenePosition.x(), subscenePosition.y());
2212 if (child) {
2213 return QQuick3DPickResult(child,
2214 ::sqrtf(pickResult.m_distanceSq),
2215 QVector2D(frontend2DItem->contentItem()->mapToItem(child, subscenePosition)),
2216 pickResult.m_scenePosition,
2217 pickResult.m_localPosition,
2218 pickResult.m_faceNormal);
2219 }
2220 }
2221
2222 return QQuick3DPickResult();
2223
2224}
2225
2226// Returns the first found scene manager of objects children
2227QQuick3DSceneManager *QQuick3DViewport::findChildSceneManager(QQuick3DObject *inObject, QQuick3DSceneManager *manager)
2228{
2229 if (manager)
2230 return manager;
2231
2232 auto children = QQuick3DObjectPrivate::get(inObject)->childItems;
2233 for (auto *child : std::as_const(children)) {
2234 if (auto m = QQuick3DObjectPrivate::get(child)->sceneManager) {
2235 manager = m;
2236 break;
2237 }
2238 manager = findChildSceneManager(child, manager);
2239 }
2240 return manager;
2241}
2242
2243void QQuick3DViewport::updateInputProcessing()
2244{
2245 // This should be called from the gui thread.
2246 setAcceptTouchEvents(m_enableInputProcessing);
2247 setAcceptHoverEvents(m_enableInputProcessing);
2248 setAcceptedMouseButtons(m_enableInputProcessing ? Qt::AllButtons : Qt::NoButton);
2249}
2250
2251void QQuick3DViewport::onReleaseCachedResources()
2252{
2253 if (auto renderer = getRenderer())
2254 renderer->releaseCachedResources();
2255}
2256
2257/*!
2258 \qmlproperty List<QtQuick3D::Object3D> View3D::extensions
2259
2260 This property contains a list of user extensions that should be used with this \l View3D.
2261
2262 \sa RenderExtension
2263*/
2264QQmlListProperty<QQuick3DObject> QQuick3DViewport::extensions()
2265{
2266 return QQmlListProperty<QQuick3DObject>{ this,
2267 &m_extensionListDirty,
2268 &QQuick3DExtensionListHelper::extensionAppend,
2269 &QQuick3DExtensionListHelper::extensionCount,
2270 &QQuick3DExtensionListHelper::extensionAt,
2271 &QQuick3DExtensionListHelper::extensionClear,
2272 &QQuick3DExtensionListHelper::extensionReplace,
2273 &QQuick3DExtensionListHelper::extensionRemoveLast};
2274}
2275
2276/*!
2277 \internal
2278 */
2279void QQuick3DViewport::rebuildExtensionList()
2280{
2281 m_extensionListDirty = true;
2282 update();
2283}
2284
2285/*!
2286 \internal
2287
2288 Private constructor for the QQuick3DViewport class so we can differentiate between
2289 a regular QQuick3DViewport and one created for a specific usage, like XR.
2290 */
2291QQuick3DViewport::QQuick3DViewport(PrivateInstanceType type, QQuickItem *parent)
2292 : QQuick3DViewport(parent)
2293{
2294 m_isXrViewInstance = type == PrivateInstanceType::XrViewInstance;
2295}
2296
2297void QQuick3DViewport::updateCameraForLayer(const QQuick3DViewport &view3D, QSSGRenderLayer &layerNode)
2298{
2299 layerNode.explicitCameras.clear();
2300 if (!view3D.m_multiViewCameras.isEmpty()) {
2301 for (QQuick3DCamera *camera : std::as_const(view3D.m_multiViewCameras))
2302 layerNode.explicitCameras.append(static_cast<QSSGRenderCamera *>(QQuick3DObjectPrivate::get(camera)->spatialNode));
2303 } else if (view3D.camera()) {
2304 if (QSSGRenderCamera *camera = static_cast<QSSGRenderCamera *>(QQuick3DObjectPrivate::get(view3D.camera())->spatialNode))
2305 layerNode.explicitCameras.append(camera);
2306 }
2307
2308 // Ensure these have a parent. All nodes need to be in the node tree somewhere, even if they're technically "parentless"
2309 // or we'll not assign a storage slot for them or update them.
2310 for (QSSGRenderCamera *camera : std::as_const(layerNode.explicitCameras)) {
2311 if (!camera->parent)
2312 layerNode.addChild(*camera);
2313 }
2314}
2315
2316void QQuick3DViewport::updateSceneManagerForImportScene()
2317{
2318 auto privateObject = QQuick3DObjectPrivate::get(m_importScene);
2319 if (!privateObject->sceneManager) {
2320 // If object doesn't already have scene manager, check from its children
2321 QQuick3DSceneManager *manager = findChildSceneManager(m_importScene);
2322 // If still not found, use the one from the scene root (scenes defined outside of an view3d)
2323 if (!manager)
2324 manager = QQuick3DObjectPrivate::get(m_sceneRoot)->sceneManager;
2325 if (manager) {
2326 manager->setWindow(window());
2327 privateObject->refSceneManager(*manager);
2328 }
2329
2330 // In the case m_sceneRoot doesn't have a valid sceneManager,
2331 // it means the view3d is not valid now.
2332 if (!privateObject->sceneManager)
2333 return;
2334
2335 }
2336 connect(privateObject->sceneManager, &QQuick3DSceneManager::needsUpdate,
2337 this, &QQuickItem::update, Qt::UniqueConnection);
2338 connect(privateObject->sceneManager, &QObject::destroyed,
2339 this, [&](QObject *) {
2340 auto privateObject = QQuick3DObjectPrivate::get(m_importScene);
2341 privateObject->sceneManager = nullptr;
2342 updateSceneManagerForImportScene();
2343 }, Qt::DirectConnection);
2344
2345 QQuick3DNode *scene = m_importScene;
2346 while (scene) {
2347 QQuick3DSceneRootNode *rn = qobject_cast<QQuick3DSceneRootNode *>(scene);
2348 scene = rn ? rn->view3D()->importScene() : nullptr;
2349
2350 if (scene) {
2351 connect(QQuick3DObjectPrivate::get(scene)->sceneManager,
2352 &QQuick3DSceneManager::needsUpdate,
2353 this, &QQuickItem::update, Qt::UniqueConnection);
2354 }
2355 }
2356}
2357
2358/*!
2359 \qmlproperty enumeration QtQuick3D::View3D::renderOverrides
2360
2361 Controls how Qt Quick 3D performs rendering.
2362
2363 \value View3D.None Rendering proceeds normally. Internal passes are scheduled as required by the
2364 features used in the application.
2365
2366 \value View3D.DisableInternalPasses Disables Qt Quick 3D's internal rendering passes.
2367 When this mode is set, the application is responsible for producing and presenting the final frame.
2368 Typically, this involves implementing custom \l {User-Defined Render Passes in Qt Quick 3D}
2369 {user passes} and presenting the result to the viewport using \l {SimpleQuadRenderer} or a
2370 custom \l {QQuick3DRenderExtension}{render extension}.
2371*/
2372
2373QQuick3DViewport::RenderOverrides QQuick3DViewport::renderOverrides() const
2374{
2375 return m_renderOverrides;
2376}
2377
2378void QQuick3DViewport::setRenderOverrides(RenderOverrides newRenderOverrides)
2379{
2380 if (m_renderOverrides == newRenderOverrides)
2381 return;
2382 m_renderOverrides = newRenderOverrides;
2383 emit renderOverridesChanged();
2384
2385 update();
2386}
2387
2388QT_END_NAMESPACE
void run() override
Implement this pure virtual function in your subclass.
CleanupJob(QQuick3DSGDirectRenderer *renderer)
static void extensionClear(QQmlListProperty< QQuick3DObject > *list)
static void extensionReplace(QQmlListProperty< QQuick3DObject > *list, qsizetype idx, QQuick3DObject *o)
static void extensionAppend(QQmlListProperty< QQuick3DObject > *list, QQuick3DObject *extension)
static QQuick3DObject * extensionAt(QQmlListProperty< QQuick3DObject > *list, qsizetype index)
static void extensionRemoveLast(QQmlListProperty< QQuick3DObject > *list)
static qsizetype extensionCount(QQmlListProperty< QQuick3DObject > *list)
Combined button and popup list for selecting options.
static qsizetype ssgn_count(QQmlListProperty< QObject > *property)
static QObject * ssgn_at(QQmlListProperty< QObject > *property, qsizetype i)
static void ssgn_append(QQmlListProperty< QObject > *property, QObject *obj)
static void ssgn_clear(QQmlListProperty< QObject > *property)
QPointF map(const QPointF &viewportPoint) override
QQuick3DSceneRenderer * renderer
void setOnDeliveryAgent(QQuickDeliveryAgent *da)
static QList< QPointer< QQuickDeliveryAgent > > owners
QPointer< QQuick3DViewport > viewport
QPointer< QQuickItem > targetItem
QSSGRenderNode * sceneParentNode