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