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 | |
37 | #include <optional> |
38 | |
39 | QT_BEGIN_NAMESPACE |
40 | |
41 | Q_LOGGING_CATEGORY(lcEv, "qt.quick3d.event" ) |
42 | Q_LOGGING_CATEGORY(lcPick, "qt.quick3d.pick" ) |
43 | Q_LOGGING_CATEGORY(lcHover, "qt.quick3d.hover" ) |
44 | |
45 | static bool isforceInputHandlingSet() |
46 | { |
47 | static const bool v = (qEnvironmentVariableIntValue(varName: "QT_QUICK3D_FORCE_INPUT_HANDLING" ) > 0); |
48 | return v; |
49 | } |
50 | |
51 | struct 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 | std::optional<QSSGRenderRay> rayResult = renderer->getRayFromViewportPos(pos: viewportPoint * dpr); |
74 | if (rayResult.has_value()) { |
75 | auto pickResult = renderer->syncPickOne(ray: rayResult.value(), node: sceneParentNode); |
76 | auto ret = pickResult.m_localUVCoords.toPointF(); |
77 | if (!uvCoordsArePixels) { |
78 | ret = QPointF(targetItem->x() + ret.x() * targetItem->width(), |
79 | targetItem->y() - ret.y() * targetItem->height() + targetItem->height()); |
80 | } |
81 | const bool outOfModel = pickResult.m_localUVCoords.isNull(); |
82 | qCDebug(lcEv) << viewportPoint << "->" << (outOfModel ? "OOM" : "" ) << ret << "@" << pickResult.m_scenePosition |
83 | << "UV" << pickResult.m_localUVCoords << "dist" << qSqrt(v: pickResult.m_distanceSq); |
84 | if (outOfModel) { |
85 | return lastGoodMapping; |
86 | } else { |
87 | lastGoodMapping = ret; |
88 | return ret; |
89 | } |
90 | } |
91 | return QPointF(); |
92 | } |
93 | |
94 | QQuick3DSceneRenderer *renderer = nullptr; |
95 | QSSGRenderNode *sceneParentNode = nullptr; |
96 | QQuickItem* targetItem = nullptr; |
97 | qreal dpr = 1; |
98 | bool uvCoordsArePixels = false; // if false, they are in the range 0..1 |
99 | QPointF lastGoodMapping; |
100 | |
101 | static QList<QPointer<QQuickDeliveryAgent>> owners; |
102 | }; |
103 | |
104 | QList<QPointer<QQuickDeliveryAgent>> ViewportTransformHelper::owners; |
105 | |
106 | class QQuick3DExtensionListHelper |
107 | { |
108 | Q_DISABLE_COPY_MOVE(QQuick3DExtensionListHelper); |
109 | public: |
110 | static void extensionAppend(QQmlListProperty<QQuick3DObject> *list, QQuick3DObject *extension) |
111 | { |
112 | QSSG_ASSERT(list && extension, return); |
113 | |
114 | if (QQuick3DViewport *that = qobject_cast<QQuick3DViewport *>(object: list->object)) { |
115 | if (const auto idx = that->m_extensions.indexOf(t: extension); idx == -1) { |
116 | if (!extension->parentItem()) |
117 | extension->setParentItem(that->m_sceneRoot); |
118 | that->m_extensions.push_back(t: extension); |
119 | that->m_extensionListDirty = true; |
120 | } |
121 | } |
122 | } |
123 | static QQuick3DObject *extensionAt(QQmlListProperty<QQuick3DObject> *list, qsizetype index) |
124 | { |
125 | QQuick3DObject *ret = nullptr; |
126 | QSSG_ASSERT(list, return ret); |
127 | |
128 | if (QQuick3DViewport *that = qobject_cast<QQuick3DViewport *>(object: list->object)) { |
129 | if (that->m_extensions.size() > index) |
130 | ret = that->m_extensions.at(i: index); |
131 | } |
132 | |
133 | return ret; |
134 | } |
135 | static qsizetype extensionCount(QQmlListProperty<QQuick3DObject> *list) |
136 | { |
137 | qsizetype ret = -1; |
138 | QSSG_ASSERT(list, return ret); |
139 | |
140 | if (QQuick3DViewport *that = qobject_cast<QQuick3DViewport *>(object: list->object)) |
141 | ret = that->m_extensions.size(); |
142 | |
143 | return ret; |
144 | } |
145 | static void extensionClear(QQmlListProperty<QQuick3DObject> *list) |
146 | { |
147 | QSSG_ASSERT(list, return); |
148 | |
149 | if (QQuick3DViewport *that = qobject_cast<QQuick3DViewport *>(object: list->object)) { |
150 | that->m_extensions.clear(); |
151 | that->m_extensionListDirty = true; |
152 | } |
153 | } |
154 | static void extensionReplace(QQmlListProperty<QQuick3DObject> *list, qsizetype idx, QQuick3DObject *o) |
155 | { |
156 | QSSG_ASSERT(list, return); |
157 | |
158 | if (QQuick3DViewport *that = qobject_cast<QQuick3DViewport *>(object: list->object)) { |
159 | if (that->m_extensions.size() > idx && idx > -1) { |
160 | that->m_extensions.replace(i: idx, t: o); |
161 | that->m_extensionListDirty = true; |
162 | } |
163 | } |
164 | } |
165 | static void extensionRemoveLast(QQmlListProperty<QQuick3DObject> *list) |
166 | { |
167 | QSSG_ASSERT(list, return); |
168 | |
169 | if (QQuick3DViewport *that = qobject_cast<QQuick3DViewport *>(object: list->object)) { |
170 | that->m_extensions.removeLast(); |
171 | that->m_extensionListDirty = true; |
172 | } |
173 | } |
174 | }; |
175 | |
176 | /*! |
177 | \qmltype View3D |
178 | \inherits QQuickItem |
179 | \inqmlmodule QtQuick3D |
180 | \brief Provides a viewport on which to render a 3D scene. |
181 | |
182 | View3D provides a 2D surface on which a 3D scene can be rendered. This |
183 | surface is a Qt Quick \l Item and can be placed in a Qt Quick scene. |
184 | |
185 | There are two ways to define the 3D scene that is visualized on the View3D: |
186 | If you define a hierarchy of \l{Node}{Node-based} items as children of |
187 | the View3D directly, then this will become the implicit scene of the View3D. |
188 | |
189 | It is also possible to reference an existing scene by using the \l importScene |
190 | property and setting it to the root \l Node of the scene you want to visualize. |
191 | This \l Node does not have to be an ancestor of the View3D, and you can have |
192 | multiple View3Ds that import the same scene. |
193 | |
194 | This is demonstrated in \l {Qt Quick 3D - View3D example}{View3D example}. |
195 | |
196 | If the View3D both has child \l{Node}{Nodes} and the \l importScene property is |
197 | set simultaneously, then both scenes will be rendered as if they were sibling |
198 | subtrees in the same scene. |
199 | |
200 | To control how a scene is rendered, you can set the \l environment |
201 | property. The type \l SceneEnvironment has a number of visual properties |
202 | that can be adjusted, such as background color, tone mapping, anti-aliasing |
203 | and more. \l ExtendedSceneEnvironment in the \c{QtQuick3D.Helpers} module |
204 | extends \l SceneEnvironment with even more features, adding common |
205 | post-processing effects. |
206 | |
207 | In addition, in order for anything to be rendered in the View3D, the scene |
208 | needs a \l Camera. If there is only a single \l Camera in the scene, then |
209 | this will automatically be picked. Otherwise, the \l camera property can |
210 | be used to select the camera. The \l Camera decides which parts of the scene |
211 | are visible, and how they are projected onto the 2D surface. |
212 | |
213 | By default, the 3D scene will first be rendered into an off-screen buffer and |
214 | then composited with the rest of the Qt Quick scene when it is done. This provides |
215 | the maximum level of compatibility, but may have performance implications on some |
216 | graphics hardware. If this is the case, the \l renderMode property can be used to |
217 | switch how the View3D is rendered into the window. |
218 | |
219 | A View3D with the default Offscreen \l renderMode is implicitly a |
220 | \l{QSGTextureProvider}{texture provider} as well. This means that \l |
221 | ShaderEffect or \l{QtQuick3D::Texture::sourceItem}{Texture.sourceItem} can reference |
222 | the View3D directly as long as all items live within the same |
223 | \l{QQuickWindow}{window}. Like with any other \l Item, it is also possible |
224 | to switch the View3D, or one of its ancestors, into a texture-based |
225 | \l{QtQuick::Item::layer.enabled}{item layer}. |
226 | |
227 | \sa {Qt Quick 3D - View3D example} |
228 | */ |
229 | |
230 | QQuick3DViewport::QQuick3DViewport(QQuickItem *parent) |
231 | : QQuickItem(parent) |
232 | { |
233 | setFlag(flag: ItemHasContents); |
234 | m_camera = nullptr; |
235 | m_sceneRoot = new QQuick3DSceneRootNode(this); |
236 | m_environment = new QQuick3DSceneEnvironment(m_sceneRoot); |
237 | m_renderStats = new QQuick3DRenderStats(); |
238 | QQuick3DSceneManager *sceneManager = new QQuick3DSceneManager(); |
239 | QQuick3DObjectPrivate::get(item: m_sceneRoot)->refSceneManager(*sceneManager); |
240 | Q_ASSERT(sceneManager == QQuick3DObjectPrivate::get(m_sceneRoot)->sceneManager); |
241 | connect(sender: sceneManager, signal: &QQuick3DSceneManager::needsUpdate, |
242 | context: this, slot: &QQuickItem::update); |
243 | |
244 | // Overrides the internal input handling to always be true |
245 | // instead of potentially updated after a sync (see updatePaintNode) |
246 | if (isforceInputHandlingSet()) { |
247 | m_enableInputProcessing = true; |
248 | updateInputProcessing(); |
249 | forceActiveFocus(); |
250 | } |
251 | } |
252 | |
253 | QQuick3DViewport::~QQuick3DViewport() |
254 | { |
255 | // If the quick window still exists, make sure to disconnect any of the direct |
256 | // connections to this View3D |
257 | if (auto qw = window()) |
258 | disconnect(sender: qw, signal: nullptr, receiver: this, member: nullptr); |
259 | |
260 | auto sceneManager = QQuick3DObjectPrivate::get(item: m_sceneRoot)->sceneManager; |
261 | if (sceneManager) { |
262 | sceneManager->setParent(nullptr); |
263 | if (auto wa = sceneManager->wattached) |
264 | wa->queueForCleanup(manager: sceneManager); |
265 | } |
266 | |
267 | delete m_sceneRoot; |
268 | m_sceneRoot = nullptr; |
269 | |
270 | // m_renderStats is tightly coupled with the render thread, so can't delete while we |
271 | // might still be rendering. |
272 | m_renderStats->deleteLater(); |
273 | |
274 | // m_directRenderer must be destroyed on the render thread at the proper time, not here. |
275 | // That's handled in releaseResources() + upon sceneGraphInvalidated |
276 | } |
277 | |
278 | static void ssgn_append(QQmlListProperty<QObject> *property, QObject *obj) |
279 | { |
280 | if (!obj) |
281 | return; |
282 | QQuick3DViewport *view3d = static_cast<QQuick3DViewport *>(property->object); |
283 | |
284 | if (QQuick3DObject *sceneObject = qmlobject_cast<QQuick3DObject *>(object: obj)) { |
285 | QQmlListProperty<QObject> itemProperty = QQuick3DObjectPrivate::get(item: view3d->scene())->data(); |
286 | itemProperty.append(&itemProperty, sceneObject); |
287 | } else { |
288 | QQuickItemPrivate::data_append(property, obj); |
289 | } |
290 | } |
291 | |
292 | static qsizetype ssgn_count(QQmlListProperty<QObject> *property) |
293 | { |
294 | QQuick3DViewport *view3d = static_cast<QQuick3DViewport *>(property->object); |
295 | if (!view3d || !view3d->scene() || !QQuick3DObjectPrivate::get(item: view3d->scene())->data().count) |
296 | return 0; |
297 | QQmlListProperty<QObject> itemProperty = QQuick3DObjectPrivate::get(item: view3d->scene())->data(); |
298 | return itemProperty.count(&itemProperty); |
299 | } |
300 | |
301 | static QObject *ssgn_at(QQmlListProperty<QObject> *property, qsizetype i) |
302 | { |
303 | QQuick3DViewport *view3d = static_cast<QQuick3DViewport *>(property->object); |
304 | QQmlListProperty<QObject> itemProperty = QQuick3DObjectPrivate::get(item: view3d->scene())->data(); |
305 | return itemProperty.at(&itemProperty, i); |
306 | } |
307 | |
308 | static void ssgn_clear(QQmlListProperty<QObject> *property) |
309 | { |
310 | QQuick3DViewport *view3d = static_cast<QQuick3DViewport *>(property->object); |
311 | QQmlListProperty<QObject> itemProperty = QQuick3DObjectPrivate::get(item: view3d->scene())->data(); |
312 | return itemProperty.clear(&itemProperty); |
313 | } |
314 | |
315 | |
316 | QQmlListProperty<QObject> QQuick3DViewport::data() |
317 | { |
318 | return QQmlListProperty<QObject>(this, |
319 | nullptr, |
320 | ssgn_append, |
321 | ssgn_count, |
322 | ssgn_at, |
323 | ssgn_clear); |
324 | } |
325 | |
326 | /*! |
327 | \qmlproperty QtQuick3D::Camera QtQuick3D::View3D::camera |
328 | |
329 | This property specifies which \l Camera is used to render the scene. If this |
330 | property is not set, then the first enabled camera in the scene will be used. |
331 | |
332 | \note If this property contains a camera that's not \l {Node::visible}{visible} then |
333 | no further attempts to find a camera will be done. |
334 | |
335 | \sa PerspectiveCamera, OrthographicCamera, FrustumCamera, CustomCamera |
336 | */ |
337 | QQuick3DCamera *QQuick3DViewport::camera() const |
338 | { |
339 | return m_camera; |
340 | } |
341 | |
342 | /*! |
343 | \qmlproperty QtQuick3D::SceneEnvironment QtQuick3D::View3D::environment |
344 | |
345 | This property specifies the SceneEnvironment used to render the scene. |
346 | |
347 | \sa SceneEnvironment |
348 | */ |
349 | QQuick3DSceneEnvironment *QQuick3DViewport::environment() const |
350 | { |
351 | return m_environment; |
352 | } |
353 | |
354 | /*! |
355 | \qmlproperty QtQuick3D::Node QtQuick3D::View3D::scene |
356 | \readonly |
357 | |
358 | Holds the root \l Node of the View3D's scene. |
359 | |
360 | \sa importScene |
361 | */ |
362 | QQuick3DNode *QQuick3DViewport::scene() const |
363 | { |
364 | return m_sceneRoot; |
365 | } |
366 | |
367 | /*! |
368 | \qmlproperty QtQuick3D::Node QtQuick3D::View3D::importScene |
369 | |
370 | This property defines the reference node of the scene to render to the viewport. |
371 | The node does not have to be a child of the View3D. This referenced node becomes |
372 | a sibling with child nodes of View3D, if there are any. |
373 | |
374 | \note This property can only be set once, and subsequent changes will have no |
375 | effect. |
376 | |
377 | \sa Node |
378 | */ |
379 | QQuick3DNode *QQuick3DViewport::importScene() const |
380 | { |
381 | return m_importScene; |
382 | } |
383 | |
384 | /*! |
385 | \qmlproperty enumeration QtQuick3D::View3D::renderMode |
386 | |
387 | This property determines how the View3D is combined with the other parts of the |
388 | Qt Quick scene. |
389 | |
390 | By default, the scene will be rendered into an off-screen buffer as an intermediate |
391 | step. This off-screen buffer is then rendered into the window (or render target) like any |
392 | other Qt Quick \l Item. |
393 | |
394 | For most users, there will be no need to change the render mode, and this property can |
395 | safely be ignored. But on some graphics hardware, the use of an off-screen buffer can be |
396 | a performance bottleneck. If this is the case, it might be worth experimenting with other |
397 | modes. |
398 | |
399 | \value View3D.Offscreen The scene is rendered into an off-screen buffer as an intermediate |
400 | step. This off-screen buffer is then composited with the rest of the Qt Quick scene. |
401 | |
402 | \value View3D.Underlay The scene is rendered directly to the window before the rest of |
403 | the Qt Quick scene is rendered. With this mode, the View3D cannot be placed on top of |
404 | other Qt Quick items. |
405 | |
406 | \value View3D.Overlay The scene is rendered directly to the window after Qt Quick is |
407 | rendered. With this mode, the View3D will always be on top of other Qt Quick items. |
408 | |
409 | \value View3D.Inline The View3D's scene graph is embedded into the main scene graph, |
410 | and the same ordering semantics are applied as to any other Qt Quick \l Item. As this |
411 | mode can lead to subtle issues, depending on the contents of the scene, due to |
412 | injecting depth-based 3D content into a 2D scene graph, it is not recommended to be |
413 | used, unless a specific need arises. |
414 | |
415 | The default is \c{View3D.Offscreen}. |
416 | |
417 | \note When changing the render mode, it is important to note that \c{View3D.Offscreen} (the |
418 | default) is the only mode which guarantees perfect graphics fidelity. The other modes |
419 | all have limitations that can cause visual glitches, so it is important to check that |
420 | the visual output still looks correct when changing this property. |
421 | |
422 | \note When using the Underlay, Overlay, or Inline modes, it can be useful, and in some |
423 | cases, required, to disable the Qt Quick scene graph's depth buffer writes via |
424 | QQuickGraphicsConfiguration::setDepthBufferFor2D() before showing the QQuickWindow or |
425 | QQuickView hosting the View3D item. |
426 | */ |
427 | QQuick3DViewport::RenderMode QQuick3DViewport::renderMode() const |
428 | { |
429 | return m_renderMode; |
430 | } |
431 | |
432 | /*! |
433 | \qmlproperty enumeration QtQuick3D::View3D::renderFormat |
434 | \since 6.4 |
435 | |
436 | This property determines the backing texture's format. Applicable only when |
437 | the View3D is rendering to a texture, for example because the \l renderMode |
438 | is \c{View3D.Offscreen}. |
439 | |
440 | The default is \c{ShaderEffectSource.RGBA8}. |
441 | |
442 | If the format is not supported by the underlying graphics driver at run |
443 | time, RGBA8 is used. |
444 | |
445 | \list |
446 | \li ShaderEffectSource.RGBA8 |
447 | \li ShaderEffectSource.RGBA16F |
448 | \li ShaderEffectSource.RGBA32F |
449 | \endlist |
450 | |
451 | \sa QtQuick::ShaderEffectSource::format, QtQuick::Item::layer.format |
452 | */ |
453 | QQuickShaderEffectSource::Format QQuick3DViewport::renderFormat() const |
454 | { |
455 | return m_renderFormat; |
456 | } |
457 | |
458 | /*! |
459 | \qmlproperty QtQuick3D::RenderStats QtQuick3D::View3D::renderStats |
460 | \readonly |
461 | |
462 | This property provides statistics about the rendering of a frame, such as |
463 | \l {RenderStats::fps}{fps}, \l {RenderStats::frameTime}{frameTime}, |
464 | \l {RenderStats::renderTime}{renderTime}, \l {RenderStats::syncTime}{syncTime}, |
465 | and \l {RenderStats::maxFrameTime}{maxFrameTime}. |
466 | */ |
467 | QQuick3DRenderStats *QQuick3DViewport::renderStats() const |
468 | { |
469 | return m_renderStats; |
470 | } |
471 | |
472 | QQuick3DSceneRenderer *QQuick3DViewport::createRenderer() const |
473 | { |
474 | QQuick3DSceneRenderer *renderer = nullptr; |
475 | |
476 | if (QQuickWindow *qw = window()) { |
477 | auto wa = QQuick3DSceneManager::getOrSetWindowAttachment(window&: *qw); |
478 | auto rci = wa->rci(); |
479 | if (!rci) { |
480 | QSGRendererInterface *rif = qw->rendererInterface(); |
481 | if (QSSG_GUARD(QSGRendererInterface::isApiRhiBased(rif->graphicsApi()))) { |
482 | QRhi *rhi = static_cast<QRhi *>(rif->getResource(window: qw, resource: QSGRendererInterface::RhiResource)); |
483 | QSSG_CHECK_X(rhi != nullptr, "No QRhi from QQuickWindow, this cannot happen" ); |
484 | // The RenderContextInterface, and the objects owned by it (such |
485 | // as, the BufferManager) are always per-QQuickWindow, and so per |
486 | // scenegraph render thread. Hence the association with window. |
487 | // Multiple View3Ds in the same window can use the same rendering |
488 | // infrastructure (so e.g. the same QSSGBufferManager), but two |
489 | // View3D objects in different windows must not, except for certain |
490 | // components that do not work with and own native graphics |
491 | // resources (most notably, QSSGShaderLibraryManager - but this |
492 | // distinction is handled internally by QSSGRenderContextInterface). |
493 | rci = std::make_shared<QSSGRenderContextInterface>(args&: rhi); |
494 | wa->setRci(rci); |
495 | |
496 | // Use DirectConnection to stay on the render thread, if there is one. |
497 | connect(sender: wa, signal: &QQuick3DWindowAttachment::releaseCachedResources, context: this, |
498 | slot: &QQuick3DViewport::onReleaseCachedResources, type: Qt::DirectConnection); |
499 | |
500 | } else { |
501 | qWarning(msg: "The Qt Quick scene is using a rendering method that is not based on QRhi and a 3D graphics API. " |
502 | "Qt Quick 3D is not functional in such an environment. The View3D item is not going to display anything." ); |
503 | } |
504 | } |
505 | |
506 | if (rci) |
507 | renderer = new QQuick3DSceneRenderer(rci); |
508 | } |
509 | |
510 | return renderer; |
511 | } |
512 | |
513 | bool QQuick3DViewport::isTextureProvider() const |
514 | { |
515 | // We can only be a texture provider if we are rendering to a texture first |
516 | if (m_renderMode == QQuick3DViewport::Offscreen) |
517 | return true; |
518 | |
519 | return false; |
520 | } |
521 | |
522 | QSGTextureProvider *QQuick3DViewport::textureProvider() const |
523 | { |
524 | // When Item::layer::enabled == true, QQuickItem will be a texture |
525 | // provider. In this case we should prefer to return the layer rather |
526 | // than the fbo texture. |
527 | if (QQuickItem::isTextureProvider()) |
528 | return QQuickItem::textureProvider(); |
529 | |
530 | // We can only be a texture provider if we are rendering to a texture first |
531 | if (m_renderMode != QQuick3DViewport::Offscreen) |
532 | return nullptr; |
533 | |
534 | QQuickWindow *w = window(); |
535 | if (!w) { |
536 | qWarning(msg: "QSSGView3D::textureProvider: can only be queried on the rendering thread of an exposed window" ); |
537 | return nullptr; |
538 | } |
539 | |
540 | if (!m_node) |
541 | m_node = new SGFramebufferObjectNode; |
542 | return m_node; |
543 | } |
544 | |
545 | class CleanupJob : public QRunnable |
546 | { |
547 | public: |
548 | CleanupJob(QQuick3DSGDirectRenderer *renderer) : m_renderer(renderer) { } |
549 | void run() override { delete m_renderer; } |
550 | private: |
551 | QQuick3DSGDirectRenderer *m_renderer; |
552 | }; |
553 | |
554 | void QQuick3DViewport::releaseResources() |
555 | { |
556 | if (m_directRenderer) { |
557 | window()->scheduleRenderJob(job: new CleanupJob(m_directRenderer), schedule: QQuickWindow::BeforeSynchronizingStage); |
558 | m_directRenderer = nullptr; |
559 | } |
560 | |
561 | m_node = nullptr; |
562 | } |
563 | |
564 | void QQuick3DViewport::cleanupDirectRenderer() |
565 | { |
566 | delete m_directRenderer; |
567 | m_directRenderer = nullptr; |
568 | } |
569 | |
570 | void QQuick3DViewport::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) |
571 | { |
572 | QQuickItem::geometryChange(newGeometry, oldGeometry); |
573 | |
574 | if (newGeometry.size() != oldGeometry.size()) |
575 | update(); |
576 | } |
577 | |
578 | QSGNode *QQuick3DViewport::updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNodeData *) |
579 | { |
580 | // When changing render modes |
581 | if (m_renderModeDirty) { |
582 | if (node) { |
583 | delete node; |
584 | node = nullptr; |
585 | m_node = nullptr; |
586 | m_renderNode = nullptr; |
587 | } |
588 | if (m_directRenderer) { |
589 | delete m_directRenderer; |
590 | m_directRenderer = nullptr; |
591 | } |
592 | } |
593 | |
594 | m_renderModeDirty = false; |
595 | |
596 | switch (m_renderMode) { |
597 | // Direct rendering |
598 | case Underlay: |
599 | Q_FALLTHROUGH(); |
600 | case Overlay: |
601 | setupDirectRenderer(m_renderMode); |
602 | node = nullptr; |
603 | break; |
604 | case Offscreen: |
605 | node = setupOffscreenRenderer(node); |
606 | break; |
607 | case Inline: |
608 | // QSGRenderNode-based rendering |
609 | node = setupInlineRenderer(node); |
610 | break; |
611 | } |
612 | |
613 | if (!isforceInputHandlingSet()) { |
614 | // Implicitly enable internal input processing if any item2ds are present. |
615 | const auto inputHandlingEnabled = |
616 | QQuick3DObjectPrivate::get(item: m_sceneRoot)->sceneManager->inputHandlingEnabled; |
617 | const auto enable = inputHandlingEnabled > 0; |
618 | if (m_enableInputProcessing != enable) { |
619 | m_enableInputProcessing = enable; |
620 | QMetaObject::invokeMethod(obj: this, member: "updateInputProcessing" , c: Qt::QueuedConnection); |
621 | } |
622 | } |
623 | |
624 | return node; |
625 | } |
626 | |
627 | void QQuick3DViewport::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value) |
628 | { |
629 | if (change == ItemSceneChange) { |
630 | if (value.window) { |
631 | // TODO: if we want to support multiple windows, there has to be a scene manager for |
632 | // every window. |
633 | QQuick3DObjectPrivate::get(item: m_sceneRoot)->sceneManager->setWindow(value.window); |
634 | if (m_importScene) |
635 | QQuick3DObjectPrivate::get(item: m_importScene)->sceneManager->setWindow(value.window); |
636 | m_renderStats->setWindow(value.window); |
637 | } |
638 | } else if (change == ItemVisibleHasChanged && isVisible()) { |
639 | update(); |
640 | } |
641 | } |
642 | |
643 | bool QQuick3DViewport::event(QEvent *event) |
644 | { |
645 | if (m_enableInputProcessing && event->isPointerEvent()) |
646 | return internalPick(event: static_cast<QPointerEvent *>(event)); |
647 | else |
648 | return QQuickItem::event(event); |
649 | } |
650 | |
651 | void QQuick3DViewport::componentComplete() |
652 | { |
653 | QQuickItem::componentComplete(); |
654 | Q_QUICK3D_PROFILE_REGISTER(this); |
655 | } |
656 | |
657 | void QQuick3DViewport::setCamera(QQuick3DCamera *camera) |
658 | { |
659 | if (m_camera == camera) |
660 | return; |
661 | |
662 | if (camera && !camera->parentItem()) |
663 | camera->setParentItem(m_sceneRoot); |
664 | if (camera) |
665 | camera->updateGlobalVariables(inViewport: QRect(0, 0, width(), height())); |
666 | |
667 | QQuick3DObjectPrivate::attachWatcherPriv(sceneContext: m_sceneRoot, callContext: this, setter: &QQuick3DViewport::setCamera, newO: camera, oldO: m_camera); |
668 | |
669 | m_camera = camera; |
670 | emit cameraChanged(); |
671 | update(); |
672 | } |
673 | |
674 | void QQuick3DViewport::setEnvironment(QQuick3DSceneEnvironment *environment) |
675 | { |
676 | if (m_environment == environment) |
677 | return; |
678 | |
679 | m_environment = environment; |
680 | if (m_environment && !m_environment->parentItem()) |
681 | m_environment->setParentItem(m_sceneRoot); |
682 | emit environmentChanged(); |
683 | update(); |
684 | } |
685 | |
686 | void QQuick3DViewport::setImportScene(QQuick3DNode *inScene) |
687 | { |
688 | // ### We may need consider the case where there is |
689 | // already a scene tree here |
690 | // FIXME : Only the first importScene is an effective one |
691 | if (m_importScene) |
692 | return; |
693 | |
694 | // FIXME : Check self-import or cross-import |
695 | // Currently it does not work since importScene qml parsed in a reverse order. |
696 | QQuick3DNode *scene = inScene; |
697 | while (scene) { |
698 | if (m_sceneRoot == scene) { |
699 | qmlWarning(me: this) << "Cannot allow self-import or cross-import!" ; |
700 | return; |
701 | } |
702 | |
703 | QQuick3DSceneRootNode *rn = qobject_cast<QQuick3DSceneRootNode *>(object: scene); |
704 | scene = rn ? rn->view3D()->importScene() : nullptr; |
705 | } |
706 | |
707 | m_importScene = inScene; |
708 | if (m_importScene) { |
709 | auto privateObject = QQuick3DObjectPrivate::get(item: m_importScene); |
710 | if (!privateObject->sceneManager) { |
711 | // If object doesn't already have scene manager, check from its children |
712 | QQuick3DSceneManager *manager = findChildSceneManager(inObject: m_importScene); |
713 | // If still not found, use the one from the scene root (scenes defined outside of an view3d) |
714 | if (!manager) |
715 | manager = QQuick3DObjectPrivate::get(item: m_sceneRoot)->sceneManager; |
716 | if (manager) { |
717 | manager->setWindow(window()); |
718 | privateObject->refSceneManager(*manager); |
719 | } |
720 | // At this point some manager will exist |
721 | Q_ASSERT(privateObject->sceneManager); |
722 | } |
723 | |
724 | connect(sender: privateObject->sceneManager, signal: &QQuick3DSceneManager::needsUpdate, |
725 | context: this, slot: &QQuickItem::update); |
726 | |
727 | QQuick3DNode *scene = inScene; |
728 | while (scene) { |
729 | QQuick3DSceneRootNode *rn = qobject_cast<QQuick3DSceneRootNode *>(object: scene); |
730 | scene = rn ? rn->view3D()->importScene() : nullptr; |
731 | |
732 | if (scene) { |
733 | connect(sender: QQuick3DObjectPrivate::get(item: scene)->sceneManager, |
734 | signal: &QQuick3DSceneManager::needsUpdate, |
735 | context: this, slot: &QQuickItem::update); |
736 | } |
737 | } |
738 | } |
739 | |
740 | emit importSceneChanged(); |
741 | update(); |
742 | } |
743 | |
744 | void QQuick3DViewport::setRenderMode(QQuick3DViewport::RenderMode renderMode) |
745 | { |
746 | if (m_renderMode == renderMode) |
747 | return; |
748 | |
749 | m_renderMode = renderMode; |
750 | m_renderModeDirty = true; |
751 | emit renderModeChanged(); |
752 | update(); |
753 | } |
754 | |
755 | void QQuick3DViewport::setRenderFormat(QQuickShaderEffectSource::Format format) |
756 | { |
757 | if (m_renderFormat == format) |
758 | return; |
759 | |
760 | m_renderFormat = format; |
761 | m_renderModeDirty = true; |
762 | emit renderFormatChanged(); |
763 | update(); |
764 | } |
765 | |
766 | |
767 | /*! |
768 | \qmlmethod vector3d View3D::mapFrom3DScene(vector3d scenePos) |
769 | |
770 | Transforms \a scenePos from scene space (3D) into view space (2D). |
771 | |
772 | The returned x- and y-values will be be in view coordinates, with the top-left |
773 | corner at [0, 0] and the bottom-right corner at [width, height]. The returned |
774 | z-value contains the distance from the near clip plane of the frustum (clipNear) to |
775 | \a scenePos in scene coordinates. If the distance is negative, that means the \a scenePos |
776 | is behind the active camera. If \a scenePos cannot be mapped to a position in the scene, |
777 | a position of [0, 0, 0] is returned. |
778 | |
779 | This function requires that \l camera is assigned to the view. |
780 | |
781 | \sa mapTo3DScene(), {Camera::mapToViewport()}{Camera.mapToViewport()} |
782 | */ |
783 | QVector3D QQuick3DViewport::mapFrom3DScene(const QVector3D &scenePos) const |
784 | { |
785 | if (!m_camera) { |
786 | qmlWarning(me: this) << "Cannot resolve view position without a camera assigned!" ; |
787 | return QVector3D(0, 0, 0); |
788 | } |
789 | |
790 | qreal _width = width(); |
791 | qreal _height = height(); |
792 | if (_width == 0 || _height == 0) |
793 | return QVector3D(0, 0, 0); |
794 | |
795 | const QVector3D normalizedPos = m_camera->mapToViewport(scenePos, width: _width, height: _height); |
796 | return normalizedPos * QVector3D(float(_width), float(_height), 1); |
797 | } |
798 | |
799 | /*! |
800 | \qmlmethod vector3d View3D::mapTo3DScene(vector3d viewPos) |
801 | |
802 | Transforms \a viewPos from view space (2D) into scene space (3D). |
803 | |
804 | The x- and y-values of \a viewPos should be in view coordinates, with the top-left |
805 | corner at [0, 0] and the bottom-right corner at [width, height]. The z-value is |
806 | interpreted as the distance from the near clip plane of the frustum (clipNear) in |
807 | scene coordinates. |
808 | |
809 | If \a viewPos cannot successfully be mapped to a position in the scene, a position of |
810 | [0, 0, 0] is returned. |
811 | |
812 | This function requires that a \l camera is assigned to the view. |
813 | |
814 | \sa mapFrom3DScene(), {Camera::mapFromViewport}{Camera.mapFromViewport()} |
815 | */ |
816 | QVector3D QQuick3DViewport::mapTo3DScene(const QVector3D &viewPos) const |
817 | { |
818 | if (!m_camera) { |
819 | qmlWarning(me: this) << "Cannot resolve scene position without a camera assigned!" ; |
820 | return QVector3D(0, 0, 0); |
821 | } |
822 | |
823 | qreal _width = width(); |
824 | qreal _height = height(); |
825 | if (_width == 0 || _height == 0) |
826 | return QVector3D(0, 0, 0); |
827 | |
828 | const QVector3D normalizedPos = viewPos / QVector3D(float(_width), float(_height), 1); |
829 | return m_camera->mapFromViewport(viewportPos: normalizedPos, width: _width, height: _height); |
830 | } |
831 | |
832 | /*! |
833 | \qmlmethod PickResult View3D::pick(float x, float y) |
834 | |
835 | This method will "shoot" a ray into the scene from view coordinates \a x and \a y |
836 | and return information about the nearest intersection with an object in the scene. |
837 | |
838 | This can, for instance, be called with mouse coordinates to find the object under the mouse cursor. |
839 | */ |
840 | QQuick3DPickResult QQuick3DViewport::pick(float x, float y) const |
841 | { |
842 | QQuick3DSceneRenderer *renderer = getRenderer(); |
843 | if (!renderer) |
844 | return QQuick3DPickResult(); |
845 | |
846 | const QPointF position(qreal(x) * window()->effectiveDevicePixelRatio(), |
847 | qreal(y) * window()->effectiveDevicePixelRatio()); |
848 | std::optional<QSSGRenderRay> rayResult = renderer->getRayFromViewportPos(pos: position); |
849 | if (!rayResult.has_value()) |
850 | return QQuick3DPickResult(); |
851 | |
852 | return processPickResult(pickResult: renderer->syncPick(ray: rayResult.value())); |
853 | |
854 | } |
855 | |
856 | /*! |
857 | \qmlmethod List<PickResult> View3D::pickAll(float x, float y) |
858 | |
859 | This method will "shoot" a ray into the scene from view coordinates \a x and \a y |
860 | and return a list of information about intersections with objects in the scene. |
861 | The returned list is sorted by distance from the camera with the nearest |
862 | intersections appearing first and the furthest appearing last. |
863 | |
864 | This can, for instance, be called with mouse coordinates to find the object under the mouse cursor. |
865 | |
866 | \since 6.2 |
867 | */ |
868 | QList<QQuick3DPickResult> QQuick3DViewport::pickAll(float x, float y) const |
869 | { |
870 | QQuick3DSceneRenderer *renderer = getRenderer(); |
871 | if (!renderer) |
872 | return QList<QQuick3DPickResult>(); |
873 | |
874 | const QPointF position(qreal(x) * window()->effectiveDevicePixelRatio(), |
875 | qreal(y) * window()->effectiveDevicePixelRatio()); |
876 | std::optional<QSSGRenderRay> rayResult = renderer->getRayFromViewportPos(pos: position); |
877 | if (!rayResult.has_value()) |
878 | return QList<QQuick3DPickResult>(); |
879 | |
880 | const auto resultList = renderer->syncPickAll(ray: rayResult.value()); |
881 | QList<QQuick3DPickResult> processedResultList; |
882 | processedResultList.reserve(asize: resultList.size()); |
883 | for (const auto &result : resultList) |
884 | processedResultList.append(t: processPickResult(pickResult: result)); |
885 | |
886 | return processedResultList; |
887 | } |
888 | |
889 | /*! |
890 | \qmlmethod PickResult View3D::rayPick(vector3d origin, vector3d direction) |
891 | |
892 | This method will "shoot" a ray into the scene starting at \a origin and in |
893 | \a direction and return information about the nearest intersection with an |
894 | object in the scene. |
895 | |
896 | This can, for instance, be called with the position and forward vector of |
897 | any object in a scene to see what object is in front of an item. This |
898 | makes it possible to do picking from any point in the scene. |
899 | |
900 | \since 6.2 |
901 | */ |
902 | QQuick3DPickResult QQuick3DViewport::rayPick(const QVector3D &origin, const QVector3D &direction) const |
903 | { |
904 | QQuick3DSceneRenderer *renderer = getRenderer(); |
905 | if (!renderer) |
906 | return QQuick3DPickResult(); |
907 | |
908 | const QSSGRenderRay ray(origin, direction); |
909 | |
910 | return processPickResult(pickResult: renderer->syncPick(ray)); |
911 | } |
912 | |
913 | /*! |
914 | \qmlmethod List<PickResult> View3D::rayPickAll(vector3d origin, vector3d direction) |
915 | |
916 | This method will "shoot" a ray into the scene starting at \a origin and in |
917 | \a direction and return a list of information about the nearest intersections with |
918 | objects in the scene. |
919 | The list is presorted by distance from the origin along the direction |
920 | vector with the nearest intersections appearing first and the furthest |
921 | appearing last. |
922 | |
923 | This can, for instance, be called with the position and forward vector of |
924 | any object in a scene to see what objects are in front of an item. This |
925 | makes it possible to do picking from any point in the scene. |
926 | |
927 | \since 6.2 |
928 | */ |
929 | QList<QQuick3DPickResult> QQuick3DViewport::rayPickAll(const QVector3D &origin, const QVector3D &direction) const |
930 | { |
931 | QQuick3DSceneRenderer *renderer = getRenderer(); |
932 | if (!renderer) |
933 | return QList<QQuick3DPickResult>(); |
934 | |
935 | const QSSGRenderRay ray(origin, direction); |
936 | |
937 | const auto resultList = renderer->syncPickAll(ray); |
938 | QList<QQuick3DPickResult> processedResultList; |
939 | processedResultList.reserve(asize: resultList.size()); |
940 | for (const auto &result : resultList) |
941 | processedResultList.append(t: processPickResult(pickResult: result)); |
942 | |
943 | return processedResultList; |
944 | } |
945 | |
946 | void QQuick3DViewport::processPointerEventFromRay(const QVector3D &origin, const QVector3D &direction, QPointerEvent *event) |
947 | { |
948 | internalPick(event, origin, direction); |
949 | } |
950 | |
951 | QQuick3DLightmapBaker *QQuick3DViewport::maybeLightmapBaker() |
952 | { |
953 | return m_lightmapBaker; |
954 | } |
955 | |
956 | QQuick3DLightmapBaker *QQuick3DViewport::lightmapBaker() |
957 | { |
958 | if (!m_lightmapBaker) |
959 | m_lightmapBaker= new QQuick3DLightmapBaker(this); |
960 | |
961 | return m_lightmapBaker; |
962 | } |
963 | |
964 | /*! |
965 | \internal |
966 | */ |
967 | void QQuick3DViewport::bakeLightmap() |
968 | { |
969 | lightmapBaker()->bake(); |
970 | } |
971 | |
972 | void QQuick3DViewport::setGlobalPickingEnabled(bool isEnabled) |
973 | { |
974 | QQuick3DSceneRenderer *renderer = getRenderer(); |
975 | if (!renderer) |
976 | return; |
977 | |
978 | renderer->setGlobalPickingEnabled(isEnabled); |
979 | } |
980 | |
981 | void QQuick3DViewport::invalidateSceneGraph() |
982 | { |
983 | m_node = nullptr; |
984 | } |
985 | |
986 | QQuick3DSceneRenderer *QQuick3DViewport::getRenderer() const |
987 | { |
988 | QQuick3DSceneRenderer *renderer = nullptr; |
989 | if (m_node) { |
990 | renderer = m_node->renderer; |
991 | } else if (m_renderNode) { |
992 | renderer = m_renderNode->renderer; |
993 | } else if (m_directRenderer) { |
994 | renderer = m_directRenderer->renderer(); |
995 | } |
996 | return renderer; |
997 | } |
998 | |
999 | void QQuick3DViewport::updateDynamicTextures() |
1000 | { |
1001 | // Update QSGDynamicTextures that are used for source textures and Quick items |
1002 | // Must be called on the render thread. |
1003 | |
1004 | const auto &sceneManager = QQuick3DObjectPrivate::get(item: m_sceneRoot)->sceneManager; |
1005 | for (auto *texture : std::as_const(t&: sceneManager->qsgDynamicTextures)) |
1006 | texture->updateTexture(); |
1007 | |
1008 | QQuick3DNode *scene = m_importScene; |
1009 | while (scene) { |
1010 | const auto &importSm = QQuick3DObjectPrivate::get(item: scene)->sceneManager; |
1011 | if (importSm != sceneManager) { |
1012 | for (auto *texture : std::as_const(t&: importSm->qsgDynamicTextures)) |
1013 | texture->updateTexture(); |
1014 | } |
1015 | |
1016 | // if importScene has another import |
1017 | QQuick3DSceneRootNode *rn = qobject_cast<QQuick3DSceneRootNode *>(object: scene); |
1018 | scene = rn ? rn->view3D()->importScene() : nullptr; |
1019 | } |
1020 | } |
1021 | |
1022 | QSGNode *QQuick3DViewport::setupOffscreenRenderer(QSGNode *node) |
1023 | { |
1024 | SGFramebufferObjectNode *n = static_cast<SGFramebufferObjectNode *>(node); |
1025 | |
1026 | if (!n) { |
1027 | if (!m_node) |
1028 | m_node = new SGFramebufferObjectNode; |
1029 | n = m_node; |
1030 | } |
1031 | |
1032 | if (!n->renderer) { |
1033 | n->window = window(); |
1034 | n->renderer = createRenderer(); |
1035 | if (!n->renderer) |
1036 | return nullptr; |
1037 | n->renderer->fboNode = n; |
1038 | n->quickFbo = this; |
1039 | connect(sender: window(), SIGNAL(screenChanged(QScreen*)), receiver: n, SLOT(handleScreenChange())); |
1040 | } |
1041 | QSize minFboSize = QQuickItemPrivate::get(item: this)->sceneGraphContext()->minimumFBOSize(); |
1042 | QSize desiredFboSize(qMax<int>(a: minFboSize.width(), b: width()), |
1043 | qMax<int>(a: minFboSize.height(), b: height())); |
1044 | |
1045 | n->devicePixelRatio = window()->effectiveDevicePixelRatio(); |
1046 | desiredFboSize *= n->devicePixelRatio; |
1047 | |
1048 | n->setFiltering(smooth() ? QSGTexture::Linear : QSGTexture::Nearest); |
1049 | n->setRect(x: 0, y: 0, w: width(), h: height()); |
1050 | if (checkIsVisible() && isComponentComplete()) { |
1051 | n->renderer->synchronize(view3D: this, size: desiredFboSize, dpr: n->devicePixelRatio); |
1052 | if (n->renderer->m_textureNeedsFlip) |
1053 | n->setTextureCoordinatesTransform(QSGSimpleTextureNode::MirrorVertically); |
1054 | updateDynamicTextures(); |
1055 | n->scheduleRender(); |
1056 | } |
1057 | |
1058 | return n; |
1059 | } |
1060 | |
1061 | QSGNode *QQuick3DViewport::setupInlineRenderer(QSGNode *node) |
1062 | { |
1063 | QQuick3DSGRenderNode *n = static_cast<QQuick3DSGRenderNode *>(node); |
1064 | if (!n) { |
1065 | if (!m_renderNode) |
1066 | m_renderNode = new QQuick3DSGRenderNode; |
1067 | n = m_renderNode; |
1068 | } |
1069 | |
1070 | if (!n->renderer) { |
1071 | n->window = window(); |
1072 | n->renderer = createRenderer(); |
1073 | if (!n->renderer) |
1074 | return nullptr; |
1075 | } |
1076 | |
1077 | const QSize targetSize = window()->effectiveDevicePixelRatio() * QSize(width(), height()); |
1078 | |
1079 | // checkIsVisible, not isVisible, because, for example, a |
1080 | // { visible: false; layer.enabled: true } item still needs |
1081 | // to function normally. |
1082 | if (checkIsVisible() && isComponentComplete()) { |
1083 | n->renderer->synchronize(view3D: this, size: targetSize, dpr: window()->effectiveDevicePixelRatio()); |
1084 | updateDynamicTextures(); |
1085 | n->markDirty(bits: QSGNode::DirtyMaterial); |
1086 | } |
1087 | |
1088 | return n; |
1089 | } |
1090 | |
1091 | |
1092 | void QQuick3DViewport::setupDirectRenderer(RenderMode mode) |
1093 | { |
1094 | auto renderMode = (mode == Underlay) ? QQuick3DSGDirectRenderer::Underlay |
1095 | : QQuick3DSGDirectRenderer::Overlay; |
1096 | if (!m_directRenderer) { |
1097 | QQuick3DSceneRenderer *sceneRenderer = createRenderer(); |
1098 | if (!sceneRenderer) |
1099 | return; |
1100 | m_directRenderer = new QQuick3DSGDirectRenderer(sceneRenderer, window(), renderMode); |
1101 | connect(sender: window(), signal: &QQuickWindow::sceneGraphInvalidated, context: this, slot: &QQuick3DViewport::cleanupDirectRenderer, type: Qt::DirectConnection); |
1102 | } |
1103 | |
1104 | const QSizeF targetSize = window()->effectiveDevicePixelRatio() * QSizeF(width(), height()); |
1105 | m_directRenderer->setViewport(QRectF(window()->effectiveDevicePixelRatio() * mapToScene(point: QPointF(0, 0)), targetSize)); |
1106 | m_directRenderer->setVisibility(isVisible()); |
1107 | if (isVisible()) { |
1108 | m_directRenderer->renderer()->synchronize(view3D: this, size: targetSize.toSize(), dpr: window()->effectiveDevicePixelRatio()); |
1109 | updateDynamicTextures(); |
1110 | m_directRenderer->requestRender(); |
1111 | } |
1112 | } |
1113 | |
1114 | // This is used for offscreen mode since we need to check if |
1115 | // this item is used by an effect but hidden |
1116 | bool QQuick3DViewport::checkIsVisible() const |
1117 | { |
1118 | auto childPrivate = QQuickItemPrivate::get(item: this); |
1119 | return (childPrivate->explicitVisible || |
1120 | (childPrivate->extra.isAllocated() && childPrivate->extra->effectRefCount)); |
1121 | |
1122 | } |
1123 | |
1124 | bool QQuick3DViewport::internalPick(QPointerEvent *event, const QVector3D &origin, const QVector3D &direction) const |
1125 | { |
1126 | // Some non-thread-safe stuff to do input |
1127 | // First need to get a handle to the renderer |
1128 | QQuick3DSceneRenderer *renderer = getRenderer(); |
1129 | if (!renderer || !event) |
1130 | return false; |
1131 | |
1132 | const bool isHover = QQuickDeliveryAgentPrivate::isHoverEvent(ev: event); |
1133 | if (!isHover) |
1134 | qCDebug(lcEv) << event; |
1135 | struct SubsceneInfo { |
1136 | QQuick3DObject* obj = nullptr; |
1137 | QVarLengthArray<QPointF, 16> eventPointScenePositions; |
1138 | }; |
1139 | QFlatMap<QQuickItem*, SubsceneInfo> visitedSubscenes; |
1140 | |
1141 | const bool useRayPicking = !direction.isNull(); |
1142 | |
1143 | for (int pointIndex = 0; pointIndex < event->pointCount(); ++pointIndex) { |
1144 | QQuick3DSceneRenderer::PickResultList pickResults; |
1145 | auto &eventPoint = event->point(i: pointIndex); |
1146 | if (useRayPicking) { |
1147 | const QSSGRenderRay ray(origin, direction); |
1148 | pickResults = renderer->syncPickAll(ray); |
1149 | } else { |
1150 | const QPointF realPosition = eventPoint.position() * window()->effectiveDevicePixelRatio(); |
1151 | std::optional<QSSGRenderRay> rayResult = renderer->getRayFromViewportPos(pos: realPosition); |
1152 | if (rayResult.has_value()) |
1153 | pickResults = renderer->syncPickAll(ray: rayResult.value()); |
1154 | } |
1155 | if (!isHover) |
1156 | qCDebug(lcPick) << pickResults.size() << "pick results for" << event->point(i: pointIndex); |
1157 | if (pickResults.isEmpty()) { |
1158 | eventPoint.setAccepted(false); // let it fall through the viewport to Items underneath |
1159 | continue; // next eventPoint |
1160 | } |
1161 | |
1162 | const auto sceneManager = QQuick3DObjectPrivate::get(item: m_sceneRoot)->sceneManager; |
1163 | |
1164 | for (const auto &pickResult : pickResults) { |
1165 | auto backendObject = pickResult.m_hitObject; |
1166 | if (!backendObject) |
1167 | continue; |
1168 | |
1169 | QQuick3DObject *frontendObject = sceneManager->lookUpNode(node: backendObject); |
1170 | if (!frontendObject && m_importScene) { |
1171 | const auto importSceneManager = QQuick3DObjectPrivate::get(item: m_importScene)->sceneManager; |
1172 | frontendObject = importSceneManager->lookUpNode(node: backendObject); |
1173 | } |
1174 | |
1175 | if (!frontendObject) |
1176 | continue; |
1177 | |
1178 | QQuickItem *subsceneRootItem = nullptr; |
1179 | QPointF subscenePosition; |
1180 | auto frontendObjectPrivate = QQuick3DObjectPrivate::get(item: frontendObject); |
1181 | if (frontendObjectPrivate->type == QQuick3DObjectPrivate::Type::Item2D) { |
1182 | // Item2D |
1183 | auto item2D = qobject_cast<QQuick3DItem2D *>(object: frontendObject); |
1184 | if (item2D) |
1185 | subsceneRootItem = item2D->contentItem(); |
1186 | if (!subsceneRootItem || subsceneRootItem->childItems().isEmpty()) |
1187 | continue; // ignore empty 2D subscenes |
1188 | // In this case the "UV" coordinates are in pixels in the subscene root item, so we can just use them. |
1189 | subscenePosition = pickResult.m_localUVCoords.toPointF(); |
1190 | // Even though an Item2D is an "infinite plane" for rendering purposes, |
1191 | // avoid delivering events outside the rectangular area that is occupied by child items, |
1192 | // so that events can "fall through" to other interactive content in the scene or behind it. |
1193 | if (!subsceneRootItem->childrenRect().contains(p: subscenePosition)) |
1194 | continue; |
1195 | } else if (frontendObjectPrivate->type == QQuick3DObjectPrivate::Type::Model) { |
1196 | // Model |
1197 | int materialSubset = pickResult.m_subset; |
1198 | const auto backendModel = static_cast<const QSSGRenderModel *>(backendObject); |
1199 | // Get material |
1200 | if (backendModel->materials.size() < (pickResult.m_subset + 1)) |
1201 | materialSubset = backendModel->materials.size() - 1; |
1202 | if (materialSubset < 0) |
1203 | continue; |
1204 | const auto backendMaterial = backendModel->materials.at(i: materialSubset); |
1205 | const auto frontendMaterial = sceneManager->lookUpNode(node: backendMaterial); |
1206 | if (!frontendMaterial) |
1207 | continue; |
1208 | const auto frontendMaterialPrivate = QQuick3DObjectPrivate::get(item: frontendMaterial); |
1209 | |
1210 | if (frontendMaterialPrivate->type == QQuick3DObjectPrivate::Type::DefaultMaterial) { |
1211 | // Default Material |
1212 | const auto defaultMaterial = qobject_cast<QQuick3DDefaultMaterial *>(object: frontendMaterial); |
1213 | if (defaultMaterial) { |
1214 | // Just check for a diffuseMap for now |
1215 | if (defaultMaterial->diffuseMap() && defaultMaterial->diffuseMap()->sourceItem()) |
1216 | subsceneRootItem = defaultMaterial->diffuseMap()->sourceItem(); |
1217 | } |
1218 | |
1219 | } else if (frontendMaterialPrivate->type == QQuick3DObjectPrivate::Type::PrincipledMaterial) { |
1220 | // Principled Material |
1221 | const auto principledMaterial = qobject_cast<QQuick3DPrincipledMaterial *>(object: frontendMaterial); |
1222 | if (principledMaterial) { |
1223 | // Just check for a baseColorMap for now |
1224 | if (principledMaterial->baseColorMap() && principledMaterial->baseColorMap()->sourceItem()) |
1225 | subsceneRootItem = principledMaterial->baseColorMap()->sourceItem(); |
1226 | } |
1227 | } else if (frontendMaterialPrivate->type == QQuick3DObjectPrivate::Type::SpecularGlossyMaterial) { |
1228 | // SpecularGlossy Material |
1229 | const auto specularGlossyMaterial = qobject_cast<QQuick3DSpecularGlossyMaterial *>(object: frontendMaterial); |
1230 | if (specularGlossyMaterial) { |
1231 | // Just check for a albedoMap for now |
1232 | if (specularGlossyMaterial->albedoMap() && specularGlossyMaterial->albedoMap()->sourceItem()) |
1233 | subsceneRootItem = specularGlossyMaterial->albedoMap()->sourceItem(); |
1234 | } |
1235 | } else if (frontendMaterialPrivate->type == QQuick3DObjectPrivate::Type::CustomMaterial) { |
1236 | // Custom Material |
1237 | const auto customMaterial = qobject_cast<QQuick3DCustomMaterial *>(object: frontendMaterial); |
1238 | if (customMaterial) { |
1239 | // This case is a bit harder because we can not know how the textures will be used |
1240 | const auto &texturesInputs = customMaterial->m_dynamicTextureMaps; |
1241 | for (const auto &textureInput : texturesInputs) { |
1242 | if (auto texture = textureInput->texture()) { |
1243 | if (texture->sourceItem()) { |
1244 | subsceneRootItem = texture->sourceItem(); |
1245 | break; |
1246 | } |
1247 | } |
1248 | } |
1249 | } |
1250 | } |
1251 | if (subsceneRootItem) { |
1252 | // In this case the pick result really is using UV coordinates. |
1253 | subscenePosition = QPointF(subsceneRootItem->x() + |
1254 | pickResult.m_localUVCoords.x() * subsceneRootItem->width(), |
1255 | subsceneRootItem->y() - |
1256 | pickResult.m_localUVCoords.y() * subsceneRootItem->height() + |
1257 | subsceneRootItem->height()); |
1258 | } |
1259 | } |
1260 | |
1261 | if (subsceneRootItem) { |
1262 | SubsceneInfo &subscene = visitedSubscenes[subsceneRootItem]; // create if not found |
1263 | subscene.obj = frontendObject; |
1264 | if (subscene.eventPointScenePositions.size() != event->pointCount()) { |
1265 | // ensure capacity, and use an out-of-scene position rather than 0,0 by default |
1266 | constexpr QPointF inf(-qt_inf(), -qt_inf()); |
1267 | subscene.eventPointScenePositions.resize(sz: event->pointCount(), v: inf); |
1268 | } |
1269 | subscene.eventPointScenePositions[pointIndex] = subscenePosition; |
1270 | } |
1271 | if (isHover) |
1272 | qCDebug(lcHover) << "hover pick result:" << frontendObject << "@" << pickResult.m_scenePosition |
1273 | << "UV" << pickResult.m_localUVCoords << "dist" << qSqrt(v: pickResult.m_distanceSq) |
1274 | << "scene" << subscenePosition << subsceneRootItem; |
1275 | else |
1276 | qCDebug(lcPick) << "pick result:" << frontendObject << "@" << pickResult.m_scenePosition |
1277 | << "UV" << pickResult.m_localUVCoords << "dist" << qSqrt(v: pickResult.m_distanceSq) |
1278 | << "scene" << subscenePosition << subsceneRootItem; |
1279 | } // for pick results from each QEventPoint |
1280 | } // for each QEventPoint |
1281 | |
1282 | // Now deliver the entire event (all points) to each relevant subscene. |
1283 | // Maybe only some points fall inside, but QQuickDeliveryAgentPrivate::deliverMatchingPointsToItem() |
1284 | // makes reduced-subset touch events that contain only the relevant points, when necessary. |
1285 | bool ret = false; |
1286 | |
1287 | // ViewportTransformHelper::removeAll(); |
1288 | QVarLengthArray<QPointF, 16> originalScenePositions; |
1289 | originalScenePositions.resize(sz: event->pointCount()); |
1290 | for (int pointIndex = 0; pointIndex < event->pointCount(); ++pointIndex) |
1291 | originalScenePositions[pointIndex] = event->point(i: pointIndex).scenePosition(); |
1292 | for (auto subscene : visitedSubscenes) { |
1293 | QQuickItem *subsceneRoot = subscene.first; |
1294 | auto &subsceneInfo = subscene.second; |
1295 | Q_ASSERT(subsceneInfo.eventPointScenePositions.size() == event->pointCount()); |
1296 | auto da = QQuickItemPrivate::get(item: subsceneRoot)->deliveryAgent(); |
1297 | if (!isHover) |
1298 | qCDebug(lcPick) << "delivering to" << subsceneRoot << "via" << da << event; |
1299 | |
1300 | for (int pointIndex = 0; pointIndex < event->pointCount(); ++pointIndex) { |
1301 | const auto &pt = subsceneInfo.eventPointScenePositions.at(idx: pointIndex); |
1302 | // By tradition, QGuiApplicationPrivate::processTouchEvent() has set the local position to the scene position, |
1303 | // and Qt Quick expects it to arrive that way: then QQuickDeliveryAgentPrivate::translateTouchEvent() |
1304 | // copies it into the scene position before localizing. |
1305 | // That might be silly, we might change it eventually, but gotta stay consistent for now. |
1306 | QEventPoint &ep = event->point(i: pointIndex); |
1307 | QMutableEventPoint::setPosition(p&: ep, arg: pt); |
1308 | QMutableEventPoint::setScenePosition(p&: ep, arg: pt); |
1309 | } |
1310 | |
1311 | if (event->isBeginEvent()) |
1312 | da->setSceneTransform(nullptr); |
1313 | if (da->event(ev: event)) { |
1314 | ret = true; |
1315 | if (QQuickDeliveryAgentPrivate::anyPointGrabbed(ev: event) && !useRayPicking) { |
1316 | // In case any QEventPoint was grabbed, the relevant QQuickDeliveryAgent needs to know |
1317 | // how to repeat the picking/coordinate transformation for each update, |
1318 | // because delivery will bypass internalPick() due to the grab, and it's |
1319 | // more efficient to avoid whole-scene picking each time anyway. |
1320 | auto frontendObjectPrivate = QQuick3DObjectPrivate::get(item: subsceneInfo.obj); |
1321 | const bool item2Dcase = (frontendObjectPrivate->type == QQuick3DObjectPrivate::Type::Item2D); |
1322 | ViewportTransformHelper *transform = new ViewportTransformHelper; |
1323 | transform->renderer = renderer; |
1324 | transform->sceneParentNode = static_cast<QSSGRenderNode*>(frontendObjectPrivate->spatialNode); |
1325 | transform->targetItem = subsceneRoot; |
1326 | transform->dpr = window()->effectiveDevicePixelRatio(); |
1327 | transform->uvCoordsArePixels = item2Dcase; |
1328 | transform->setOnDeliveryAgent(da); |
1329 | qCDebug(lcPick) << event->type() << "created ViewportTransformHelper on" << da; |
1330 | } |
1331 | } else if (event->type() != QEvent::HoverMove) { |
1332 | qCDebug(lcPick) << subsceneRoot << "didn't want" << event; |
1333 | } |
1334 | event->setAccepted(false); // reject implicit grab and let it keep propagating |
1335 | } |
1336 | if (visitedSubscenes.isEmpty()) { |
1337 | event->setAccepted(false); |
1338 | } else { |
1339 | for (int pointIndex = 0; pointIndex < event->pointCount(); ++pointIndex) |
1340 | QMutableEventPoint::setScenePosition(p&: event->point(i: pointIndex), arg: originalScenePositions.at(idx: pointIndex)); |
1341 | } |
1342 | return ret; |
1343 | } |
1344 | |
1345 | QQuick3DPickResult QQuick3DViewport::processPickResult(const QSSGRenderPickResult &pickResult) const |
1346 | { |
1347 | if (!pickResult.m_hitObject) |
1348 | return QQuick3DPickResult(); |
1349 | |
1350 | auto backendObject = pickResult.m_hitObject; |
1351 | const auto sceneManager = QQuick3DObjectPrivate::get(item: m_sceneRoot)->sceneManager; |
1352 | QQuick3DObject *frontendObject = sceneManager->lookUpNode(node: backendObject); |
1353 | |
1354 | // FIXME : for the case of consecutive importScenes |
1355 | if (!frontendObject && m_importScene) { |
1356 | const auto importSceneManager = QQuick3DObjectPrivate::get(item: m_importScene)->sceneManager; |
1357 | frontendObject = importSceneManager->lookUpNode(node: backendObject); |
1358 | } |
1359 | |
1360 | QQuick3DModel *model = qobject_cast<QQuick3DModel *>(object: frontendObject); |
1361 | if (!model) |
1362 | return QQuick3DPickResult(); |
1363 | |
1364 | return QQuick3DPickResult(model, |
1365 | ::sqrtf(x: pickResult.m_distanceSq), |
1366 | pickResult.m_localUVCoords, |
1367 | pickResult.m_scenePosition, |
1368 | pickResult.m_localPosition, |
1369 | pickResult.m_faceNormal, |
1370 | pickResult.m_instanceIndex); |
1371 | } |
1372 | |
1373 | // Returns the first found scene manager of objects children |
1374 | QQuick3DSceneManager *QQuick3DViewport::findChildSceneManager(QQuick3DObject *inObject, QQuick3DSceneManager *manager) |
1375 | { |
1376 | if (manager) |
1377 | return manager; |
1378 | |
1379 | auto children = QQuick3DObjectPrivate::get(item: inObject)->childItems; |
1380 | for (auto child : children) { |
1381 | if (auto m = QQuick3DObjectPrivate::get(item: child)->sceneManager) { |
1382 | manager = m; |
1383 | break; |
1384 | } |
1385 | manager = findChildSceneManager(inObject: child, manager); |
1386 | } |
1387 | return manager; |
1388 | } |
1389 | |
1390 | void QQuick3DViewport::updateInputProcessing() |
1391 | { |
1392 | // This should be called from the gui thread. |
1393 | setAcceptTouchEvents(m_enableInputProcessing); |
1394 | setAcceptHoverEvents(m_enableInputProcessing); |
1395 | setAcceptedMouseButtons(m_enableInputProcessing ? Qt::AllButtons : Qt::NoButton); |
1396 | } |
1397 | |
1398 | void QQuick3DViewport::onReleaseCachedResources() |
1399 | { |
1400 | if (auto renderer = getRenderer()) |
1401 | renderer->releaseCachedResources(); |
1402 | } |
1403 | |
1404 | /*! |
1405 | \internal |
1406 | */ |
1407 | QQmlListProperty<QQuick3DObject> QQuick3DViewport::extensions() |
1408 | { |
1409 | return QQmlListProperty<QQuick3DObject>{ this, |
1410 | &m_extensionListDirty, |
1411 | &QQuick3DExtensionListHelper::extensionAppend, |
1412 | &QQuick3DExtensionListHelper::extensionCount, |
1413 | &QQuick3DExtensionListHelper::extensionAt, |
1414 | &QQuick3DExtensionListHelper::extensionClear, |
1415 | &QQuick3DExtensionListHelper::extensionReplace, |
1416 | &QQuick3DExtensionListHelper::extensionRemoveLast}; |
1417 | } |
1418 | |
1419 | QT_END_NAMESPACE |
1420 | |