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