1// Copyright (C) 2019 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qquick3dviewport_p.h"
5#include "qquick3dsceneenvironment_p.h"
6#include "qquick3dscenemanager_p.h"
7#include "qquick3dtexture_p.h"
8#include "qquick3dscenerenderer_p.h"
9#include "qquick3dscenerootnode_p.h"
10#include "qquick3dcamera_p.h"
11#include "qquick3dmodel_p.h"
12#include "qquick3drenderstats_p.h"
13#include "qquick3ditem2d_p.h"
14#include "qquick3ddefaultmaterial_p.h"
15#include "qquick3dprincipledmaterial_p.h"
16#include "qquick3dcustommaterial_p.h"
17#include "qquick3dspecularglossymaterial_p.h"
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
40QT_BEGIN_NAMESPACE
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(varName: "QT_QUICK3D_FORCE_INPUT_HANDLING") > 0);
48 return v;
49}
50
51struct ViewportTransformHelper : public QQuickDeliveryAgent::Transform
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(t: 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(point: viewportPoint);
79 point.rx() *= scaleX;
80 point.ry() *= scaleY;
81 std::optional<QSSGRenderRay> rayResult = renderer->getRayFromViewportPos(pos: point);
82 if (rayResult.has_value()) {
83 const auto pickResults = renderer->syncPickOne(ray: rayResult.value(), node: 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(v: 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
105 QPointer<QQuick3DViewport> viewport;
106 QQuick3DSceneRenderer *renderer = nullptr;
107 QSSGRenderNode *sceneParentNode = nullptr;
108 QPointer<QQuickItem> targetItem;
109 qreal scaleX = 1;
110 qreal scaleY = 1;
111 bool uvCoordsArePixels = false; // if false, they are in the range 0..1
112 QPointF lastGoodMapping;
113
114 static QList<QPointer<QQuickDeliveryAgent>> owners;
115};
116
117QList<QPointer<QQuickDeliveryAgent>> ViewportTransformHelper::owners;
118
119class QQuick3DExtensionListHelper
120{
121 Q_DISABLE_COPY_MOVE(QQuick3DExtensionListHelper);
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 *>(object: list->object)) {
128 if (const auto idx = that->m_extensions.indexOf(t: extension); idx == -1) {
129 if (!extension->parentItem())
130 extension->setParentItem(that->m_sceneRoot);
131 that->m_extensions.push_back(t: 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 *>(object: list->object)) {
142 if (that->m_extensions.size() > index)
143 ret = that->m_extensions.at(i: 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 *>(object: 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 *>(object: 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 *>(object: list->object)) {
172 if (that->m_extensions.size() > idx && idx > -1) {
173 that->m_extensions.replace(i: idx, t: 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 *>(object: 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(flag: ItemHasContents);
247 m_camera = nullptr;
248 m_sceneRoot = new QQuick3DSceneRootNode(this);
249 m_renderStats = new QQuick3DRenderStats();
250 QQuick3DSceneManager *sceneManager = new QQuick3DSceneManager();
251 QQuick3DObjectPrivate::get(item: m_sceneRoot)->refSceneManager(*sceneManager);
252 Q_ASSERT(sceneManager == QQuick3DObjectPrivate::get(m_sceneRoot)->sceneManager);
253 connect(sender: sceneManager, signal: &QQuick3DSceneManager::needsUpdate,
254 context: this, slot: &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(sender: qw, signal: nullptr, receiver: this, member: nullptr);
283
284 auto sceneManager = QQuick3DObjectPrivate::get(item: m_sceneRoot)->sceneManager;
285 if (sceneManager) {
286 sceneManager->setParent(nullptr);
287 if (auto wa = sceneManager->wattached)
288 wa->queueForCleanup(manager: 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(object: sceneManager->wattached, function: &QQuick3DWindowAttachment::evaluateEol, type: 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 *>(object: obj)) {
311 QQmlListProperty<QObject> itemProperty = QQuick3DObjectPrivate::get(item: 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(item: view3d->scene())->data().count)
322 return 0;
323 QQmlListProperty<QObject> itemProperty = QQuick3DObjectPrivate::get(item: 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(item: 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(item: 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(thread: 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(window&: *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(window: qw, resource: 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>(args&: rhi);
557 wa->setRci(rci);
558
559 // Use DirectConnection to stay on the render thread, if there is one.
560 connect(sender: wa, signal: &QQuick3DWindowAttachment::releaseCachedResources, context: this,
561 slot: &QQuick3DViewport::onReleaseCachedResources, type: Qt::DirectConnection);
562
563 } else {
564 qWarning(msg: "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 }
572
573 return renderer;
574}
575
576bool QQuick3DViewport::isTextureProvider() const
577{
578 // We can only be a texture provider if we are rendering to a texture first
579 if (m_renderMode == QQuick3DViewport::Offscreen)
580 return true;
581
582 return false;
583}
584
585QSGTextureProvider *QQuick3DViewport::textureProvider() const
586{
587 // When Item::layer::enabled == true, QQuickItem will be a texture
588 // provider. In this case we should prefer to return the layer rather
589 // than the fbo texture.
590 if (QQuickItem::isTextureProvider())
591 return QQuickItem::textureProvider();
592
593 // We can only be a texture provider if we are rendering to a texture first
594 if (m_renderMode != QQuick3DViewport::Offscreen)
595 return nullptr;
596
597 QQuickWindow *w = window();
598 if (!w) {
599 qWarning(msg: "QSSGView3D::textureProvider: can only be queried on the rendering thread of an exposed window");
600 return nullptr;
601 }
602
603 if (!m_node)
604 m_node = new SGFramebufferObjectNode;
605 return m_node;
606}
607
608class CleanupJob : public QRunnable
609{
610public:
611 CleanupJob(QQuick3DSGDirectRenderer *renderer) : m_renderer(renderer) { }
612 void run() override { delete m_renderer; }
613private:
614 QQuick3DSGDirectRenderer *m_renderer;
615};
616
617void QQuick3DViewport::releaseResources()
618{
619 if (m_directRenderer) {
620 window()->scheduleRenderJob(job: new CleanupJob(m_directRenderer), schedule: QQuickWindow::BeforeSynchronizingStage);
621 m_directRenderer = nullptr;
622 }
623
624 m_node = nullptr;
625}
626
627void QQuick3DViewport::cleanupDirectRenderer()
628{
629 delete m_directRenderer;
630 m_directRenderer = nullptr;
631}
632
633void QQuick3DViewport::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
634{
635 QQuickItem::geometryChange(newGeometry, oldGeometry);
636
637 if (newGeometry.size() != oldGeometry.size())
638 update();
639}
640
641QSGNode *QQuick3DViewport::updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNodeData *)
642{
643 // When changing render modes
644 if (m_renderModeDirty) {
645 if (node) {
646 delete node;
647 node = nullptr;
648 m_node = nullptr;
649 m_renderNode = nullptr;
650 }
651 if (m_directRenderer) {
652 delete m_directRenderer;
653 m_directRenderer = nullptr;
654 }
655 }
656
657 m_renderModeDirty = false;
658
659 switch (m_renderMode) {
660 // Direct rendering
661 case Underlay:
662 Q_FALLTHROUGH();
663 case Overlay:
664 setupDirectRenderer(m_renderMode);
665 node = nullptr;
666 break;
667 case Offscreen:
668 node = setupOffscreenRenderer(node);
669 break;
670 case Inline:
671 // QSGRenderNode-based rendering
672 node = setupInlineRenderer(node);
673 break;
674 }
675
676 if (!isforceInputHandlingSet()) {
677 // Implicitly enable internal input processing if any item2ds are present.
678 const auto inputHandlingEnabled =
679 QQuick3DObjectPrivate::get(item: m_sceneRoot)->sceneManager->inputHandlingEnabled;
680 const auto enable = inputHandlingEnabled > 0;
681 if (m_enableInputProcessing != enable) {
682 m_enableInputProcessing = enable;
683 QMetaObject::invokeMethod(obj: this, member: "updateInputProcessing", c: Qt::QueuedConnection);
684 }
685 }
686
687 return node;
688}
689
690void QQuick3DViewport::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
691{
692 if (change == ItemSceneChange) {
693 if (value.window) {
694 // TODO: if we want to support multiple windows, there has to be a scene manager for
695 // every window.
696 QQuick3DObjectPrivate::get(item: m_sceneRoot)->sceneManager->setWindow(value.window);
697 if (m_importScene)
698 QQuick3DObjectPrivate::get(item: m_importScene)->sceneManager->setWindow(value.window);
699 m_renderStats->setWindow(value.window);
700 }
701 } else if (change == ItemVisibleHasChanged && isVisible()) {
702 update();
703 }
704}
705
706bool QQuick3DViewport::event(QEvent *event)
707{
708 if (m_enableInputProcessing && event->isPointerEvent())
709 return internalPick(event: static_cast<QPointerEvent *>(event));
710 else
711 return QQuickItem::event(event);
712}
713
714void QQuick3DViewport::componentComplete()
715{
716 QQuickItem::componentComplete();
717 Q_QUICK3D_PROFILE_REGISTER(this);
718}
719
720void QQuick3DViewport::setCamera(QQuick3DCamera *camera)
721{
722 if (m_camera == camera)
723 return;
724
725 if (camera && !camera->parentItem())
726 camera->setParentItem(m_sceneRoot);
727 if (camera)
728 camera->updateGlobalVariables(inViewport: QRect(0, 0, width(), height()));
729
730 QQuick3DObjectPrivate::attachWatcherPriv(sceneContext: m_sceneRoot, callContext: this, setter: &QQuick3DViewport::setCamera, newO: camera, oldO: m_camera);
731
732 m_camera = camera;
733 emit cameraChanged();
734 update();
735}
736
737void QQuick3DViewport::setMultiViewCameras(QQuick3DCamera **firstCamera, int count)
738{
739 m_multiViewCameras.clear();
740 bool sendChangeSignal = false;
741 for (int i = 0; i < count; ++i) {
742 QQuick3DCamera *camera = *(firstCamera + i);
743 if (camera) {
744 if (!camera->parentItem())
745 camera->setParentItem(m_sceneRoot);
746 camera->updateGlobalVariables(inViewport: QRect(0, 0, width(), height()));
747 }
748 if (i == 0) {
749 if (m_camera != camera) {
750 m_camera = camera;
751 sendChangeSignal = true;
752 }
753 }
754
755 m_multiViewCameras.append(t: camera);
756
757 // ### do we need attachWatcher stuff? the Xr-provided cameras cannot disappear, although the XrActor (the owner) might
758 }
759
760 if (sendChangeSignal)
761 emit cameraChanged();
762
763 update();
764}
765
766void QQuick3DViewport::setEnvironment(QQuick3DSceneEnvironment *environment)
767{
768 if (m_environment == environment)
769 return;
770
771 m_environment = environment;
772 if (m_environment && !m_environment->parentItem())
773 m_environment->setParentItem(m_sceneRoot);
774
775 QQuick3DObjectPrivate::attachWatcherPriv(sceneContext: m_sceneRoot, callContext: this, setter: &QQuick3DViewport::setEnvironment, newO: environment, oldO: m_environment);
776
777 emit environmentChanged();
778 update();
779}
780
781void QQuick3DViewport::setImportScene(QQuick3DNode *inScene)
782{
783 // ### We may need consider the case where there is
784 // already a scene tree here
785 // FIXME : Only the first importScene is an effective one
786 if (m_importScene)
787 return;
788
789 // FIXME : Check self-import or cross-import
790 // Currently it does not work since importScene qml parsed in a reverse order.
791 QQuick3DNode *scene = inScene;
792 while (scene) {
793 if (m_sceneRoot == scene) {
794 qmlWarning(me: this) << "Cannot allow self-import or cross-import!";
795 return;
796 }
797
798 QQuick3DSceneRootNode *rn = qobject_cast<QQuick3DSceneRootNode *>(object: scene);
799 scene = rn ? rn->view3D()->importScene() : nullptr;
800 }
801
802 m_importScene = inScene;
803 if (m_importScene)
804 updateSceneManagerForImportScene();
805
806 emit importSceneChanged();
807 update();
808}
809
810void QQuick3DViewport::setRenderMode(QQuick3DViewport::RenderMode renderMode)
811{
812 if (m_renderMode == renderMode)
813 return;
814
815 m_renderMode = renderMode;
816 m_renderModeDirty = true;
817 emit renderModeChanged();
818 update();
819}
820
821#if QT_CONFIG(quick_shadereffect)
822void QQuick3DViewport::setRenderFormat(QQuickShaderEffectSource::Format format)
823{
824 if (m_renderFormat == format)
825 return;
826
827 m_renderFormat = format;
828 m_renderModeDirty = true;
829 emit renderFormatChanged();
830 update();
831}
832#endif
833
834/*!
835 \qmlproperty int QtQuick3D::View3D::explicitTextureWidth
836 \since 6.7
837
838 The width, in pixels, of the item's associated texture. Relevant when a
839 fixed texture size is desired that does not depend on the item's size. This
840 size has no effect on the geometry of the item (its size and placement
841 within the scene), which means the texture's content will appear scaled up
842 or down (and possibly stretched) onto the item's area.
843
844 By default the value is \c 0. A value of 0 means that texture's size
845 follows the item's size. (\c{texture size in pixels} = \c{item's logical
846 size} * \c{device pixel ratio}).
847
848 \note This property is relevant only when \l renderMode is set to \c
849 Offscreen. Its value is ignored otherwise.
850
851 \sa explicitTextureHeight, effectiveTextureSize, DebugView
852*/
853int QQuick3DViewport::explicitTextureWidth() const
854{
855 return m_explicitTextureWidth;
856}
857
858void QQuick3DViewport::setExplicitTextureWidth(int width)
859{
860 if (m_explicitTextureWidth == width)
861 return;
862
863 m_explicitTextureWidth = width;
864 emit explicitTextureWidthChanged();
865 update();
866}
867
868/*!
869 \qmlproperty int QtQuick3D::View3D::explicitTextureHeight
870 \since 6.7
871
872 The height, in pixels, of the item's associated texture. Relevant when a
873 fixed texture size is desired that does not depend on the item's size. This
874 size has no effect on the geometry of the item (its size and placement
875 within the scene), which means the texture's content will appear scaled up
876 or down (and possibly stretched) onto the item's area.
877
878 By default the value is \c 0. A value of 0 means that texture's size
879 follows the item's size. (\c{texture size in pixels} = \c{item's logical
880 size} * \c{device pixel ratio}).
881
882 \note This property is relevant only when \l renderMode is set to \c
883 Offscreen. Its value is ignored otherwise.
884
885 \sa explicitTextureWidth, effectiveTextureSize, DebugView
886*/
887int QQuick3DViewport::explicitTextureHeight() const
888{
889 return m_explicitTextureHeight;
890}
891
892void QQuick3DViewport::setExplicitTextureHeight(int height)
893{
894 if (m_explicitTextureHeight == height)
895 return;
896
897 m_explicitTextureHeight = height;
898 emit explicitTextureHeightChanged();
899 update();
900}
901
902/*!
903 \qmlproperty size QtQuick3D::View3D::effectiveTextureSize
904 \since 6.7
905
906 This property exposes the size, in pixels, of the underlying color (and
907 depth/stencil) buffers. It is provided for use on the GUI (main) thread, in
908 QML bindings or JavaScript.
909
910 This is a read-only property.
911
912 \note This property is relevant only when \l renderMode is set to
913 \c Offscreen.
914
915 \sa explicitTextureWidth, explicitTextureHeight, DebugView
916*/
917QSize QQuick3DViewport::effectiveTextureSize() const
918{
919 return m_effectiveTextureSize;
920}
921
922
923/*!
924 \qmlmethod vector3d View3D::mapFrom3DScene(vector3d scenePos)
925
926 Transforms \a scenePos from scene space (3D) into view space (2D).
927
928 The returned x- and y-values will be be in view coordinates, with the top-left
929 corner at [0, 0] and the bottom-right corner at [width, height]. The returned
930 z-value contains the distance from the near clip plane of the frustum (clipNear) to
931 \a scenePos in scene coordinates. If the distance is negative, that means the \a scenePos
932 is behind the active camera. If \a scenePos cannot be mapped to a position in the scene,
933 a position of [0, 0, 0] is returned.
934
935 This function requires that \l camera is assigned to the view.
936
937 \sa mapTo3DScene(), {Camera::mapToViewport()}{Camera.mapToViewport()}
938*/
939QVector3D QQuick3DViewport::mapFrom3DScene(const QVector3D &scenePos) const
940{
941 if (!m_camera) {
942 qmlWarning(me: this) << "Cannot resolve view position without a camera assigned!";
943 return QVector3D(0, 0, 0);
944 }
945
946 qreal _width = width();
947 qreal _height = height();
948 if (_width == 0 || _height == 0)
949 return QVector3D(0, 0, 0);
950
951 const QVector3D normalizedPos = m_camera->mapToViewport(scenePos, width: _width, height: _height);
952 return normalizedPos * QVector3D(float(_width), float(_height), 1);
953}
954
955/*!
956 \qmlmethod vector3d View3D::mapTo3DScene(vector3d viewPos)
957
958 Transforms \a viewPos from view space (2D) into scene space (3D).
959
960 The x- and y-values of \a viewPos should be in view coordinates, with the top-left
961 corner at [0, 0] and the bottom-right corner at [width, height]. The z-value is
962 interpreted as the distance from the near clip plane of the frustum (clipNear) in
963 scene coordinates.
964
965 If \a viewPos cannot successfully be mapped to a position in the scene, a position of
966 [0, 0, 0] is returned.
967
968 This function requires that a \l camera is assigned to the view.
969
970 \sa mapFrom3DScene(), {Camera::mapFromViewport}{Camera.mapFromViewport()}
971*/
972QVector3D QQuick3DViewport::mapTo3DScene(const QVector3D &viewPos) const
973{
974 if (!m_camera) {
975 qmlWarning(me: this) << "Cannot resolve scene position without a camera assigned!";
976 return QVector3D(0, 0, 0);
977 }
978
979 qreal _width = width();
980 qreal _height = height();
981 if (_width == 0 || _height == 0)
982 return QVector3D(0, 0, 0);
983
984 const QVector3D normalizedPos = viewPos / QVector3D(float(_width), float(_height), 1);
985 return m_camera->mapFromViewport(viewportPos: normalizedPos, width: _width, height: _height);
986}
987
988/*!
989 \qmlmethod pickResult View3D::pick(float x, float y)
990
991 This method will "shoot" a ray into the scene from view coordinates \a x and \a y
992 and return information about the nearest intersection with an object in the scene.
993
994 This can, for instance, be called with mouse coordinates to find the object under the mouse cursor.
995*/
996QQuick3DPickResult QQuick3DViewport::pick(float x, float y) const
997{
998 QQuick3DSceneRenderer *renderer = getRenderer();
999 if (!renderer)
1000 return QQuick3DPickResult();
1001
1002 const QPointF position(qreal(x) * window()->effectiveDevicePixelRatio() * m_widthMultiplier,
1003 qreal(y) * window()->effectiveDevicePixelRatio() * m_heightMultiplier);
1004 std::optional<QSSGRenderRay> rayResult = renderer->getRayFromViewportPos(pos: position);
1005 if (!rayResult.has_value())
1006 return QQuick3DPickResult();
1007
1008 const auto resultList = renderer->syncPick(ray: rayResult.value());
1009 return getNearestPickResult(pickResults: resultList);
1010}
1011
1012/*!
1013 \qmlmethod pickResult View3D::pick(float x, float y, Model model)
1014
1015 This method will "shoot" a ray into the scene from view coordinates \a x and \a y
1016 and return information about the intersection between the ray and the specified \a model.
1017
1018 This can, for instance, be called with mouse coordinates to find the object under the mouse cursor.
1019
1020 \since 6.8
1021*/
1022QQuick3DPickResult QQuick3DViewport::pick(float x, float y, QQuick3DModel *model) const
1023{
1024 QQuick3DSceneRenderer *renderer = getRenderer();
1025 if (!renderer)
1026 return QQuick3DPickResult();
1027
1028 const QPointF position(qreal(x) * window()->effectiveDevicePixelRatio(),
1029 qreal(y) * window()->effectiveDevicePixelRatio());
1030 std::optional<QSSGRenderRay> rayResult = renderer->getRayFromViewportPos(pos: position);
1031
1032 if (!rayResult.has_value())
1033 return QQuick3DPickResult();
1034
1035 const auto renderNode = static_cast<QSSGRenderNode *>(QQuick3DObjectPrivate::get(item: model)->spatialNode);
1036 const auto resultList = renderer->syncPickOne(ray: rayResult.value(), node: renderNode);
1037 return getNearestPickResult(pickResults: resultList);
1038}
1039
1040/*!
1041 \qmlmethod List<pickResult> View3D::pickSubset(float x, float y, list<Model> models)
1042
1043 This method will "shoot" a ray into the scene from view coordinates \a x and \a y
1044 and return information about the intersections with the passed in list of \a models.
1045 This will only check against the list of models passed in.
1046 The returned list is sorted by distance from the camera with the nearest
1047 intersections appearing first and the furthest appearing last.
1048
1049 This can, for instance, be called with mouse coordinates to find the object under the mouse cursor.
1050
1051 Works with both property list<Model> and dynamic JavaScript arrays of models.
1052
1053 \since 6.8
1054*/
1055QList<QQuick3DPickResult> QQuick3DViewport::pickSubset(float x, float y, const QJSValue &models) const
1056{
1057 QQuick3DSceneRenderer *renderer = getRenderer();
1058 if (!renderer)
1059 return {};
1060
1061 QVarLengthArray<QSSGRenderNode*> renderNodes;
1062 // Check for regular JavaScript array
1063 if (models.isArray()) {
1064 const auto length = models.property(QStringLiteral("length")).toInt();
1065 if (length == 0)
1066 return {};
1067
1068 for (int i = 0; i < length; ++i) {
1069 const auto isQObject = models.property(arrayIndex: i).isQObject();
1070 if (!isQObject) {
1071 qmlWarning(me: this) << "Type provided for picking is not a QObject. Needs to be of type QQuick3DModel.";
1072 continue;
1073 }
1074 const auto obj = models.property(arrayIndex: i).toQObject();
1075 const auto model = qobject_cast<QQuick3DModel *>(object: obj);
1076 if (!model) {
1077 qmlWarning(me: this) << "Type " << obj->metaObject()->className() << " is not supported for picking. Needs to be of type QQuick3DModel.";
1078 continue;
1079 }
1080 const auto priv = QQuick3DObjectPrivate::get(item: model);
1081 if (priv && priv->spatialNode) {
1082 renderNodes.push_back(t: static_cast<QSSGRenderNode*>(priv->spatialNode));
1083 }
1084 }
1085 } else {
1086 // Check for property list<Model>
1087 const auto subsetVariant = models.toVariant();
1088 if (!subsetVariant.isValid() || !subsetVariant.canConvert<QQmlListReference>())
1089 return {};
1090
1091 const auto list = subsetVariant.value<QQmlListReference>();
1092
1093 // Only support array of models
1094 if (list.listElementType()->className() != QQuick3DModel::staticMetaObject.className()) {
1095 qmlWarning(me: this) << "Type " << list.listElementType()->className() << " is not supported for picking. Needs to be of type QQuick3DModel.";
1096 return {};
1097 }
1098 for (int i = 0; i < list.count(); ++i) {
1099 auto model = static_cast<QQuick3DModel *>(list.at(i));
1100 if (!model)
1101 continue;
1102 auto priv = QQuick3DObjectPrivate::get(item: model);
1103 if (priv && priv->spatialNode) {
1104 renderNodes.push_back(t: static_cast<QSSGRenderNode*>(priv->spatialNode));
1105 }
1106 }
1107 }
1108
1109 if (renderNodes.empty())
1110 return {};
1111
1112 const QPointF position(qreal(x) * window()->effectiveDevicePixelRatio(),
1113 qreal(y) * window()->effectiveDevicePixelRatio());
1114 std::optional<QSSGRenderRay> rayResult = renderer->getRayFromViewportPos(pos: position);
1115 if (!rayResult.has_value())
1116 return {};
1117
1118 const auto resultList = renderer->syncPickSubset(ray: rayResult.value(), subset: renderNodes);
1119
1120 QList<QQuick3DPickResult> processedResultList;
1121 processedResultList.reserve(asize: resultList.size());
1122 for (const auto &result : resultList)
1123 processedResultList.append(t: processPickResult(pickResult: result));
1124
1125 return processedResultList;
1126}
1127
1128/*!
1129 \qmlmethod List<pickResult> View3D::pickAll(float x, float y)
1130
1131 This method will "shoot" a ray into the scene from view coordinates \a x and \a y
1132 and return a list of information about intersections with objects in the scene.
1133 The returned list is sorted by distance from the camera with the nearest
1134 intersections appearing first and the furthest appearing last.
1135
1136 This can, for instance, be called with mouse coordinates to find the object under the mouse cursor.
1137
1138 \since 6.2
1139*/
1140QList<QQuick3DPickResult> QQuick3DViewport::pickAll(float x, float y) const
1141{
1142 QQuick3DSceneRenderer *renderer = getRenderer();
1143 if (!renderer)
1144 return QList<QQuick3DPickResult>();
1145
1146 const QPointF position(qreal(x) * window()->effectiveDevicePixelRatio() * m_widthMultiplier,
1147 qreal(y) * window()->effectiveDevicePixelRatio() * m_heightMultiplier);
1148 std::optional<QSSGRenderRay> rayResult = renderer->getRayFromViewportPos(pos: position);
1149 if (!rayResult.has_value())
1150 return QList<QQuick3DPickResult>();
1151
1152 const auto resultList = renderer->syncPickAll(ray: rayResult.value());
1153 QList<QQuick3DPickResult> processedResultList;
1154 processedResultList.reserve(asize: resultList.size());
1155 for (const auto &result : resultList)
1156 processedResultList.append(t: processPickResult(pickResult: result));
1157
1158 return processedResultList;
1159}
1160
1161/*!
1162 \qmlmethod pickResult View3D::rayPick(vector3d origin, vector3d direction)
1163
1164 This method will "shoot" a ray into the scene starting at \a origin and in
1165 \a direction and return information about the nearest intersection with an
1166 object in the scene.
1167
1168 This can, for instance, be called with the position and forward vector of
1169 any object in a scene to see what object is in front of an item. This
1170 makes it possible to do picking from any point in the scene.
1171
1172 \since 6.2
1173*/
1174QQuick3DPickResult QQuick3DViewport::rayPick(const QVector3D &origin, const QVector3D &direction) const
1175{
1176 QQuick3DSceneRenderer *renderer = getRenderer();
1177 if (!renderer)
1178 return QQuick3DPickResult();
1179
1180 const QSSGRenderRay ray(origin, direction);
1181 const auto resultList = renderer->syncPick(ray);
1182 return getNearestPickResult(pickResults: resultList);
1183}
1184
1185/*!
1186 \qmlmethod List<pickResult> View3D::rayPickAll(vector3d origin, vector3d direction)
1187
1188 This method will "shoot" a ray into the scene starting at \a origin and in
1189 \a direction and return a list of information about the nearest intersections with
1190 objects in the scene.
1191 The list is presorted by distance from the origin along the direction
1192 vector with the nearest intersections appearing first and the furthest
1193 appearing last.
1194
1195 This can, for instance, be called with the position and forward vector of
1196 any object in a scene to see what objects are in front of an item. This
1197 makes it possible to do picking from any point in the scene.
1198
1199 \since 6.2
1200*/
1201QList<QQuick3DPickResult> QQuick3DViewport::rayPickAll(const QVector3D &origin, const QVector3D &direction) const
1202{
1203 QQuick3DSceneRenderer *renderer = getRenderer();
1204 if (!renderer)
1205 return QList<QQuick3DPickResult>();
1206
1207 const QSSGRenderRay ray(origin, direction);
1208
1209 const auto resultList = renderer->syncPickAll(ray);
1210 QList<QQuick3DPickResult> processedResultList;
1211 processedResultList.reserve(asize: resultList.size());
1212 for (const auto &result : resultList) {
1213 auto processedResult = processPickResult(pickResult: result);
1214 if (processedResult.hitType() != QQuick3DPickResultEnums::HitType::Null)
1215 processedResultList.append(t: processedResult);
1216 }
1217
1218 return processedResultList;
1219}
1220
1221void QQuick3DViewport::processPointerEventFromRay(const QVector3D &origin, const QVector3D &direction, QPointerEvent *event) const
1222{
1223 internalPick(event, origin, direction);
1224}
1225
1226// Note: we have enough information to implement Capability::Hover and Capability::ZPosition,
1227// but those properties are not currently available in QTouchEvent/QEventPoint
1228
1229namespace {
1230class SyntheticTouchDevice : public QPointingDevice
1231{
1232public:
1233 SyntheticTouchDevice(QObject *parent = nullptr)
1234 : QPointingDevice(QLatin1StringView("QtQuick3D Touch Synthesizer"),
1235 0,
1236 DeviceType::TouchScreen,
1237 PointerType::Finger,
1238 Capability::Position,
1239 10, 0,
1240 QString(), QPointingDeviceUniqueId(),
1241 parent)
1242 {
1243 }
1244};
1245}
1246
1247/*!
1248 \qmlmethod View3D::setTouchpoint(Item target, point position, int pointId, bool pressed)
1249
1250 Sends a synthetic touch event to \a target, moving the touch point with ID \a pointId to \a position,
1251 with \a pressed determining if the point is pressed.
1252 Also sends the appropriate touch release event if \a pointId was previously active on a different
1253 item.
1254
1255 \since 6.8
1256 */
1257
1258void QQuick3DViewport::setTouchpoint(QQuickItem *target, const QPointF &position, int pointId, bool pressed)
1259{
1260 if (pointId >= m_touchState.size())
1261 m_touchState.resize(sz: pointId + 1);
1262 auto prevState = m_touchState[pointId];
1263
1264 const bool sameTarget = prevState.target == target;
1265 const bool wasPressed = prevState.isPressed;
1266
1267 const bool isPress = pressed && (!sameTarget || !wasPressed);
1268 const bool isRelease = !pressed && wasPressed && sameTarget;
1269
1270 // Hover if we're not active, and we weren't previously active.
1271 // We assume that we always get a non-active for a target when we release.
1272 // This function sends a release events if the target is changed.
1273 if (!sameTarget && wasPressed)
1274 qWarning(msg: "QQuick3DViewport::setTouchpoint missing release event");
1275
1276 if (!pressed && !wasPressed) {
1277 // This would be a hover event: skipping
1278 return;
1279 }
1280
1281 m_touchState[pointId] = { .target: target, .position: position, .isPressed: pressed };
1282
1283 if (!m_syntheticTouchDevice)
1284 m_syntheticTouchDevice = new SyntheticTouchDevice(this);
1285
1286 QPointingDevicePrivate *devPriv = QPointingDevicePrivate::get(q: m_syntheticTouchDevice);
1287
1288 auto makePoint = [devPriv](int id, QEventPoint::State pointState, QPointF pos) -> QEventPoint {
1289 auto epd = devPriv->pointById(id);
1290 auto &ep = epd->eventPoint;
1291 if (pointState != QEventPoint::State::Stationary)
1292 ep.setAccepted(false);
1293
1294 auto res = QMutableEventPoint::withTimeStamp(timestamp: 0, pointId: id, state: pointState, position: pos, scenePosition: pos, globalPosition: pos);
1295 QMutableEventPoint::update(from: res, to&: ep);
1296 return res;
1297 };
1298
1299 auto sendTouchEvent = [&](QQuickItem *t, const QPointF &position, int pointId, QEventPoint::State pointState) -> void {
1300 QList<QEventPoint> points;
1301 bool otherPoint = false; // Does the event have another point already?
1302 for (int i = 0; i < m_touchState.size(); ++i) {
1303 const auto &ts = m_touchState[i];
1304 if (ts.target != t)
1305 continue;
1306 if (i == pointId) {
1307 auto newPoint = makePoint(i, pointState, position);
1308 points << newPoint;
1309 } else if (ts.isPressed) {
1310 otherPoint = true;
1311 points << makePoint(i, QEventPoint::Stationary, ts.position);
1312 }
1313 }
1314
1315 QEvent::Type type;
1316 if (pointState == QEventPoint::Pressed && !otherPoint)
1317 type = QEvent::Type::TouchBegin;
1318 else if (pointState == QEventPoint::Released && !otherPoint)
1319 type = QEvent::Type::TouchEnd;
1320 else
1321 type = QEvent::Type::TouchUpdate;
1322
1323 QTouchEvent ev(type, m_syntheticTouchDevice, {}, points);
1324
1325 if (t) {
1326 // Actually send event:
1327 auto da = QQuickItemPrivate::get(item: t)->deliveryAgent();
1328 bool handled = da->event(ev: &ev);
1329 Q_UNUSED(handled);
1330 }
1331
1332 // Duplicate logic from QQuickWindowPrivate::clearGrabbers
1333 if (ev.isEndEvent()) {
1334 for (auto &point : ev.points()) {
1335 if (point.state() == QEventPoint::State::Released) {
1336 ev.setExclusiveGrabber(point, exclusiveGrabber: nullptr);
1337 ev.clearPassiveGrabbers(point);
1338 }
1339 }
1340 }
1341 };
1342
1343 // Send a release event to the previous target
1344 if (prevState.target && !sameTarget)
1345 sendTouchEvent(prevState.target, prevState.position, pointId, QEventPoint::Released);
1346
1347 // Now send an event for the new state
1348 QEventPoint::State newState = isPress ? QEventPoint::Pressed : isRelease ? QEventPoint::Released : QEventPoint::Updated;
1349 sendTouchEvent(target, position, pointId, newState);
1350}
1351
1352QQuick3DLightmapBaker *QQuick3DViewport::maybeLightmapBaker()
1353{
1354 return m_lightmapBaker;
1355}
1356
1357QQuick3DLightmapBaker *QQuick3DViewport::lightmapBaker()
1358{
1359 if (!m_lightmapBaker)
1360 m_lightmapBaker= new QQuick3DLightmapBaker(this);
1361
1362 return m_lightmapBaker;
1363}
1364
1365/*!
1366 \internal
1367*/
1368void QQuick3DViewport::bakeLightmap()
1369{
1370 QQuick3DSceneRenderer *renderer = getRenderer();
1371 if (!renderer || !renderer->m_layer->renderData)
1372 return;
1373
1374 const bool currentlyBaking = renderer->m_layer->renderData->lightmapBaker != nullptr;
1375
1376 if (!currentlyBaking)
1377 lightmapBaker()->bake();
1378}
1379
1380/*!
1381 \internal
1382*/
1383void QQuick3DViewport::denoiseLightmap()
1384{
1385 QQuick3DSceneRenderer *renderer = getRenderer();
1386 if (!renderer || !renderer->m_layer->renderData)
1387 return;
1388
1389 const bool currentlyBaking = renderer->m_layer->renderData->lightmapBaker != nullptr;
1390
1391 if (!currentlyBaking)
1392 lightmapBaker()->denoise();
1393}
1394
1395
1396void QQuick3DViewport::setGlobalPickingEnabled(bool isEnabled)
1397{
1398 QQuick3DSceneRenderer *renderer = getRenderer();
1399 if (!renderer)
1400 return;
1401
1402 renderer->setGlobalPickingEnabled(isEnabled);
1403}
1404
1405void QQuick3DViewport::invalidateSceneGraph()
1406{
1407 m_node = nullptr;
1408}
1409
1410QQuick3DSceneRenderer *QQuick3DViewport::getRenderer() const
1411{
1412 QQuick3DSceneRenderer *renderer = nullptr;
1413 if (m_node) {
1414 renderer = m_node->renderer;
1415 } else if (m_renderNode) {
1416 renderer = m_renderNode->renderer;
1417 } else if (m_directRenderer) {
1418 renderer = m_directRenderer->renderer();
1419 }
1420 return renderer;
1421}
1422
1423void QQuick3DViewport::updateDynamicTextures()
1424{
1425 // Update QSGDynamicTextures that are used for source textures and Quick items
1426 // Must be called on the render thread.
1427
1428 const auto &sceneManager = QQuick3DObjectPrivate::get(item: m_sceneRoot)->sceneManager;
1429 for (auto *texture : std::as_const(t&: sceneManager->qsgDynamicTextures))
1430 texture->updateTexture();
1431
1432 QQuick3DNode *scene = m_importScene;
1433 while (scene) {
1434 const auto &importSm = QQuick3DObjectPrivate::get(item: scene)->sceneManager;
1435 if (importSm != sceneManager) {
1436 for (auto *texture : std::as_const(t&: importSm->qsgDynamicTextures))
1437 texture->updateTexture();
1438 }
1439
1440 // if importScene has another import
1441 QQuick3DSceneRootNode *rn = qobject_cast<QQuick3DSceneRootNode *>(object: scene);
1442 scene = rn ? rn->view3D()->importScene() : nullptr;
1443 }
1444}
1445
1446QSGNode *QQuick3DViewport::setupOffscreenRenderer(QSGNode *node)
1447{
1448 SGFramebufferObjectNode *n = static_cast<SGFramebufferObjectNode *>(node);
1449
1450 if (!n) {
1451 if (!m_node)
1452 m_node = new SGFramebufferObjectNode;
1453 n = m_node;
1454 }
1455
1456 if (!n->renderer) {
1457 n->window = window();
1458 n->renderer = createRenderer();
1459 if (!n->renderer)
1460 return nullptr;
1461 n->renderer->fboNode = n;
1462 n->quickFbo = this;
1463 connect(sender: window(), SIGNAL(screenChanged(QScreen*)), receiver: n, SLOT(handleScreenChange()));
1464 }
1465
1466 const qreal dpr = window()->effectiveDevicePixelRatio();
1467 const QSize minFboSize = QQuickItemPrivate::get(item: this)->sceneGraphContext()->minimumFBOSize();
1468 QSize desiredFboSize = QSize(m_explicitTextureWidth, m_explicitTextureHeight);
1469 if (desiredFboSize.isEmpty()) {
1470 desiredFboSize = QSize(width(), height()) * dpr;
1471 n->devicePixelRatio = dpr;
1472 // 1:1 mapping between the backing texture and the on-screen quad
1473 m_widthMultiplier = 1.0f;
1474 m_heightMultiplier = 1.0f;
1475 } else {
1476 QSize itemPixelSize = QSize(width(), height()) * dpr;
1477 // not 1:1 maping between the backing texture and the on-screen quad
1478 m_widthMultiplier = desiredFboSize.width() / float(itemPixelSize.width());
1479 m_heightMultiplier = desiredFboSize.height() / float(itemPixelSize.height());
1480 n->devicePixelRatio = 1.0;
1481 }
1482 desiredFboSize.setWidth(qMax(a: minFboSize.width(), b: desiredFboSize.width()));
1483 desiredFboSize.setHeight(qMax(a: minFboSize.height(), b: desiredFboSize.height()));
1484
1485 if (desiredFboSize != m_effectiveTextureSize) {
1486 m_effectiveTextureSize = desiredFboSize;
1487 emit effectiveTextureSizeChanged();
1488 }
1489
1490 n->setFiltering(smooth() ? QSGTexture::Linear : QSGTexture::Nearest);
1491 n->setRect(x: 0, y: 0, w: width(), h: height());
1492 if (checkIsVisible() && isComponentComplete()) {
1493 n->renderer->synchronize(view3D: this, size: desiredFboSize, dpr: n->devicePixelRatio);
1494 if (n->renderer->m_textureNeedsFlip)
1495 n->setTextureCoordinatesTransform(QSGSimpleTextureNode::MirrorVertically);
1496 updateDynamicTextures();
1497 n->scheduleRender();
1498 }
1499
1500 return n;
1501}
1502
1503QSGNode *QQuick3DViewport::setupInlineRenderer(QSGNode *node)
1504{
1505 QQuick3DSGRenderNode *n = static_cast<QQuick3DSGRenderNode *>(node);
1506 if (!n) {
1507 if (!m_renderNode)
1508 m_renderNode = new QQuick3DSGRenderNode;
1509 n = m_renderNode;
1510 }
1511
1512 if (!n->renderer) {
1513 n->window = window();
1514 n->renderer = createRenderer();
1515 if (!n->renderer)
1516 return nullptr;
1517 }
1518
1519 if (!m_effectiveTextureSize.isEmpty()) {
1520 m_effectiveTextureSize = QSize();
1521 emit effectiveTextureSizeChanged();
1522 }
1523
1524 const QSize targetSize = window()->effectiveDevicePixelRatio() * QSize(width(), height());
1525
1526 // checkIsVisible, not isVisible, because, for example, a
1527 // { visible: false; layer.enabled: true } item still needs
1528 // to function normally.
1529 if (checkIsVisible() && isComponentComplete()) {
1530 n->renderer->synchronize(view3D: this, size: targetSize, dpr: window()->effectiveDevicePixelRatio());
1531 updateDynamicTextures();
1532 n->markDirty(bits: QSGNode::DirtyMaterial);
1533 }
1534
1535 return n;
1536}
1537
1538
1539void QQuick3DViewport::setupDirectRenderer(RenderMode mode)
1540{
1541 auto renderMode = (mode == Underlay) ? QQuick3DSGDirectRenderer::Underlay
1542 : QQuick3DSGDirectRenderer::Overlay;
1543 if (!m_directRenderer) {
1544 QQuick3DSceneRenderer *sceneRenderer = createRenderer();
1545 if (!sceneRenderer)
1546 return;
1547 m_directRenderer = new QQuick3DSGDirectRenderer(sceneRenderer, window(), renderMode);
1548 connect(sender: window(), signal: &QQuickWindow::sceneGraphInvalidated, context: this, slot: &QQuick3DViewport::cleanupDirectRenderer, type: Qt::DirectConnection);
1549 }
1550
1551 if (!m_effectiveTextureSize.isEmpty()) {
1552 m_effectiveTextureSize = QSize();
1553 emit effectiveTextureSizeChanged();
1554 }
1555
1556 const QSizeF targetSize = window()->effectiveDevicePixelRatio() * QSizeF(width(), height());
1557 m_directRenderer->setViewport(QRectF(window()->effectiveDevicePixelRatio() * mapToScene(point: QPointF(0, 0)), targetSize));
1558 m_directRenderer->setVisibility(isVisible());
1559 if (isVisible()) {
1560 m_directRenderer->preSynchronize();
1561 m_directRenderer->renderer()->synchronize(view3D: this, size: targetSize.toSize(), dpr: window()->effectiveDevicePixelRatio());
1562 updateDynamicTextures();
1563 m_directRenderer->requestRender();
1564 }
1565}
1566
1567// This is used for offscreen mode since we need to check if
1568// this item is used by an effect but hidden
1569bool QQuick3DViewport::checkIsVisible() const
1570{
1571 auto childPrivate = QQuickItemPrivate::get(item: this);
1572 return (childPrivate->explicitVisible ||
1573 (childPrivate->extra.isAllocated() && childPrivate->extra->effectRefCount));
1574
1575}
1576
1577/*!
1578 \internal
1579
1580 This method processes a pickResult on an object with the objective of checking
1581 if there are any QQuickItem based subscenes that the QPointerEvent needs to be
1582 forwarded to (3D -> 2D). If such a subscene is found, the event will be mapped
1583 to the correct cordinate system, and added to the visitedSubscenes map for later
1584 event delivery.
1585*/
1586void QQuick3DViewport::processPickedObject(const QSSGRenderPickResult &pickResult,
1587 int pointIndex,
1588 QPointerEvent *event,
1589 QFlatMap<QQuickItem *, SubsceneInfo> &visitedSubscenes) const
1590{
1591 QQuickItem *subsceneRootItem = nullptr;
1592 QPointF subscenePosition;
1593 const auto backendObject = pickResult.m_hitObject;
1594 const auto frontendObject = findFrontendNode(backendObject);
1595 if (!frontendObject)
1596 return;
1597
1598 // Figure out if there are any QQuickItem based scenes we need to forward
1599 // the event to, and if the are found, determine how to translate the UV Coords
1600 // in the pickResult based on what type the object containing the scene is.
1601 auto frontendObjectPrivate = QQuick3DObjectPrivate::get(item: frontendObject);
1602 if (frontendObjectPrivate->type == QQuick3DObjectPrivate::Type::Item2D) {
1603 // Item2D, this is the case where there is just an embedded Qt Quick 2D Item
1604 // rendered directly to the scene.
1605 auto item2D = qobject_cast<QQuick3DItem2D *>(object: frontendObject);
1606 if (item2D)
1607 subsceneRootItem = item2D->contentItem();
1608 if (!subsceneRootItem || subsceneRootItem->childItems().isEmpty())
1609 return; // ignore empty 2D subscenes
1610
1611 // In this case the "UV" coordinates are in pixels in the subscene root item's coordinate system.
1612 subscenePosition = pickResult.m_localUVCoords.toPointF();
1613
1614 // The following code will account for custom input masking, as well any
1615 // transformations that might have been applied to the Item
1616 if (!subsceneRootItem->childAt(x: subscenePosition.x(), y: subscenePosition.y()))
1617 return;
1618 } else if (frontendObjectPrivate->type == QQuick3DObjectPrivate::Type::Model) {
1619 // Model
1620 int materialSubset = pickResult.m_subset;
1621 const auto backendModel = static_cast<const QSSGRenderModel *>(backendObject);
1622 // Get material
1623 if (backendModel->materials.size() < (pickResult.m_subset + 1))
1624 materialSubset = backendModel->materials.size() - 1;
1625 if (materialSubset < 0)
1626 return;
1627 const auto backendMaterial = backendModel->materials.at(i: materialSubset);
1628 const auto frontendMaterial = static_cast<QQuick3DMaterial*>(findFrontendNode(backendObject: backendMaterial));
1629 subsceneRootItem = getSubSceneRootItem(material: frontendMaterial);
1630
1631 if (subsceneRootItem) {
1632 // In this case the pick result really is using UV coordinates.
1633 subscenePosition = QPointF(subsceneRootItem->x() + pickResult.m_localUVCoords.x() * subsceneRootItem->width(),
1634 subsceneRootItem->y() - pickResult.m_localUVCoords.y() * subsceneRootItem->height() + subsceneRootItem->height());
1635 }
1636 }
1637
1638 // Add the new event (item and position) to the visitedSubscene map.
1639 if (subsceneRootItem) {
1640 SubsceneInfo &subscene = visitedSubscenes[subsceneRootItem]; // create if not found
1641 subscene.obj = frontendObject;
1642 if (subscene.eventPointScenePositions.size() != event->pointCount()) {
1643 // ensure capacity, and use an out-of-scene position rather than 0,0 by default
1644 constexpr QPointF inf(-qt_inf(), -qt_inf());
1645 subscene.eventPointScenePositions.resize(sz: event->pointCount(), v: inf);
1646 }
1647 subscene.eventPointScenePositions[pointIndex] = subscenePosition;
1648 }
1649}
1650
1651/*!
1652 \internal
1653
1654 This method will try and find a QQuickItem based by looking for sourceItem()
1655 properties on the primary color channels for the various material types.
1656
1657 \note for CustomMaterial there is just a best effort given where the first
1658 associated Texture with a sourceItem property is used.
1659*/
1660
1661QQuickItem *QQuick3DViewport::getSubSceneRootItem(QQuick3DMaterial *material) const
1662{
1663 if (!material)
1664 return nullptr;
1665
1666 QQuickItem *subsceneRootItem = nullptr;
1667 const auto frontendMaterialPrivate = QQuick3DObjectPrivate::get(item: material);
1668
1669 if (frontendMaterialPrivate->type == QQuick3DObjectPrivate::Type::DefaultMaterial) {
1670 // Default Material
1671 const auto defaultMaterial = qobject_cast<QQuick3DDefaultMaterial *>(object: material);
1672 if (defaultMaterial) {
1673 // Just check for a diffuseMap for now
1674 if (defaultMaterial->diffuseMap() && defaultMaterial->diffuseMap()->sourceItem())
1675 subsceneRootItem = defaultMaterial->diffuseMap()->sourceItem();
1676 }
1677
1678 } else if (frontendMaterialPrivate->type == QQuick3DObjectPrivate::Type::PrincipledMaterial) {
1679 // Principled Material
1680 const auto principledMaterial = qobject_cast<QQuick3DPrincipledMaterial *>(object: material);
1681 if (principledMaterial) {
1682 // Just check for a baseColorMap for now
1683 if (principledMaterial->baseColorMap() && principledMaterial->baseColorMap()->sourceItem())
1684 subsceneRootItem = principledMaterial->baseColorMap()->sourceItem();
1685 }
1686 } else if (frontendMaterialPrivate->type == QQuick3DObjectPrivate::Type::SpecularGlossyMaterial) {
1687 // SpecularGlossy Material
1688 const auto specularGlossyMaterial = qobject_cast<QQuick3DSpecularGlossyMaterial *>(object: material);
1689 if (specularGlossyMaterial) {
1690 // Just check for a albedoMap for now
1691 if (specularGlossyMaterial->albedoMap() && specularGlossyMaterial->albedoMap()->sourceItem())
1692 subsceneRootItem = specularGlossyMaterial->albedoMap()->sourceItem();
1693 }
1694 } else if (frontendMaterialPrivate->type == QQuick3DObjectPrivate::Type::CustomMaterial) {
1695 // Custom Material
1696 const auto customMaterial = qobject_cast<QQuick3DCustomMaterial *>(object: material);
1697 if (customMaterial) {
1698 // This case is a bit harder because we can not know how the textures will be used
1699 const auto &texturesInputs = customMaterial->m_dynamicTextureMaps;
1700 for (const auto &textureInput : texturesInputs) {
1701 if (auto texture = textureInput->texture()) {
1702 if (texture->sourceItem()) {
1703 subsceneRootItem = texture->sourceItem();
1704 break;
1705 }
1706 }
1707 }
1708 }
1709 }
1710 return subsceneRootItem;
1711}
1712
1713
1714/*!
1715 \internal
1716*/
1717QQuick3DPickResult QQuick3DViewport::getNearestPickResult(const QVarLengthArray<QSSGRenderPickResult, 20> &pickResults) const
1718{
1719 for (const auto &result : pickResults) {
1720 auto pickResult = processPickResult(pickResult: result);
1721 if (pickResult.hitType() != QQuick3DPickResultEnums::HitType::Null)
1722 return pickResult;
1723 }
1724
1725 return QQuick3DPickResult();
1726}
1727
1728/*!
1729 \internal
1730 This method is responsible for going through the visitedSubscenes map and forwarding
1731 the event with corrected coordinates to each subscene.
1732
1733*/
1734bool QQuick3DViewport::forwardEventToSubscenes(QPointerEvent *event,
1735 bool useRayPicking,
1736 QQuick3DSceneRenderer *renderer,
1737 const QFlatMap<QQuickItem *, SubsceneInfo> &visitedSubscenes) const
1738{
1739 // Now deliver the entire event (all points) to each relevant subscene.
1740 // Maybe only some points fall inside, but QQuickDeliveryAgentPrivate::deliverMatchingPointsToItem()
1741 // makes reduced-subset touch events that contain only the relevant points, when necessary.
1742 bool ret = false;
1743
1744 QVarLengthArray<QPointF, 16> originalScenePositions;
1745 originalScenePositions.resize(sz: event->pointCount());
1746 for (int pointIndex = 0; pointIndex < event->pointCount(); ++pointIndex)
1747 originalScenePositions[pointIndex] = event->point(i: pointIndex).scenePosition();
1748 for (auto subscene : visitedSubscenes) {
1749 QQuickItem *subsceneRoot = subscene.first;
1750 auto &subsceneInfo = subscene.second;
1751 Q_ASSERT(subsceneInfo.eventPointScenePositions.size() == event->pointCount());
1752 auto da = QQuickItemPrivate::get(item: subsceneRoot)->deliveryAgent();
1753 for (int pointIndex = 0; pointIndex < event->pointCount(); ++pointIndex) {
1754 const auto &pt = subsceneInfo.eventPointScenePositions.at(idx: pointIndex);
1755 // By tradition, QGuiApplicationPrivate::processTouchEvent() has set the local position to the scene position,
1756 // and Qt Quick expects it to arrive that way: then QQuickDeliveryAgentPrivate::translateTouchEvent()
1757 // copies it into the scene position before localizing.
1758 // That might be silly, we might change it eventually, but gotta stay consistent for now.
1759 QEventPoint &ep = event->point(i: pointIndex);
1760 QMutableEventPoint::setPosition(p&: ep, arg: pt);
1761 QMutableEventPoint::setScenePosition(p&: ep, arg: pt);
1762 }
1763
1764 if (event->isBeginEvent())
1765 da->setSceneTransform(nullptr);
1766 if (da->event(ev: event)) {
1767 ret = true;
1768 if (QQuickDeliveryAgentPrivate::anyPointGrabbed(ev: event) && !useRayPicking) {
1769 // In case any QEventPoint was grabbed, the relevant QQuickDeliveryAgent needs to know
1770 // how to repeat the picking/coordinate transformation for each update,
1771 // because delivery will bypass internalPick() due to the grab, and it's
1772 // more efficient to avoid whole-scene picking each time anyway.
1773 auto frontendObjectPrivate = QQuick3DObjectPrivate::get(item: subsceneInfo.obj);
1774 const bool item2Dcase = (frontendObjectPrivate->type == QQuick3DObjectPrivate::Type::Item2D);
1775 ViewportTransformHelper *transform = new ViewportTransformHelper;
1776 transform->viewport = const_cast<QQuick3DViewport *>(this);
1777 transform->renderer = renderer;
1778 transform->sceneParentNode = static_cast<QSSGRenderNode*>(frontendObjectPrivate->spatialNode);
1779 transform->targetItem = subsceneRoot;
1780 transform->scaleX = window()->effectiveDevicePixelRatio() * m_widthMultiplier;
1781 transform->scaleY = window()->effectiveDevicePixelRatio() * m_heightMultiplier;
1782 transform->uvCoordsArePixels = item2Dcase;
1783 transform->setOnDeliveryAgent(da);
1784 qCDebug(lcPick) << event->type() << "created ViewportTransformHelper on" << da;
1785 }
1786 } else if (event->type() != QEvent::HoverMove) {
1787 qCDebug(lcPick) << subsceneRoot << "didn't want" << event;
1788 }
1789 event->setAccepted(false); // reject implicit grab and let it keep propagating
1790 }
1791 if (visitedSubscenes.isEmpty()) {
1792 event->setAccepted(false);
1793 } else {
1794 for (int pointIndex = 0; pointIndex < event->pointCount(); ++pointIndex)
1795 QMutableEventPoint::setScenePosition(p&: event->point(i: pointIndex), arg: originalScenePositions.at(idx: pointIndex));
1796 }
1797
1798 // Normally this would occur in QQuickWindowPrivate::clearGrabbers(...) but
1799 // for ray based input, input never goes through QQuickWindow (since events
1800 // are generated from within scene space and not window/screen space).
1801 if (event->isEndEvent() && useRayPicking) {
1802 if (event->isSinglePointEvent()) {
1803 if (static_cast<QSinglePointEvent *>(event)->buttons() == Qt::NoButton) {
1804 auto &firstPt = event->point(i: 0);
1805 event->setExclusiveGrabber(point: firstPt, exclusiveGrabber: nullptr);
1806 event->clearPassiveGrabbers(point: firstPt);
1807 }
1808 } else {
1809 for (auto &point : event->points()) {
1810 if (point.state() == QEventPoint::State::Released) {
1811 event->setExclusiveGrabber(point, exclusiveGrabber: nullptr);
1812 event->clearPassiveGrabbers(point);
1813 }
1814 }
1815 }
1816 }
1817
1818 return ret;
1819}
1820
1821
1822bool QQuick3DViewport::internalPick(QPointerEvent *event, const QVector3D &origin, const QVector3D &direction) const
1823{
1824 QQuick3DSceneRenderer *renderer = getRenderer();
1825 if (!renderer || !event)
1826 return false;
1827
1828 QFlatMap<QQuickItem*, SubsceneInfo> visitedSubscenes;
1829 const bool useRayPicking = !direction.isNull();
1830
1831 for (int pointIndex = 0; pointIndex < event->pointCount(); ++pointIndex) {
1832 auto &eventPoint = event->point(i: pointIndex);
1833 QVarLengthArray<QSSGRenderPickResult, 20> pickResults;
1834 if (Q_UNLIKELY(useRayPicking))
1835 pickResults = getPickResults(renderer, origin, direction);
1836 else
1837 pickResults = getPickResults(renderer, eventPoint);
1838
1839 if (!pickResults.isEmpty())
1840 for (const auto &pickResult : pickResults)
1841 processPickedObject(pickResult, pointIndex, event, visitedSubscenes);
1842 else
1843 eventPoint.setAccepted(false); // let it fall through the viewport to Items underneath
1844 }
1845
1846 return forwardEventToSubscenes(event, useRayPicking, renderer, visitedSubscenes);
1847}
1848
1849bool QQuick3DViewport::singlePointPick(QSinglePointEvent *event, const QVector3D &origin, const QVector3D &direction)
1850{
1851 QQuick3DSceneRenderer *renderer = getRenderer();
1852 if (!renderer || !event)
1853 return false;
1854
1855 QSSGRenderRay ray(origin, direction);
1856
1857 Q_ASSERT(event->pointCount() == 1);
1858 QPointF originalPosition = event->point(i: 0).scenePosition();
1859
1860 auto pickResults = renderer->syncPickAll(ray);
1861
1862 bool delivered = false;
1863
1864 constexpr float jitterLimit = 2.5; // TODO: add property for this?
1865 bool withinJitterLimit = false;
1866
1867 for (const auto &pickResult : pickResults) {
1868 auto [item, position] = getItemAndPosition(pickResult);
1869 if (!item)
1870 break;
1871 if (item == m_prevMouseItem && (position - m_prevMousePos).manhattanLength() < jitterLimit && !event->button()) {
1872 withinJitterLimit = true;
1873 break;
1874 }
1875 auto da = QQuickItemPrivate::get(item)->deliveryAgent();
1876 QEventPoint &ep = event->point(i: 0);
1877 QMutableEventPoint::setPosition(p&: ep, arg: position);
1878 QMutableEventPoint::setScenePosition(p&: ep, arg: position);
1879 if (da->event(ev: event)) {
1880 delivered = true;
1881 if (event->type() == QEvent::MouseButtonPress) {
1882 m_prevMouseItem = item;
1883 m_prevMousePos = position;
1884 withinJitterLimit = true;
1885 }
1886 break;
1887 }
1888 }
1889
1890 QMutableEventPoint::setScenePosition(p&: event->point(i: 0), arg: originalPosition);
1891 if (!withinJitterLimit)
1892 m_prevMouseItem = nullptr;
1893
1894 // Normally this would occur in QQuickWindowPrivate::clearGrabbers(...) but
1895 // for ray based input, input never goes through QQuickWindow (since events
1896 // are generated from within scene space and not window/screen space).
1897 if (event->isEndEvent()) {
1898 if (event->buttons() == Qt::NoButton) {
1899 auto &firstPt = event->point(i: 0);
1900 event->setExclusiveGrabber(point: firstPt, exclusiveGrabber: nullptr);
1901 event->clearPassiveGrabbers(point: firstPt);
1902 }
1903 }
1904
1905 return delivered;
1906}
1907
1908QPair<QQuickItem *, QPointF> QQuick3DViewport::getItemAndPosition(const QSSGRenderPickResult &pickResult)
1909{
1910 QQuickItem *subsceneRootItem = nullptr;
1911 QPointF subscenePosition;
1912 const auto backendObject = pickResult.m_hitObject;
1913 const auto frontendObject = findFrontendNode(backendObject);
1914 if (!frontendObject)
1915 return {};
1916 auto frontendObjectPrivate = QQuick3DObjectPrivate::get(item: frontendObject);
1917 if (frontendObjectPrivate->type == QQuick3DObjectPrivate::Type::Item2D) {
1918 // Item2D, this is the case where there is just an embedded Qt Quick 2D Item
1919 // rendered directly to the scene.
1920 auto item2D = qobject_cast<QQuick3DItem2D *>(object: frontendObject);
1921 if (item2D)
1922 subsceneRootItem = item2D->contentItem();
1923 if (!subsceneRootItem || subsceneRootItem->childItems().isEmpty())
1924 return {}; // ignore empty 2D subscenes
1925
1926 // In this case the "UV" coordinates are in pixels in the subscene root item's coordinate system.
1927 subscenePosition = pickResult.m_localUVCoords.toPointF();
1928
1929 // The following code will account for custom input masking, as well any
1930 // transformations that might have been applied to the Item
1931 if (!subsceneRootItem->childAt(x: subscenePosition.x(), y: subscenePosition.y()))
1932 return {};
1933 } else if (frontendObjectPrivate->type == QQuick3DObjectPrivate::Type::Model) {
1934 // Model
1935 int materialSubset = pickResult.m_subset;
1936 const auto backendModel = static_cast<const QSSGRenderModel *>(backendObject);
1937 // Get material
1938 if (backendModel->materials.size() < (pickResult.m_subset + 1))
1939 materialSubset = backendModel->materials.size() - 1;
1940 if (materialSubset < 0)
1941 return {};
1942 const auto backendMaterial = backendModel->materials.at(i: materialSubset);
1943 const auto frontendMaterial = static_cast<QQuick3DMaterial *>(findFrontendNode(backendObject: backendMaterial));
1944 subsceneRootItem = getSubSceneRootItem(material: frontendMaterial);
1945
1946 if (subsceneRootItem) {
1947 // In this case the pick result really is using UV coordinates.
1948 subscenePosition = QPointF(subsceneRootItem->x() + pickResult.m_localUVCoords.x() * subsceneRootItem->width(),
1949 subsceneRootItem->y() - pickResult.m_localUVCoords.y() * subsceneRootItem->height() + subsceneRootItem->height());
1950 }
1951 }
1952 return {subsceneRootItem, subscenePosition};
1953}
1954
1955QVarLengthArray<QSSGRenderPickResult, 20> QQuick3DViewport::getPickResults(QQuick3DSceneRenderer *renderer,
1956 const QVector3D &origin,
1957 const QVector3D &direction) const
1958{
1959 const QSSGRenderRay ray(origin, direction);
1960 return renderer->syncPickAll(ray);
1961}
1962
1963QVarLengthArray<QSSGRenderPickResult, 20> QQuick3DViewport::getPickResults(QQuick3DSceneRenderer *renderer, const QEventPoint &eventPoint) const
1964{
1965 QVarLengthArray<QSSGRenderPickResult, 20> pickResults;
1966 QPointF realPosition = eventPoint.position() * window()->effectiveDevicePixelRatio();
1967 // correct when mapping is not 1:1 due to explicit backing texture size
1968 realPosition.rx() *= m_widthMultiplier;
1969 realPosition.ry() *= m_heightMultiplier;
1970 std::optional<QSSGRenderRay> rayResult = renderer->getRayFromViewportPos(pos: realPosition);
1971 if (rayResult.has_value())
1972 pickResults = renderer->syncPickAll(ray: rayResult.value());
1973 return pickResults;
1974}
1975
1976/*!
1977 \internal
1978
1979 This provides a way to lookup frontendNodes with a backend node taking into consideration both
1980 the scene and the importScene
1981*/
1982QQuick3DObject *QQuick3DViewport::findFrontendNode(const QSSGRenderGraphObject *backendObject) const
1983{
1984 if (!backendObject)
1985 return nullptr;
1986
1987 const auto sceneManager = QQuick3DObjectPrivate::get(item: m_sceneRoot)->sceneManager;
1988 QQuick3DObject *frontendObject = sceneManager->lookUpNode(node: backendObject);
1989 if (!frontendObject && m_importScene) {
1990 const auto importSceneManager = QQuick3DObjectPrivate::get(item: m_importScene)->sceneManager;
1991 frontendObject = importSceneManager->lookUpNode(node: backendObject);
1992 }
1993 return frontendObject;
1994}
1995
1996QQuick3DPickResult QQuick3DViewport::processPickResult(const QSSGRenderPickResult &pickResult) const
1997{
1998 if (!pickResult.m_hitObject)
1999 return QQuick3DPickResult();
2000
2001 QQuick3DObject *frontendObject = findFrontendNode(backendObject: pickResult.m_hitObject);
2002
2003 QQuick3DModel *model = qobject_cast<QQuick3DModel *>(object: frontendObject);
2004 if (model)
2005 return QQuick3DPickResult(model,
2006 ::sqrtf(x: pickResult.m_distanceSq),
2007 pickResult.m_localUVCoords,
2008 pickResult.m_scenePosition,
2009 pickResult.m_localPosition,
2010 pickResult.m_faceNormal,
2011 pickResult.m_sceneNormal,
2012 pickResult.m_instanceIndex);
2013
2014 QQuick3DItem2D *frontend2DItem = qobject_cast<QQuick3DItem2D *>(object: frontendObject);
2015 if (frontend2DItem && frontend2DItem->contentItem()) {
2016 // Check if the pick is inside the content item (since the ray just intersected on the items plane)
2017 const QPointF subscenePosition = pickResult.m_localUVCoords.toPointF();
2018 const auto child = frontend2DItem->contentItem()->childAt(x: subscenePosition.x(), y: subscenePosition.y());
2019 if (child) {
2020 return QQuick3DPickResult(child,
2021 ::sqrtf(x: pickResult.m_distanceSq),
2022 QVector2D(frontend2DItem->contentItem()->mapToItem(item: child, point: subscenePosition)),
2023 pickResult.m_scenePosition,
2024 pickResult.m_localPosition,
2025 pickResult.m_faceNormal);
2026 }
2027 }
2028
2029 return QQuick3DPickResult();
2030
2031}
2032
2033// Returns the first found scene manager of objects children
2034QQuick3DSceneManager *QQuick3DViewport::findChildSceneManager(QQuick3DObject *inObject, QQuick3DSceneManager *manager)
2035{
2036 if (manager)
2037 return manager;
2038
2039 auto children = QQuick3DObjectPrivate::get(item: inObject)->childItems;
2040 for (auto child : children) {
2041 if (auto m = QQuick3DObjectPrivate::get(item: child)->sceneManager) {
2042 manager = m;
2043 break;
2044 }
2045 manager = findChildSceneManager(inObject: child, manager);
2046 }
2047 return manager;
2048}
2049
2050void QQuick3DViewport::updateInputProcessing()
2051{
2052 // This should be called from the gui thread.
2053 setAcceptTouchEvents(m_enableInputProcessing);
2054 setAcceptHoverEvents(m_enableInputProcessing);
2055 setAcceptedMouseButtons(m_enableInputProcessing ? Qt::AllButtons : Qt::NoButton);
2056}
2057
2058void QQuick3DViewport::onReleaseCachedResources()
2059{
2060 if (auto renderer = getRenderer())
2061 renderer->releaseCachedResources();
2062}
2063
2064/*!
2065 \qmlproperty List<QtQuick3D::Object3D> View3D::extensions
2066
2067 This property contains a list of user extensions that should be used with this \l View3D.
2068
2069 \sa RenderExtension
2070*/
2071QQmlListProperty<QQuick3DObject> QQuick3DViewport::extensions()
2072{
2073 return QQmlListProperty<QQuick3DObject>{ this,
2074 &m_extensionListDirty,
2075 &QQuick3DExtensionListHelper::extensionAppend,
2076 &QQuick3DExtensionListHelper::extensionCount,
2077 &QQuick3DExtensionListHelper::extensionAt,
2078 &QQuick3DExtensionListHelper::extensionClear,
2079 &QQuick3DExtensionListHelper::extensionReplace,
2080 &QQuick3DExtensionListHelper::extensionRemoveLast};
2081}
2082
2083/*!
2084 \internal
2085 */
2086void QQuick3DViewport::rebuildExtensionList()
2087{
2088 m_extensionListDirty = true;
2089 update();
2090}
2091
2092/*!
2093 \internal
2094
2095 Private constructor for the QQuick3DViewport class so we can differentiate between
2096 a regular QQuick3DViewport and one created for a specific usage, like XR.
2097 */
2098QQuick3DViewport::QQuick3DViewport(PrivateInstanceType type, QQuickItem *parent)
2099 : QQuick3DViewport(parent)
2100{
2101 m_isXrViewInstance = type == PrivateInstanceType::XrViewInstance;
2102}
2103
2104void QQuick3DViewport::updateCameraForLayer(const QQuick3DViewport &view3D, QSSGRenderLayer &layerNode)
2105{
2106 layerNode.explicitCameras.clear();
2107 if (!view3D.m_multiViewCameras.isEmpty()) {
2108 for (QQuick3DCamera *camera : std::as_const(t: view3D.m_multiViewCameras))
2109 layerNode.explicitCameras.append(t: static_cast<QSSGRenderCamera *>(QQuick3DObjectPrivate::get(item: camera)->spatialNode));
2110 } else if (view3D.camera()) {
2111 layerNode.explicitCameras.append(t: static_cast<QSSGRenderCamera *>(QQuick3DObjectPrivate::get(item: view3D.camera())->spatialNode));
2112 }
2113
2114 // Ensure these have a parent. All nodes need to be in the node tree somewhere, even if they're technically "parentless"
2115 // or we'll not assign a storage slot for them or update them.
2116 for (QSSGRenderCamera *camera : std::as_const(t&: layerNode.explicitCameras)) {
2117 if (!camera->parent)
2118 layerNode.addChild(inChild&: *camera);
2119 }
2120}
2121
2122void QQuick3DViewport::updateSceneManagerForImportScene()
2123{
2124 auto privateObject = QQuick3DObjectPrivate::get(item: m_importScene);
2125 if (!privateObject->sceneManager) {
2126 // If object doesn't already have scene manager, check from its children
2127 QQuick3DSceneManager *manager = findChildSceneManager(inObject: m_importScene);
2128 // If still not found, use the one from the scene root (scenes defined outside of an view3d)
2129 if (!manager)
2130 manager = QQuick3DObjectPrivate::get(item: m_sceneRoot)->sceneManager;
2131 if (manager) {
2132 manager->setWindow(window());
2133 privateObject->refSceneManager(*manager);
2134 }
2135
2136 // In the case m_sceneRoot doesn't have a valid sceneManager,
2137 // it means the view3d is not valid now.
2138 if (!privateObject->sceneManager)
2139 return;
2140
2141 }
2142 connect(sender: privateObject->sceneManager, signal: &QQuick3DSceneManager::needsUpdate,
2143 context: this, slot: &QQuickItem::update, type: Qt::UniqueConnection);
2144 connect(sender: privateObject->sceneManager, signal: &QObject::destroyed,
2145 context: this, slot: [&](QObject *) {
2146 auto privateObject = QQuick3DObjectPrivate::get(item: m_importScene);
2147 privateObject->sceneManager = nullptr;
2148 updateSceneManagerForImportScene();
2149 }, type: Qt::DirectConnection);
2150
2151 QQuick3DNode *scene = m_importScene;
2152 while (scene) {
2153 QQuick3DSceneRootNode *rn = qobject_cast<QQuick3DSceneRootNode *>(object: scene);
2154 scene = rn ? rn->view3D()->importScene() : nullptr;
2155
2156 if (scene) {
2157 connect(sender: QQuick3DObjectPrivate::get(item: scene)->sceneManager,
2158 signal: &QQuick3DSceneManager::needsUpdate,
2159 context: this, slot: &QQuickItem::update, type: Qt::UniqueConnection);
2160 }
2161 }
2162}
2163
2164QT_END_NAMESPACE
2165

source code of qtquick3d/src/quick3d/qquick3dviewport.cpp