// Copyright (C) 2019 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only

#include "qquick3dviewport_p.h"
#include "qquick3dsceneenvironment_p.h"
#include "qquick3dscenemanager_p.h"
#include "qquick3dtexture_p.h"
#include "qquick3dscenerenderer_p.h"
#include "qquick3dscenerootnode_p.h"
#include "qquick3dcamera_p.h"
#include "qquick3dmodel_p.h"
#include "qquick3drenderstats_p.h"
#include "qquick3ditem2d_p.h"
#include "qquick3ddefaultmaterial_p.h"
#include "qquick3dprincipledmaterial_p.h"
#include "qquick3dcustommaterial_p.h"
#include "qquick3dspecularglossymaterial_p.h"

#include <QtQuick3DRuntimeRender/private/qssgrenderlayer_p.h>
#include <QtQuick3DRuntimeRender/private/qssglayerrenderdata_p.h>

#include <QtQuick3DUtils/private/qssgassert_p.h>

#include <qsgtextureprovider.h>
#include <QSGSimpleTextureNode>
#include <QSGRendererInterface>
#include <QQuickWindow>
#include <QtQuick/private/qquickitem_p.h>
#include <QtQuick/private/qquickpointerhandler_p.h>

#include <QtQml>

#include <QtGui/private/qeventpoint_p.h>

#include <QtCore/private/qnumeric_p.h>
#include <QtCore/qpointer.h>

#include <optional>

QT_BEGIN_NAMESPACE

Q_STATIC_LOGGING_CATEGORY(lcEv, "qt.quick3d.event")
Q_STATIC_LOGGING_CATEGORY(lcPick, "qt.quick3d.pick")

static bool isforceInputHandlingSet()
{
    static const bool v = (qEnvironmentVariableIntValue("QT_QUICK3D_FORCE_INPUT_HANDLING") > 0);
    return v;
}

struct ViewportTransformHelper : public QQuickDeliveryAgent::Transform
{
    static void removeAll() {
        for (auto o : owners) {
            if (!o.isNull())
                o->setSceneTransform(nullptr);
        }
        owners.clear();
    }

    void setOnDeliveryAgent(QQuickDeliveryAgent *da) {
        da->setSceneTransform(this);
        owners.append(da);
    }

    /*
        Transforms viewport coordinates to 2D scene coordinates.
        Returns the point in targetItem corresponding to \a viewportPoint,
        assuming that targetItem is mapped onto sceneParentNode.
        If it's no longer a "hit" on sceneParentNode, returns the last-good point.
    */
    QPointF map(const QPointF &viewportPoint) override {
        QPointF point = viewportPoint;
        // Despite the name, the input coordinates are the window viewport coordinates
        // so unless the View3D is the same size of the Window, we need to translate
        // to the View3D coordinates before doing any picking.
        if (viewport)
            point = viewport->mapFromScene(viewportPoint);
        point.rx() *= scaleX;
        point.ry() *= scaleY;
        std::optional<QSSGRenderRay> rayResult = renderer->getRayFromViewportPos(point);
        if (rayResult.has_value()) {
            const auto pickResults = renderer->syncPickOne(rayResult.value(), sceneParentNode);
            if (!pickResults.isEmpty()) {
                const auto pickResult = pickResults.first();
                auto ret = pickResult.m_localUVCoords.toPointF();
                if (!uvCoordsArePixels) {
                    ret = QPointF(targetItem->x() + ret.x() * targetItem->width(),
                                  targetItem->y() - ret.y() * targetItem->height() + targetItem->height());
                }
                const bool outOfModel = pickResult.m_localUVCoords.isNull();
                qCDebug(lcEv) << viewportPoint << "->" << (outOfModel ? "OOM" : "") << ret << "@" << pickResult.m_scenePosition
                              << "UV" << pickResult.m_localUVCoords << "dist" << qSqrt(pickResult.m_distanceSq);
                if (outOfModel) {
                    return lastGoodMapping;
                } else {
                    lastGoodMapping = ret;
                    return ret;
                }
            }
        }
        return QPointF();
    }

    QPointer<QQuick3DViewport> viewport;
    QQuick3DSceneRenderer *renderer = nullptr;
    QSSGRenderNode *sceneParentNode = nullptr;
    QPointer<QQuickItem> targetItem;
    qreal scaleX = 1;
    qreal scaleY = 1;
    bool uvCoordsArePixels = false; // if false, they are in the range 0..1
    QPointF lastGoodMapping;

    static QList<QPointer<QQuickDeliveryAgent>> owners;
};

QList<QPointer<QQuickDeliveryAgent>> ViewportTransformHelper::owners;

class QQuick3DExtensionListHelper
{
    Q_DISABLE_COPY_MOVE(QQuick3DExtensionListHelper);
public:
    static void extensionAppend(QQmlListProperty<QQuick3DObject> *list, QQuick3DObject *extension)
    {
        QSSG_ASSERT(list && extension, return);

        if (QQuick3DViewport *that = qobject_cast<QQuick3DViewport *>(list->object)) {
            if (const auto idx = that->m_extensions.indexOf(extension); idx == -1) {
                if (!extension->parentItem())
                    extension->setParentItem(that->m_sceneRoot);
                that->m_extensions.push_back(extension);
                that->m_extensionListDirty = true;
            }
        }
    }
    static QQuick3DObject *extensionAt(QQmlListProperty<QQuick3DObject> *list, qsizetype index)
    {
        QQuick3DObject *ret = nullptr;
        QSSG_ASSERT(list, return ret);

        if (QQuick3DViewport *that = qobject_cast<QQuick3DViewport *>(list->object)) {
            if (that->m_extensions.size() > index)
                ret = that->m_extensions.at(index);
        }

        return ret;
    }
    static qsizetype extensionCount(QQmlListProperty<QQuick3DObject> *list)
    {
        qsizetype ret = -1;
        QSSG_ASSERT(list, return ret);

        if (QQuick3DViewport *that = qobject_cast<QQuick3DViewport *>(list->object))
            ret = that->m_extensions.size();

        return ret;
    }
    static void extensionClear(QQmlListProperty<QQuick3DObject> *list)
    {
        QSSG_ASSERT(list, return);

        if (QQuick3DViewport *that = qobject_cast<QQuick3DViewport *>(list->object)) {
            that->m_extensions.clear();
            that->m_extensionListDirty = true;
        }
    }
    static void extensionReplace(QQmlListProperty<QQuick3DObject> *list, qsizetype idx, QQuick3DObject *o)
    {
        QSSG_ASSERT(list, return);

        if (QQuick3DViewport *that = qobject_cast<QQuick3DViewport *>(list->object)) {
            if (that->m_extensions.size() > idx && idx > -1) {
                that->m_extensions.replace(idx, o);
                that->m_extensionListDirty = true;
            }
        }
    }
        static void extensionRemoveLast(QQmlListProperty<QQuick3DObject> *list)
    {
        QSSG_ASSERT(list, return);

        if (QQuick3DViewport *that = qobject_cast<QQuick3DViewport *>(list->object)) {
            that->m_extensions.removeLast();
            that->m_extensionListDirty = true;
        }
    }
};

/*!
    \qmltype View3D
    \inherits Item
    \inqmlmodule QtQuick3D
    \brief Provides a viewport on which to render a 3D scene.

    View3D provides a 2D surface on which a 3D scene can be rendered. This
    surface is a Qt Quick \l Item and can be placed in a Qt Quick scene.

    There are two ways to define the 3D scene that is visualized on the View3D:
    If you define a hierarchy of \l{Node}{Node-based} items as children of
    the View3D directly, then this will become the implicit scene of the View3D.

    It is also possible to reference an existing scene by using the \l importScene
    property and setting it to the root \l Node of the scene you want to visualize.
    This \l Node does not have to be an ancestor of the View3D, and you can have
    multiple View3Ds that import the same scene.

    This is demonstrated in \l {Qt Quick 3D - View3D example}{View3D example}.

    If the View3D both has child \l{Node}{Nodes} and the \l importScene property is
    set simultaneously, then both scenes will be rendered as if they were sibling
    subtrees in the same scene.

    To control how a scene is rendered, you can set the \l environment
    property. The type \l SceneEnvironment has a number of visual properties
    that can be adjusted, such as background color, tone mapping, anti-aliasing
    and more. \l ExtendedSceneEnvironment in the \c{QtQuick3D.Helpers} module
    extends \l SceneEnvironment with even more features, adding common
    post-processing effects.

    In addition, in order for anything to be rendered in the View3D, the scene
    needs a \l Camera. If there is only a single \l Camera in the scene, then
    this will automatically be picked. Otherwise, the \l camera property can
    be used to select the camera. The \l Camera decides which parts of the scene
    are visible, and how they are projected onto the 2D surface.

    By default, the 3D scene will first be rendered into an off-screen buffer and
    then composited with the rest of the Qt Quick scene when it is done. This provides
    the maximum level of compatibility, but may have performance implications on some
    graphics hardware. If this is the case, the \l renderMode property can be used to
    switch how the View3D is rendered into the window.

    A View3D with the default Offscreen \l renderMode is implicitly a
    \l{QSGTextureProvider}{texture provider} as well. This means that \l
    ShaderEffect or \l{QtQuick3D::Texture::sourceItem}{Texture.sourceItem} can reference
    the View3D directly as long as all items live within the same
    \l{QQuickWindow}{window}. Like with any other \l Item, it is also possible
    to switch the View3D, or one of its ancestors, into a texture-based
    \l{QtQuick::Item::layer.enabled}{item layer}.

    \sa {Qt Quick 3D - View3D example}
*/

QQuick3DViewport::QQuick3DViewport(QQuickItem *parent)
    : QQuickItem(parent)
{
    setFlag(ItemHasContents);
    m_camera = nullptr;
    m_sceneRoot = new QQuick3DSceneRootNode(this);
    m_renderStats = new QQuick3DRenderStats();
    QQuick3DSceneManager *sceneManager = new QQuick3DSceneManager();
    QQuick3DObjectPrivate::get(m_sceneRoot)->refSceneManager(*sceneManager);
    Q_ASSERT(sceneManager == QQuick3DObjectPrivate::get(m_sceneRoot)->sceneManager);
    connect(sceneManager, &QQuick3DSceneManager::needsUpdate,
            this, &QQuickItem::update);

    // Overrides the internal input handling to always be true
    // instead of potentially updated after a sync (see updatePaintNode)
    if (isforceInputHandlingSet()) {
        m_enableInputProcessing = true;
        updateInputProcessing();
        forceActiveFocus();
    }
}

QQuick3DViewport::~QQuick3DViewport()
{
    // With the threaded render loop m_directRenderer must be destroyed on the
    // render thread at the proper time, not here. That's handled in
    // releaseResources() + upon sceneGraphInvalidated. However with a
    // QQuickRenderControl-based window on the main thread there is a good
    // chance that this viewport (and so our sceneGraphInvalidated signal
    // connection) is destroyed before the window and the rendercontrol. So act
    // here then.
    if (m_directRenderer && m_directRenderer->thread() == thread()) {
        delete m_directRenderer;
        m_directRenderer = nullptr;
    }

    // If the quick window still exists, make sure to disconnect any of the direct
    // connections to this View3D
    if (auto qw = window())
        disconnect(qw, nullptr, this, nullptr);

    auto sceneManager = QQuick3DObjectPrivate::get(m_sceneRoot)->sceneManager;
    if (sceneManager) {
        sceneManager->setParent(nullptr);
        if (auto wa = sceneManager->wattached)
            wa->queueForCleanup(sceneManager);
    }

    delete m_sceneRoot;
    m_sceneRoot = nullptr;

    delete m_builtInEnvironment;

    // m_renderStats is tightly coupled with the render thread, so can't delete while we
    // might still be rendering.
    m_renderStats->deleteLater();

    if (!window() && sceneManager && sceneManager->wattached)
        QMetaObject::invokeMethod(sceneManager->wattached, &QQuick3DWindowAttachment::evaluateEol, Qt::QueuedConnection);
}

static void ssgn_append(QQmlListProperty<QObject> *property, QObject *obj)
{
    if (!obj)
        return;
    QQuick3DViewport *view3d = static_cast<QQuick3DViewport *>(property->object);

    if (QQuick3DObject *sceneObject = qmlobject_cast<QQuick3DObject *>(obj)) {
        QQmlListProperty<QObject> itemProperty = QQuick3DObjectPrivate::get(view3d->scene())->data();
        itemProperty.append(&itemProperty, sceneObject);
    } else {
        QQuickItemPrivate::data_append(property, obj);
    }
}

static qsizetype ssgn_count(QQmlListProperty<QObject> *property)
{
    QQuick3DViewport *view3d = static_cast<QQuick3DViewport *>(property->object);
    if (!view3d || !view3d->scene() || !QQuick3DObjectPrivate::get(view3d->scene())->data().count)
        return 0;
    QQmlListProperty<QObject> itemProperty = QQuick3DObjectPrivate::get(view3d->scene())->data();
    return itemProperty.count(&itemProperty);
}

static QObject *ssgn_at(QQmlListProperty<QObject> *property, qsizetype i)
{
    QQuick3DViewport *view3d = static_cast<QQuick3DViewport *>(property->object);
    QQmlListProperty<QObject> itemProperty = QQuick3DObjectPrivate::get(view3d->scene())->data();
    return itemProperty.at(&itemProperty, i);
}

static void ssgn_clear(QQmlListProperty<QObject> *property)
{
    QQuick3DViewport *view3d = static_cast<QQuick3DViewport *>(property->object);
    QQmlListProperty<QObject> itemProperty = QQuick3DObjectPrivate::get(view3d->scene())->data();
    return itemProperty.clear(&itemProperty);
}


QQmlListProperty<QObject> QQuick3DViewport::data()
{
    return QQmlListProperty<QObject>(this,
                                     nullptr,
                                     ssgn_append,
                                     ssgn_count,
                                     ssgn_at,
                                     ssgn_clear);
}

/*!
    \qmlproperty QtQuick3D::Camera QtQuick3D::View3D::camera

    This property specifies which \l Camera is used to render the scene. If this
    property is not set, then the first enabled camera in the scene will be used.

    \note If this property contains a camera that's not \l {Node::visible}{visible} then
    no further attempts to find a camera will be done.

    \sa PerspectiveCamera, OrthographicCamera, FrustumCamera, CustomCamera
*/
QQuick3DCamera *QQuick3DViewport::camera() const
{
    return m_camera;
}

/*!
    \qmlproperty QtQuick3D::SceneEnvironment QtQuick3D::View3D::environment

    This property specifies the SceneEnvironment used to render the scene.

    \note Setting this property to \c null will reset the SceneEnvironment to the default.

    \sa SceneEnvironment
*/
QQuick3DSceneEnvironment *QQuick3DViewport::environment() const
{
    if (!m_environment) {
        if (!m_builtInEnvironment) {
            m_builtInEnvironment = new QQuick3DSceneEnvironment;
            // Check that we are on the "correct" thread, and move the environment to the
            // correct thread if not. This can happen when environment() is called from the
            // sync and no scene environment has been set.
            if (QThread::currentThread() != m_sceneRoot->thread())
                m_builtInEnvironment->moveToThread(m_sceneRoot->thread());
            m_builtInEnvironment->setParentItem(m_sceneRoot);
        }

        return m_builtInEnvironment;
    }

    return m_environment;
}

/*!
    \qmlproperty QtQuick3D::Node QtQuick3D::View3D::scene
    \readonly

    Returns the root \l Node of the View3D's scene.

    To define the 3D scene that is visualized in the View3D:

    \list
    \li  Define a hierarchy of \l{Node}{Node-based} items as children of
        the View3D directly, then this will become the implicit \l scene of the
        View3D.
    \li Reference an existing scene by using the \l importScene property and
        set it to the root \l Node of the scene you want to visualize. This
        \l Node does not have to be an ancestor of the View3D, and you can have
        multiple View3Ds that import the same scene.
    \endlist

    \sa importScene
*/
QQuick3DNode *QQuick3DViewport::scene() const
{
    return m_sceneRoot;
}

/*!
    \qmlproperty QtQuick3D::Node QtQuick3D::View3D::importScene

    This property defines the reference node of the scene to render to the
    viewport. The node does not have to be a child of the View3D. This
    referenced node becomes a sibling with child nodes of View3D, if there are
    any.

    \note This property can only be set once, and subsequent changes will have
    no effect.

    You can also define a hierarchy of \l{Node}{Node-based} items as children of
    the View3D directly, then this will become the implicit scene of the
    View3D.

    To return the current scene of the View3D, use the \l scene property.

    \sa Node
*/
QQuick3DNode *QQuick3DViewport::importScene() const
{
    return m_importScene;
}

/*!
    \qmlproperty enumeration QtQuick3D::View3D::renderMode

    This property determines how the View3D is combined with the other parts of the
    Qt Quick scene.

    By default, the scene will be rendered into an off-screen buffer as an intermediate
    step. This off-screen buffer is then rendered into the window (or render target) like any
    other Qt Quick \l Item.

    For most users, there will be no need to change the render mode, and this property can
    safely be ignored. But on some graphics hardware, the use of an off-screen buffer can be
    a performance bottleneck. If this is the case, it might be worth experimenting with other
    modes.

    \value View3D.Offscreen The scene is rendered into an off-screen buffer as an intermediate
    step. This off-screen buffer is then composited with the rest of the Qt Quick scene.

    \value View3D.Underlay The scene is rendered directly to the window before the rest of
    the Qt Quick scene is rendered. With this mode, the View3D cannot be placed on top of
    other Qt Quick items.

    \value View3D.Overlay The scene is rendered directly to the window after Qt Quick is
    rendered.  With this mode, the View3D will always be on top of other Qt Quick items.

    \value View3D.Inline The View3D's scene graph is embedded into the main scene graph,
    and the same ordering semantics are applied as to any other Qt Quick \l Item. As this
    mode can lead to subtle issues, depending on the contents of the scene, due to
    injecting depth-based 3D content into a 2D scene graph, it is not recommended to be
    used, unless a specific need arises.

    The default is \c{View3D.Offscreen}.

    \note When changing the render mode, it is important to note that \c{View3D.Offscreen} (the
    default) is the only mode which guarantees perfect graphics fidelity. The other modes
    all have limitations that can cause visual glitches, so it is important to check that
    the visual output still looks correct when changing this property.

    \note When using the Underlay, Overlay, or Inline modes, it can be useful, and in some
    cases, required, to disable the Qt Quick scene graph's depth buffer writes via
    QQuickGraphicsConfiguration::setDepthBufferFor2D() before showing the QQuickWindow or
    QQuickView hosting the View3D item.
*/
QQuick3DViewport::RenderMode QQuick3DViewport::renderMode() const
{
    return m_renderMode;
}

/*!
    \qmlproperty enumeration QtQuick3D::View3D::renderFormat
    \since 6.4

    This property determines the backing texture's format. Applicable only when
    the View3D is rendering to a texture, for example because the \l renderMode
    is \c{View3D.Offscreen}.

    The default is \c{ShaderEffectSource.RGBA8}.

    If the format is not supported by the underlying graphics driver at run
    time, RGBA8 is used.

    \list
    \li ShaderEffectSource.RGBA8
    \li ShaderEffectSource.RGBA16F
    \li ShaderEffectSource.RGBA32F
    \endlist

    \sa QtQuick::ShaderEffectSource::format, QtQuick::Item::layer.format
 */
#if QT_CONFIG(quick_shadereffect)
QQuickShaderEffectSource::Format QQuick3DViewport::renderFormat() const
{
    return m_renderFormat;
}
#endif

/*!
    \qmlproperty QtQuick3D::RenderStats QtQuick3D::View3D::renderStats
    \readonly

    This property provides statistics about the rendering of a frame, such as
    \l {RenderStats::fps}{fps}, \l {RenderStats::frameTime}{frameTime},
    \l {RenderStats::renderTime}{renderTime}, \l {RenderStats::syncTime}{syncTime},
    and \l {RenderStats::maxFrameTime}{maxFrameTime}.
*/
QQuick3DRenderStats *QQuick3DViewport::renderStats() const
{
    return m_renderStats;
}

QQuick3DSceneRenderer *QQuick3DViewport::createRenderer() const
{
    QQuick3DSceneRenderer *renderer = nullptr;

    if (QQuickWindow *qw = window()) {
        auto wa = QQuick3DSceneManager::getOrSetWindowAttachment(*qw);
        auto rci = wa->rci();
        if (!rci) {
            QSGRendererInterface *rif = qw->rendererInterface();
            if (QSSG_GUARD(QSGRendererInterface::isApiRhiBased(rif->graphicsApi()))) {
                QRhi *rhi = static_cast<QRhi *>(rif->getResource(qw, QSGRendererInterface::RhiResource));
                QSSG_CHECK_X(rhi != nullptr, "No QRhi from QQuickWindow, this cannot happen");
                // The RenderContextInterface, and the objects owned by it (such
                // as, the BufferManager) are always per-QQuickWindow, and so per
                // scenegraph render thread. Hence the association with window.
                // Multiple View3Ds in the same window can use the same rendering
                // infrastructure (so e.g. the same QSSGBufferManager), but two
                // View3D objects in different windows must not, except for certain
                // components that do not work with and own native graphics
                // resources (most notably, QSSGShaderLibraryManager - but this
                // distinction is handled internally by QSSGRenderContextInterface).
                rci = std::make_shared<QSSGRenderContextInterface>(rhi);
                wa->setRci(rci);

                // Use DirectConnection to stay on the render thread, if there is one.
                connect(wa, &QQuick3DWindowAttachment::releaseCachedResources, this,
                        &QQuick3DViewport::onReleaseCachedResources, Qt::DirectConnection);

            } else {
                qWarning("The Qt Quick scene is using a rendering method that is not based on QRhi and a 3D graphics API. "
                         "Qt Quick 3D is not functional in such an environment. The View3D item is not going to display anything.");
            }
        }

        if (rci)
            renderer = new QQuick3DSceneRenderer(rci);
    }

    return renderer;
}

bool QQuick3DViewport::isTextureProvider() const
{
    // We can only be a texture provider if we are rendering to a texture first
    if (m_renderMode == QQuick3DViewport::Offscreen)
        return true;

    return false;
}

QSGTextureProvider *QQuick3DViewport::textureProvider() const
{
    // When Item::layer::enabled == true, QQuickItem will be a texture
    // provider. In this case we should prefer to return the layer rather
    // than the fbo texture.
    if (QQuickItem::isTextureProvider())
        return QQuickItem::textureProvider();

    // We can only be a texture provider if we are rendering to a texture first
    if (m_renderMode != QQuick3DViewport::Offscreen)
        return nullptr;

    QQuickWindow *w = window();
    if (!w) {
        qWarning("QSSGView3D::textureProvider: can only be queried on the rendering thread of an exposed window");
        return nullptr;
    }

    if (!m_node)
        m_node = new SGFramebufferObjectNode;
    return m_node;
}

class CleanupJob : public QRunnable
{
public:
    CleanupJob(QQuick3DSGDirectRenderer *renderer) : m_renderer(renderer) { }
    void run() override { delete m_renderer; }
private:
    QQuick3DSGDirectRenderer *m_renderer;
};

void QQuick3DViewport::releaseResources()
{
    if (m_directRenderer) {
        window()->scheduleRenderJob(new CleanupJob(m_directRenderer), QQuickWindow::BeforeSynchronizingStage);
        m_directRenderer = nullptr;
    }

    m_node = nullptr;
}

void QQuick3DViewport::cleanupDirectRenderer()
{
    delete m_directRenderer;
    m_directRenderer = nullptr;
}

void QQuick3DViewport::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
{
    QQuickItem::geometryChange(newGeometry, oldGeometry);

    if (newGeometry.size() != oldGeometry.size())
        update();
}

QSGNode *QQuick3DViewport::updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNodeData *)
{
    // When changing render modes
    if (m_renderModeDirty) {
        if (node) {
            delete node;
            node = nullptr;
            m_node = nullptr;
            m_renderNode = nullptr;
        }
        if (m_directRenderer) {
            delete m_directRenderer;
            m_directRenderer = nullptr;
        }
    }

    m_renderModeDirty = false;

    switch (m_renderMode) {
    // Direct rendering
    case Underlay:
        Q_FALLTHROUGH();
    case Overlay:
        setupDirectRenderer(m_renderMode);
        node = nullptr;
        break;
    case Offscreen:
        node = setupOffscreenRenderer(node);
        break;
    case Inline:
        // QSGRenderNode-based rendering
        node = setupInlineRenderer(node);
        break;
    }

    if (!isforceInputHandlingSet()) {
        // Implicitly enable internal input processing if any item2ds are present.
        const auto inputHandlingEnabled =
                QQuick3DObjectPrivate::get(m_sceneRoot)->sceneManager->inputHandlingEnabled;
        const auto enable = inputHandlingEnabled > 0;
        if (m_enableInputProcessing != enable) {
            m_enableInputProcessing = enable;
            QMetaObject::invokeMethod(this, "updateInputProcessing", Qt::QueuedConnection);
        }
    }

    return node;
}

void QQuick3DViewport::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
{
    if (change == ItemSceneChange) {
        if (value.window) {
            // TODO: if we want to support multiple windows, there has to be a scene manager for
            // every window.
            QQuick3DObjectPrivate::get(m_sceneRoot)->sceneManager->setWindow(value.window);
            if (m_importScene)
                QQuick3DObjectPrivate::get(m_importScene)->sceneManager->setWindow(value.window);
            m_renderStats->setWindow(value.window);
        }
    } else if (change == ItemVisibleHasChanged && isVisible()) {
        update();
    }
}

bool QQuick3DViewport::event(QEvent *event)
{
    if (m_enableInputProcessing && event->isPointerEvent())
        return internalPick(static_cast<QPointerEvent *>(event));
    else
        return QQuickItem::event(event);
}

void QQuick3DViewport::componentComplete()
{
    QQuickItem::componentComplete();
    Q_QUICK3D_PROFILE_REGISTER(this);
}

void QQuick3DViewport::setCamera(QQuick3DCamera *camera)
{
    if (m_camera == camera)
        return;

    if (camera && !camera->parentItem())
        camera->setParentItem(m_sceneRoot);
    if (camera)
        camera->updateGlobalVariables(QRect(0, 0, width(), height()));

    QQuick3DObjectPrivate::attachWatcherPriv(m_sceneRoot, this, &QQuick3DViewport::setCamera, camera, m_camera);

    m_camera = camera;
    emit cameraChanged();
    update();
}

void QQuick3DViewport::setMultiViewCameras(QQuick3DCamera **firstCamera, int count)
{
    m_multiViewCameras.clear();
    bool sendChangeSignal = false;
    for (int i = 0; i < count; ++i) {
        QQuick3DCamera *camera = *(firstCamera + i);
        if (camera) {
            if (!camera->parentItem())
                camera->setParentItem(m_sceneRoot);
            camera->updateGlobalVariables(QRect(0, 0, width(), height()));
        }
        if (i == 0) {
            if (m_camera != camera) {
                m_camera = camera;
                sendChangeSignal = true;
            }
        }

        m_multiViewCameras.append(camera);

        // ### do we need attachWatcher stuff? the Xr-provided cameras cannot disappear, although the XrActor (the owner) might
    }

    if (sendChangeSignal)
        emit cameraChanged();

    update();
}

void QQuick3DViewport::setEnvironment(QQuick3DSceneEnvironment *environment)
{
    if (m_environment == environment)
        return;

    m_environment = environment;
    if (m_environment && !m_environment->parentItem())
        m_environment->setParentItem(m_sceneRoot);

    QQuick3DObjectPrivate::attachWatcherPriv(m_sceneRoot, this, &QQuick3DViewport::setEnvironment, environment, m_environment);

    emit environmentChanged();
    update();
}

void QQuick3DViewport::setImportScene(QQuick3DNode *inScene)
{
    // ### We may need consider the case where there is
    // already a scene tree here
    // FIXME : Only the first importScene is an effective one
    if (m_importScene)
        return;

    // FIXME : Check self-import or cross-import
    // Currently it does not work since importScene qml parsed in a reverse order.
    QQuick3DNode *scene = inScene;
    while (scene) {
        if (m_sceneRoot == scene) {
            qmlWarning(this) << "Cannot allow self-import or cross-import!";
            return;
        }

        QQuick3DSceneRootNode *rn = qobject_cast<QQuick3DSceneRootNode *>(scene);
        scene = rn ? rn->view3D()->importScene() : nullptr;
    }

    m_importScene = inScene;
    if (m_importScene) {
        auto privateObject = QQuick3DObjectPrivate::get(m_importScene);
        if (!privateObject->sceneManager) {
            // If object doesn't already have scene manager, check from its children
            QQuick3DSceneManager *manager = findChildSceneManager(m_importScene);
            // If still not found, use the one from the scene root (scenes defined outside of an view3d)
            if (!manager)
                manager = QQuick3DObjectPrivate::get(m_sceneRoot)->sceneManager;
            if (manager) {
                manager->setWindow(window());
                privateObject->refSceneManager(*manager);
            }
            // At this point some manager will exist
            Q_ASSERT(privateObject->sceneManager);
        }

        connect(privateObject->sceneManager, &QQuick3DSceneManager::needsUpdate,
                this, &QQuickItem::update);

        QQuick3DNode *scene = inScene;
        while (scene) {
            QQuick3DSceneRootNode *rn = qobject_cast<QQuick3DSceneRootNode *>(scene);
            scene = rn ? rn->view3D()->importScene() : nullptr;

            if (scene) {
                connect(QQuick3DObjectPrivate::get(scene)->sceneManager,
                        &QQuick3DSceneManager::needsUpdate,
                        this, &QQuickItem::update);
            }
        }
    }

    emit importSceneChanged();
    update();
}

void QQuick3DViewport::setRenderMode(QQuick3DViewport::RenderMode renderMode)
{
    if (m_renderMode == renderMode)
        return;

    m_renderMode = renderMode;
    m_renderModeDirty = true;
    emit renderModeChanged();
    update();
}

#if QT_CONFIG(quick_shadereffect)
void QQuick3DViewport::setRenderFormat(QQuickShaderEffectSource::Format format)
{
    if (m_renderFormat == format)
        return;

    m_renderFormat = format;
    m_renderModeDirty = true;
    emit renderFormatChanged();
    update();
}
#endif

/*!
    \qmlproperty int QtQuick3D::View3D::explicitTextureWidth
    \since 6.7

    The width, in pixels, of the item's associated texture. Relevant when a
    fixed texture size is desired that does not depend on the item's size. This
    size has no effect on the geometry of the item (its size and placement
    within the scene), which means the texture's content will appear scaled up
    or down (and possibly stretched) onto the item's area.

    By default the value is \c 0. A value of 0 means that texture's size
    follows the item's size. (\c{texture size in pixels} = \c{item's logical
    size} * \c{device pixel ratio}).

    \note This property is relevant only when \l renderMode is set to \c
    Offscreen. Its value is ignored otherwise.

    \sa explicitTextureHeight, effectiveTextureSize, DebugView
*/
int QQuick3DViewport::explicitTextureWidth() const
{
    return m_explicitTextureWidth;
}

void QQuick3DViewport::setExplicitTextureWidth(int width)
{
    if (m_explicitTextureWidth == width)
        return;

    m_explicitTextureWidth = width;
    emit explicitTextureWidthChanged();
    update();
}

/*!
    \qmlproperty int QtQuick3D::View3D::explicitTextureHeight
    \since 6.7

    The height, in pixels, of the item's associated texture. Relevant when a
    fixed texture size is desired that does not depend on the item's size. This
    size has no effect on the geometry of the item (its size and placement
    within the scene), which means the texture's content will appear scaled up
    or down (and possibly stretched) onto the item's area.

    By default the value is \c 0. A value of 0 means that texture's size
    follows the item's size. (\c{texture size in pixels} = \c{item's logical
    size} * \c{device pixel ratio}).

    \note This property is relevant only when \l renderMode is set to \c
    Offscreen. Its value is ignored otherwise.

    \sa explicitTextureWidth, effectiveTextureSize, DebugView
*/
int QQuick3DViewport::explicitTextureHeight() const
{
    return m_explicitTextureHeight;
}

void QQuick3DViewport::setExplicitTextureHeight(int height)
{
    if (m_explicitTextureHeight == height)
        return;

    m_explicitTextureHeight = height;
    emit explicitTextureHeightChanged();
    update();
}

/*!
    \qmlproperty size QtQuick3D::View3D::effectiveTextureSize
    \since 6.7

    This property exposes the size, in pixels, of the underlying color (and
    depth/stencil) buffers. It is provided for use on the GUI (main) thread, in
    QML bindings or JavaScript.

    This is a read-only property.

    \note This property is relevant only when \l renderMode is set to
    \c Offscreen.

    \sa explicitTextureWidth, explicitTextureHeight, DebugView
*/
QSize QQuick3DViewport::effectiveTextureSize() const
{
    return m_effectiveTextureSize;
}


/*!
    \qmlmethod vector3d View3D::mapFrom3DScene(vector3d scenePos)

    Transforms \a scenePos from scene space (3D) into view space (2D).

    The returned x- and y-values will be be in view coordinates, with the top-left
    corner at [0, 0] and the bottom-right corner at [width, height]. The returned
    z-value contains the distance from the near clip plane of the frustum (clipNear) to
    \a scenePos in scene coordinates. If the distance is negative, that means the \a scenePos
    is behind the active camera. If \a scenePos cannot be mapped to a position in the scene,
    a position of [0, 0, 0] is returned.

    This function requires that \l camera is assigned to the view.

    \sa mapTo3DScene(), {Camera::mapToViewport()}{Camera.mapToViewport()}
*/
QVector3D QQuick3DViewport::mapFrom3DScene(const QVector3D &scenePos) const
{
    if (!m_camera) {
        qmlWarning(this) << "Cannot resolve view position without a camera assigned!";
        return QVector3D(0, 0, 0);
    }

    qreal _width = width();
    qreal _height = height();
    if (_width == 0 || _height == 0)
        return QVector3D(0, 0, 0);

    const QVector3D normalizedPos = m_camera->mapToViewport(scenePos, _width, _height);
    return normalizedPos * QVector3D(float(_width), float(_height), 1);
}

/*!
    \qmlmethod vector3d View3D::mapTo3DScene(vector3d viewPos)

    Transforms \a viewPos from view space (2D) into scene space (3D).

    The x- and y-values of \a viewPos should be in view coordinates, with the top-left
    corner at [0, 0] and the bottom-right corner at [width, height]. The z-value is
    interpreted as the distance from the near clip plane of the frustum (clipNear) in
    scene coordinates.

    If \a viewPos cannot successfully be mapped to a position in the scene, a position of
    [0, 0, 0] is returned.

    This function requires that a \l camera is assigned to the view.

    \sa mapFrom3DScene(), {Camera::mapFromViewport}{Camera.mapFromViewport()}
*/
QVector3D QQuick3DViewport::mapTo3DScene(const QVector3D &viewPos) const
{
    if (!m_camera) {
        qmlWarning(this) << "Cannot resolve scene position without a camera assigned!";
        return QVector3D(0, 0, 0);
    }

    qreal _width = width();
    qreal _height = height();
    if (_width == 0 || _height == 0)
        return QVector3D(0, 0, 0);

    const QVector3D normalizedPos = viewPos / QVector3D(float(_width), float(_height), 1);
    return m_camera->mapFromViewport(normalizedPos, _width, _height);
}

/*!
    \qmlmethod pickResult View3D::pick(float x, float y)

    This method will "shoot" a ray into the scene from view coordinates \a x and \a y
    and return information about the nearest intersection with an object in the scene.

    This can, for instance, be called with mouse coordinates to find the object under the mouse cursor.
*/
QQuick3DPickResult QQuick3DViewport::pick(float x, float y) const
{
    QQuick3DSceneRenderer *renderer = getRenderer();
    if (!renderer)
        return QQuick3DPickResult();

    const QPointF position(qreal(x) * window()->effectiveDevicePixelRatio() * m_widthMultiplier,
                           qreal(y) * window()->effectiveDevicePixelRatio() * m_heightMultiplier);
    std::optional<QSSGRenderRay> rayResult = renderer->getRayFromViewportPos(position);
    if (!rayResult.has_value())
        return QQuick3DPickResult();

    const auto resultList = renderer->syncPick(rayResult.value());
    return getNearestPickResult(resultList);
}

/*!
    \qmlmethod pickResult View3D::pick(float x, float y, Model model)

    This method will "shoot" a ray into the scene from view coordinates \a x and \a y
    and return information about the intersection between the ray and the specified \a model.

    This can, for instance, be called with mouse coordinates to find the object under the mouse cursor.

    \since 6.8
*/
QQuick3DPickResult QQuick3DViewport::pick(float x, float y, QQuick3DModel *model) const
{
    QQuick3DSceneRenderer *renderer = getRenderer();
    if (!renderer)
        return QQuick3DPickResult();

    const QPointF position(qreal(x) * window()->effectiveDevicePixelRatio(),
                           qreal(y) * window()->effectiveDevicePixelRatio());
    std::optional<QSSGRenderRay> rayResult = renderer->getRayFromViewportPos(position);

    if (!rayResult.has_value())
        return QQuick3DPickResult();

    const auto renderNode = static_cast<QSSGRenderNode *>(QQuick3DObjectPrivate::get(model)->spatialNode);
    const auto resultList = renderer->syncPickOne(rayResult.value(), renderNode);
    return getNearestPickResult(resultList);
}

/*!
    \qmlmethod List<pickResult> View3D::pickSubset(float x, float y, list<Model> models)

    This method will "shoot" a ray into the scene from view coordinates \a x and \a y
    and return information about the intersections with the passed in list of \a models.
    This will only check against the list of models passed in.
    The returned list is sorted by distance from the camera with the nearest
    intersections appearing first and the furthest appearing last.

    This can, for instance, be called with mouse coordinates to find the object under the mouse cursor.

    Works with both property list<Model> and dynamic JavaScript arrays of models.

    \since 6.8
*/
QList<QQuick3DPickResult> QQuick3DViewport::pickSubset(float x, float y, const QJSValue &models) const
{
    QQuick3DSceneRenderer *renderer = getRenderer();
    if (!renderer)
        return {};

    QVarLengthArray<QSSGRenderNode*> renderNodes;
    // Check for regular JavaScript array
    if (models.isArray()) {
        const auto length = models.property(QStringLiteral("length")).toInt();
        if (length == 0)
            return {};

        for (int i = 0; i < length; ++i) {
            const auto isQObject = models.property(i).isQObject();
            if (!isQObject) {
                qmlWarning(this) << "Type provided for picking is not a QObject. Needs to be of type QQuick3DModel.";
                continue;
            }
            const auto obj = models.property(i).toQObject();
            const auto model = qobject_cast<QQuick3DModel *>(obj);
            if (!model) {
                qmlWarning(this) << "Type " << obj->metaObject()->className() << " is not supported for picking. Needs to be of type QQuick3DModel.";
                continue;
            }
            const auto priv = QQuick3DObjectPrivate::get(model);
            if (priv && priv->spatialNode) {
                renderNodes.push_back(static_cast<QSSGRenderNode*>(priv->spatialNode));
            }
        }
    } else {
        // Check for property list<Model>
        const auto subsetVariant = models.toVariant();
        if (!subsetVariant.isValid() || !subsetVariant.canConvert<QQmlListReference>())
            return {};

        const auto list = subsetVariant.value<QQmlListReference>();

        // Only support array of models
        if (list.listElementType()->className() != QQuick3DModel::staticMetaObject.className()) {
            qmlWarning(this) << "Type " << list.listElementType()->className() << " is not supported for picking. Needs to be of type QQuick3DModel.";
            return {};
        }
        for (int i = 0; i < list.count(); ++i) {
            auto model = static_cast<QQuick3DModel *>(list.at(i));
            if (!model)
                continue;
            auto priv = QQuick3DObjectPrivate::get(model);
            if (priv && priv->spatialNode) {
                renderNodes.push_back(static_cast<QSSGRenderNode*>(priv->spatialNode));
            }
        }
    }

    if (renderNodes.empty())
        return {};

    const QPointF position(qreal(x) * window()->effectiveDevicePixelRatio(),
                           qreal(y) * window()->effectiveDevicePixelRatio());
    std::optional<QSSGRenderRay> rayResult = renderer->getRayFromViewportPos(position);
    if (!rayResult.has_value())
        return {};

    const auto resultList = renderer->syncPickSubset(rayResult.value(), renderNodes);

    QList<QQuick3DPickResult> processedResultList;
    processedResultList.reserve(resultList.size());
    for (const auto &result : resultList)
        processedResultList.append(processPickResult(result));

    return processedResultList;
}

/*!
    \qmlmethod List<pickResult> View3D::pickAll(float x, float y)

    This method will "shoot" a ray into the scene from view coordinates \a x and \a y
    and return a list of information about intersections with objects in the scene.
    The returned list is sorted by distance from the camera with the nearest
    intersections appearing first and the furthest appearing last.

    This can, for instance, be called with mouse coordinates to find the object under the mouse cursor.

    \since 6.2
*/
QList<QQuick3DPickResult> QQuick3DViewport::pickAll(float x, float y) const
{
    QQuick3DSceneRenderer *renderer = getRenderer();
    if (!renderer)
        return QList<QQuick3DPickResult>();

    const QPointF position(qreal(x) * window()->effectiveDevicePixelRatio() * m_widthMultiplier,
                           qreal(y) * window()->effectiveDevicePixelRatio() * m_heightMultiplier);
    std::optional<QSSGRenderRay> rayResult = renderer->getRayFromViewportPos(position);
    if (!rayResult.has_value())
        return QList<QQuick3DPickResult>();

    const auto resultList = renderer->syncPickAll(rayResult.value());
    QList<QQuick3DPickResult> processedResultList;
    processedResultList.reserve(resultList.size());
    for (const auto &result : resultList)
        processedResultList.append(processPickResult(result));

    return processedResultList;
}

/*!
    \qmlmethod pickResult View3D::rayPick(vector3d origin, vector3d direction)

    This method will "shoot" a ray into the scene starting at \a origin and in
    \a direction and return information about the nearest intersection with an
    object in the scene.

    This can, for instance, be called with the position and forward vector of
    any object in a scene to see what object is in front of an item. This
    makes it possible to do picking from any point in the scene.

    \since 6.2
*/
QQuick3DPickResult QQuick3DViewport::rayPick(const QVector3D &origin, const QVector3D &direction) const
{
    QQuick3DSceneRenderer *renderer = getRenderer();
    if (!renderer)
        return QQuick3DPickResult();

    const QSSGRenderRay ray(origin, direction);
    const auto resultList = renderer->syncPick(ray);
    return getNearestPickResult(resultList);
}

/*!
    \qmlmethod List<pickResult> View3D::rayPickAll(vector3d origin, vector3d direction)

    This method will "shoot" a ray into the scene starting at \a origin and in
    \a direction and return a list of information about the nearest intersections with
    objects in the scene.
    The list is presorted by distance from the origin along the direction
    vector with the nearest intersections appearing first and the furthest
    appearing last.

    This can, for instance, be called with the position and forward vector of
    any object in a scene to see what objects are in front of an item. This
    makes it possible to do picking from any point in the scene.

    \since 6.2
*/
QList<QQuick3DPickResult> QQuick3DViewport::rayPickAll(const QVector3D &origin, const QVector3D &direction) const
{
    QQuick3DSceneRenderer *renderer = getRenderer();
    if (!renderer)
        return QList<QQuick3DPickResult>();

    const QSSGRenderRay ray(origin, direction);

    const auto resultList = renderer->syncPickAll(ray);
    QList<QQuick3DPickResult> processedResultList;
    processedResultList.reserve(resultList.size());
    for (const auto &result : resultList) {
        auto processedResult = processPickResult(result);
        if (processedResult.hitType() != QQuick3DPickResultEnums::HitType::Null)
            processedResultList.append(processedResult);
    }

    return processedResultList;
}

void QQuick3DViewport::processPointerEventFromRay(const QVector3D &origin, const QVector3D &direction, QPointerEvent *event) const
{
    internalPick(event, origin, direction);
}

// Note: we have enough information to implement Capability::Hover and Capability::ZPosition,
// but those properties are not currently available in QTouchEvent/QEventPoint

namespace {
class SyntheticTouchDevice : public QPointingDevice
{
public:
    SyntheticTouchDevice(QObject *parent = nullptr)
        : QPointingDevice(QLatin1StringView("QtQuick3D Touch Synthesizer"),
                          0,
                          DeviceType::TouchScreen,
                          PointerType::Finger,
                          Capability::Position,
                          10, 0,
                          QString(), QPointingDeviceUniqueId(),
                          parent)
    {
    }
};
}

/*!
    \qmlmethod View3D::setTouchpoint(Item target, point position, int pointId, bool pressed)

    Sends a synthetic touch event to \a target, moving the touch point with ID \a pointId to \a position,
    with \a pressed determining if the point is pressed.
    Also sends the appropriate touch release event if \a pointId was previously active on a different
    item.

    \since 6.8
 */

void QQuick3DViewport::setTouchpoint(QQuickItem *target, const QPointF &position, int pointId, bool pressed)
{
    if (pointId >= m_touchState.size())
        m_touchState.resize(pointId + 1);
    auto prevState = m_touchState[pointId];

    const bool sameTarget = prevState.target == target;
    const bool wasPressed = prevState.isPressed;

    const bool isPress = pressed && (!sameTarget || !wasPressed);
    const bool isRelease = !pressed && wasPressed && sameTarget;

    // Hover if we're not active, and we weren't previously active.
    // We assume that we always get a non-active for a target when we release.
    // This function sends a release events if the target is changed.
    if (!sameTarget && wasPressed)
        qWarning("QQuick3DViewport::setTouchpoint missing release event");

    if (!pressed && !wasPressed) {
        // This would be a hover event: skipping
        return;
    }

    m_touchState[pointId] = { target, position, pressed };

    if (!m_syntheticTouchDevice)
        m_syntheticTouchDevice = new SyntheticTouchDevice(this);

    QPointingDevicePrivate *devPriv = QPointingDevicePrivate::get(m_syntheticTouchDevice);

    auto makePoint = [devPriv](int id, QEventPoint::State pointState, QPointF pos) -> QEventPoint {
        auto epd = devPriv->pointById(id);
        auto &ep = epd->eventPoint;
        if (pointState != QEventPoint::State::Stationary)
            ep.setAccepted(false);

        auto res = QMutableEventPoint::withTimeStamp(0, id, pointState, pos, pos, pos);
        QMutableEventPoint::update(res, ep);
        return res;
    };

    auto sendTouchEvent = [&](QQuickItem *t, const QPointF &position, int pointId, QEventPoint::State pointState) -> void {
        QList<QEventPoint> points;
        bool otherPoint = false; // Does the event have another point already?
        for (int i = 0; i < m_touchState.size(); ++i) {
            const auto &ts = m_touchState[i];
            if (ts.target != t)
                continue;
            if (i == pointId) {
                auto newPoint = makePoint(i, pointState, position);
                points << newPoint;
            } else if (ts.isPressed) {
                otherPoint = true;
                points << makePoint(i, QEventPoint::Stationary, ts.position);
            }
        }

        QEvent::Type type;
        if (pointState == QEventPoint::Pressed && !otherPoint)
            type = QEvent::Type::TouchBegin;
        else if (pointState == QEventPoint::Released && !otherPoint)
            type = QEvent::Type::TouchEnd;
        else
            type = QEvent::Type::TouchUpdate;

        QTouchEvent ev(type, m_syntheticTouchDevice, {}, points);

        if (t) {
            // Actually send event:
            auto da = QQuickItemPrivate::get(t)->deliveryAgent();
            bool handled = da->event(&ev);
            Q_UNUSED(handled);
        }

        // Duplicate logic from QQuickWindowPrivate::clearGrabbers
        if (ev.isEndEvent()) {
            for (auto &point : ev.points()) {
                if (point.state() == QEventPoint::State::Released) {
                    ev.setExclusiveGrabber(point, nullptr);
                    ev.clearPassiveGrabbers(point);
                }
            }
        }
    };

    // Send a release event to the previous target
    if (prevState.target && !sameTarget)
        sendTouchEvent(prevState.target, prevState.position, pointId, QEventPoint::Released);

    // Now send an event for the new state
    QEventPoint::State newState = isPress ? QEventPoint::Pressed : isRelease ? QEventPoint::Released : QEventPoint::Updated;
    sendTouchEvent(target, position, pointId, newState);
}

QQuick3DLightmapBaker *QQuick3DViewport::maybeLightmapBaker()
{
    return m_lightmapBaker;
}

QQuick3DLightmapBaker *QQuick3DViewport::lightmapBaker()
{
    if (!m_lightmapBaker)
        m_lightmapBaker= new QQuick3DLightmapBaker(this);

    return m_lightmapBaker;
}

/*!
    \internal
*/
void QQuick3DViewport::bakeLightmap()
{
    lightmapBaker()->bake();
}

void QQuick3DViewport::setGlobalPickingEnabled(bool isEnabled)
{
    QQuick3DSceneRenderer *renderer = getRenderer();
    if (!renderer)
        return;

    renderer->setGlobalPickingEnabled(isEnabled);
}

void QQuick3DViewport::invalidateSceneGraph()
{
    m_node = nullptr;
}

QQuick3DSceneRenderer *QQuick3DViewport::getRenderer() const
{
    QQuick3DSceneRenderer *renderer = nullptr;
    if (m_node) {
        renderer = m_node->renderer;
    } else if (m_renderNode) {
        renderer = m_renderNode->renderer;
    } else if (m_directRenderer) {
        renderer = m_directRenderer->renderer();
    }
    return renderer;
}

void QQuick3DViewport::updateDynamicTextures()
{
    // Update QSGDynamicTextures that are used for source textures and Quick items
    // Must be called on the render thread.

    const auto &sceneManager = QQuick3DObjectPrivate::get(m_sceneRoot)->sceneManager;
    for (auto *texture : std::as_const(sceneManager->qsgDynamicTextures))
        texture->updateTexture();

    QQuick3DNode *scene = m_importScene;
    while (scene) {
        const auto &importSm = QQuick3DObjectPrivate::get(scene)->sceneManager;
        if (importSm != sceneManager) {
            for (auto *texture : std::as_const(importSm->qsgDynamicTextures))
                texture->updateTexture();
        }

        // if importScene has another import
        QQuick3DSceneRootNode *rn = qobject_cast<QQuick3DSceneRootNode *>(scene);
        scene = rn ? rn->view3D()->importScene() : nullptr;
    }
}

QSGNode *QQuick3DViewport::setupOffscreenRenderer(QSGNode *node)
{
    SGFramebufferObjectNode *n = static_cast<SGFramebufferObjectNode *>(node);

    if (!n) {
        if (!m_node)
            m_node = new SGFramebufferObjectNode;
        n = m_node;
    }

    if (!n->renderer) {
        n->window = window();
        n->renderer = createRenderer();
        if (!n->renderer)
            return nullptr;
        n->renderer->fboNode = n;
        n->quickFbo = this;
        connect(window(), SIGNAL(screenChanged(QScreen*)), n, SLOT(handleScreenChange()));
    }

    const qreal dpr = window()->effectiveDevicePixelRatio();
    const QSize minFboSize = QQuickItemPrivate::get(this)->sceneGraphContext()->minimumFBOSize();
    QSize desiredFboSize = QSize(m_explicitTextureWidth, m_explicitTextureHeight);
    if (desiredFboSize.isEmpty()) {
        desiredFboSize = QSize(width(), height()) * dpr;
        n->devicePixelRatio = dpr;
        // 1:1 mapping between the backing texture and the on-screen quad
        m_widthMultiplier = 1.0f;
        m_heightMultiplier = 1.0f;
    } else {
        QSize itemPixelSize = QSize(width(), height()) * dpr;
        // not 1:1 maping between the backing texture and the on-screen quad
        m_widthMultiplier = desiredFboSize.width() / float(itemPixelSize.width());
        m_heightMultiplier = desiredFboSize.height() / float(itemPixelSize.height());
        n->devicePixelRatio = 1.0;
    }
    desiredFboSize.setWidth(qMax(minFboSize.width(), desiredFboSize.width()));
    desiredFboSize.setHeight(qMax(minFboSize.height(), desiredFboSize.height()));

    if (desiredFboSize != m_effectiveTextureSize) {
        m_effectiveTextureSize = desiredFboSize;
        emit effectiveTextureSizeChanged();
    }

    n->setFiltering(smooth() ? QSGTexture::Linear : QSGTexture::Nearest);
    n->setRect(0, 0, width(), height());
    if (checkIsVisible() && isComponentComplete()) {
        n->renderer->synchronize(this, desiredFboSize, n->devicePixelRatio);
        if (n->renderer->m_textureNeedsFlip)
            n->setTextureCoordinatesTransform(QSGSimpleTextureNode::MirrorVertically);
        updateDynamicTextures();
        n->scheduleRender();
    }

    return n;
}

QSGNode *QQuick3DViewport::setupInlineRenderer(QSGNode *node)
{
    QQuick3DSGRenderNode *n = static_cast<QQuick3DSGRenderNode *>(node);
    if (!n) {
        if (!m_renderNode)
            m_renderNode = new QQuick3DSGRenderNode;
        n = m_renderNode;
    }

    if (!n->renderer) {
        n->window = window();
        n->renderer = createRenderer();
        if (!n->renderer)
            return nullptr;
    }

    if (!m_effectiveTextureSize.isEmpty()) {
        m_effectiveTextureSize = QSize();
        emit effectiveTextureSizeChanged();
    }

    const QSize targetSize = window()->effectiveDevicePixelRatio() * QSize(width(), height());

    // checkIsVisible, not isVisible, because, for example, a
    // { visible: false; layer.enabled: true } item still needs
    // to function normally.
    if (checkIsVisible() && isComponentComplete()) {
        n->renderer->synchronize(this, targetSize, window()->effectiveDevicePixelRatio());
        updateDynamicTextures();
        n->markDirty(QSGNode::DirtyMaterial);
    }

    return n;
}


void QQuick3DViewport::setupDirectRenderer(RenderMode mode)
{
    auto renderMode = (mode == Underlay) ? QQuick3DSGDirectRenderer::Underlay
                                         : QQuick3DSGDirectRenderer::Overlay;
    if (!m_directRenderer) {
        QQuick3DSceneRenderer *sceneRenderer = createRenderer();
        if (!sceneRenderer)
            return;
        m_directRenderer = new QQuick3DSGDirectRenderer(sceneRenderer, window(), renderMode);
        connect(window(), &QQuickWindow::sceneGraphInvalidated, this, &QQuick3DViewport::cleanupDirectRenderer, Qt::DirectConnection);
    }

    if (!m_effectiveTextureSize.isEmpty()) {
        m_effectiveTextureSize = QSize();
        emit effectiveTextureSizeChanged();
    }

    const QSizeF targetSize = window()->effectiveDevicePixelRatio() * QSizeF(width(), height());
    m_directRenderer->setViewport(QRectF(window()->effectiveDevicePixelRatio() * mapToScene(QPointF(0, 0)), targetSize));
    m_directRenderer->setVisibility(isVisible());
    if (isVisible()) {
        m_directRenderer->preSynchronize();
        m_directRenderer->renderer()->synchronize(this, targetSize.toSize(), window()->effectiveDevicePixelRatio());
        updateDynamicTextures();
        m_directRenderer->requestRender();
    }
}

// This is used for offscreen mode since we need to check if
// this item is used by an effect but hidden
bool QQuick3DViewport::checkIsVisible() const
{
    auto childPrivate = QQuickItemPrivate::get(this);
    return (childPrivate->explicitVisible ||
            (childPrivate->extra.isAllocated() && childPrivate->extra->effectRefCount));

}

/*!
    \internal

    This method processes a pickResult on an object with the objective of checking
    if there are any QQuickItem based subscenes that the QPointerEvent needs to be
    forwarded to (3D -> 2D). If such a subscene is found, the event will be mapped
    to the correct cordinate system, and added to the visitedSubscenes map for later
    event delivery.
*/
void QQuick3DViewport::processPickedObject(const QSSGRenderPickResult &pickResult,
                                           int pointIndex,
                                           QPointerEvent *event,
                                           QFlatMap<QQuickItem *, SubsceneInfo> &visitedSubscenes) const
{
    QQuickItem *subsceneRootItem = nullptr;
    QPointF subscenePosition;
    const auto backendObject = pickResult.m_hitObject;
    const auto frontendObject = findFrontendNode(backendObject);
    if (!frontendObject)
        return;

    // Figure out if there are any QQuickItem based scenes we need to forward
    // the event to, and if the are found, determine how to translate the UV Coords
    // in the pickResult based on what type the object containing the scene is.
    auto frontendObjectPrivate = QQuick3DObjectPrivate::get(frontendObject);
    if (frontendObjectPrivate->type == QQuick3DObjectPrivate::Type::Item2D) {
        // Item2D, this is the case where there is just an embedded Qt Quick 2D Item
        // rendered directly to the scene.
        auto item2D = qobject_cast<QQuick3DItem2D *>(frontendObject);
        if (item2D)
            subsceneRootItem = item2D->contentItem();
        if (!subsceneRootItem || subsceneRootItem->childItems().isEmpty())
            return; // ignore empty 2D subscenes

        // In this case the "UV" coordinates are in pixels in the subscene root item's coordinate system.
        subscenePosition = pickResult.m_localUVCoords.toPointF();

        // The following code will account for custom input masking, as well any
        // transformations that might have been applied to the Item
        if (!subsceneRootItem->childAt(subscenePosition.x(), subscenePosition.y()))
            return;
    } else if (frontendObjectPrivate->type == QQuick3DObjectPrivate::Type::Model) {
        // Model
        int materialSubset = pickResult.m_subset;
        const auto backendModel = static_cast<const QSSGRenderModel *>(backendObject);
        // Get material
        if (backendModel->materials.size() < (pickResult.m_subset + 1))
            materialSubset = backendModel->materials.size() - 1;
        if (materialSubset < 0)
            return;
        const auto backendMaterial = backendModel->materials.at(materialSubset);
        const auto frontendMaterial = static_cast<QQuick3DMaterial*>(findFrontendNode(backendMaterial));
        subsceneRootItem = getSubSceneRootItem(frontendMaterial);

        if (subsceneRootItem) {
            // In this case the pick result really is using UV coordinates.
            subscenePosition = QPointF(subsceneRootItem->x() + pickResult.m_localUVCoords.x() * subsceneRootItem->width(),
                                       subsceneRootItem->y() - pickResult.m_localUVCoords.y() * subsceneRootItem->height() + subsceneRootItem->height());
        }
    }

    //  Add the new event (item and position) to the visitedSubscene map.
    if (subsceneRootItem) {
        SubsceneInfo &subscene = visitedSubscenes[subsceneRootItem]; // create if not found
        subscene.obj = frontendObject;
        if (subscene.eventPointScenePositions.size() != event->pointCount()) {
            // ensure capacity, and use an out-of-scene position rather than 0,0 by default
            constexpr QPointF inf(-qt_inf(), -qt_inf());
            subscene.eventPointScenePositions.resize(event->pointCount(), inf);
        }
        subscene.eventPointScenePositions[pointIndex] = subscenePosition;
    }
}

/*!
    \internal

    This method will try and find a QQuickItem based by looking for sourceItem()
    properties on the primary color channels for the various material types.

    \note for CustomMaterial there is just a best effort given where the first
    associated Texture with a sourceItem property is used.
*/

QQuickItem *QQuick3DViewport::getSubSceneRootItem(QQuick3DMaterial *material) const
{
    if (!material)
        return nullptr;

    QQuickItem *subsceneRootItem = nullptr;
    const auto frontendMaterialPrivate = QQuick3DObjectPrivate::get(material);

    if (frontendMaterialPrivate->type == QQuick3DObjectPrivate::Type::DefaultMaterial) {
        // Default Material
        const auto defaultMaterial = qobject_cast<QQuick3DDefaultMaterial *>(material);
        if (defaultMaterial) {
            // Just check for a diffuseMap for now
            if (defaultMaterial->diffuseMap() && defaultMaterial->diffuseMap()->sourceItem())
                subsceneRootItem = defaultMaterial->diffuseMap()->sourceItem();
        }

    } else if (frontendMaterialPrivate->type == QQuick3DObjectPrivate::Type::PrincipledMaterial) {
        // Principled Material
        const auto principledMaterial = qobject_cast<QQuick3DPrincipledMaterial *>(material);
        if (principledMaterial) {
            // Just check for a baseColorMap for now
            if (principledMaterial->baseColorMap() && principledMaterial->baseColorMap()->sourceItem())
                subsceneRootItem = principledMaterial->baseColorMap()->sourceItem();
        }
    } else if (frontendMaterialPrivate->type == QQuick3DObjectPrivate::Type::SpecularGlossyMaterial) {
        // SpecularGlossy Material
        const auto specularGlossyMaterial = qobject_cast<QQuick3DSpecularGlossyMaterial *>(material);
        if (specularGlossyMaterial) {
            // Just check for a albedoMap for now
            if (specularGlossyMaterial->albedoMap() && specularGlossyMaterial->albedoMap()->sourceItem())
                subsceneRootItem = specularGlossyMaterial->albedoMap()->sourceItem();
        }
    } else if (frontendMaterialPrivate->type == QQuick3DObjectPrivate::Type::CustomMaterial) {
        // Custom Material
        const auto customMaterial = qobject_cast<QQuick3DCustomMaterial *>(material);
        if (customMaterial) {
            // This case is a bit harder because we can not know how the textures will be used
            const auto &texturesInputs = customMaterial->m_dynamicTextureMaps;
            for (const auto &textureInput : texturesInputs) {
                if (auto texture = textureInput->texture()) {
                    if (texture->sourceItem()) {
                        subsceneRootItem = texture->sourceItem();
                        break;
                    }
                }
            }
        }
    }
    return subsceneRootItem;
}


/*!
    \internal
*/
QQuick3DPickResult QQuick3DViewport::getNearestPickResult(const QVarLengthArray<QSSGRenderPickResult, 20> &pickResults) const
{
    for (const auto &result : pickResults) {
        auto pickResult = processPickResult(result);
        if (pickResult.hitType() != QQuick3DPickResultEnums::HitType::Null)
            return pickResult;
    }

    return QQuick3DPickResult();
}

/*!
    \internal
    This method is responsible for going through the visitedSubscenes map and forwarding
    the event with corrected coordinates to each subscene.

*/
bool QQuick3DViewport::forwardEventToSubscenes(QPointerEvent *event,
                                                bool useRayPicking,
                                                QQuick3DSceneRenderer *renderer,
                                                const QFlatMap<QQuickItem *, SubsceneInfo> &visitedSubscenes) const
{
    // Now deliver the entire event (all points) to each relevant subscene.
    // Maybe only some points fall inside, but QQuickDeliveryAgentPrivate::deliverMatchingPointsToItem()
    // makes reduced-subset touch events that contain only the relevant points, when necessary.
    bool ret = false;

    QVarLengthArray<QPointF, 16> originalScenePositions;
    originalScenePositions.resize(event->pointCount());
    for (int pointIndex = 0; pointIndex < event->pointCount(); ++pointIndex)
        originalScenePositions[pointIndex] = event->point(pointIndex).scenePosition();
    for (auto subscene : visitedSubscenes) {
        QQuickItem *subsceneRoot = subscene.first;
        auto &subsceneInfo = subscene.second;
        Q_ASSERT(subsceneInfo.eventPointScenePositions.size() == event->pointCount());
        auto da = QQuickItemPrivate::get(subsceneRoot)->deliveryAgent();
        for (int pointIndex = 0; pointIndex < event->pointCount(); ++pointIndex) {
            const auto &pt = subsceneInfo.eventPointScenePositions.at(pointIndex);
            // By tradition, QGuiApplicationPrivate::processTouchEvent() has set the local position to the scene position,
            // and Qt Quick expects it to arrive that way: then QQuickDeliveryAgentPrivate::translateTouchEvent()
            // copies it into the scene position before localizing.
            // That might be silly, we might change it eventually, but gotta stay consistent for now.
            QEventPoint &ep = event->point(pointIndex);
            QMutableEventPoint::setPosition(ep, pt);
            QMutableEventPoint::setScenePosition(ep, pt);
        }

        if (event->isBeginEvent())
            da->setSceneTransform(nullptr);
        if (da->event(event)) {
            ret = true;
            if (QQuickDeliveryAgentPrivate::anyPointGrabbed(event) && !useRayPicking) {
                // In case any QEventPoint was grabbed, the relevant QQuickDeliveryAgent needs to know
                // how to repeat the picking/coordinate transformation for each update,
                // because delivery will bypass internalPick() due to the grab, and it's
                // more efficient to avoid whole-scene picking each time anyway.
                auto frontendObjectPrivate = QQuick3DObjectPrivate::get(subsceneInfo.obj);
                const bool item2Dcase = (frontendObjectPrivate->type == QQuick3DObjectPrivate::Type::Item2D);
                ViewportTransformHelper *transform = new ViewportTransformHelper;
                transform->viewport = const_cast<QQuick3DViewport *>(this);
                transform->renderer = renderer;
                transform->sceneParentNode = static_cast<QSSGRenderNode*>(frontendObjectPrivate->spatialNode);
                transform->targetItem = subsceneRoot;
                transform->scaleX = window()->effectiveDevicePixelRatio() * m_widthMultiplier;
                transform->scaleY = window()->effectiveDevicePixelRatio() * m_heightMultiplier;
                transform->uvCoordsArePixels = item2Dcase;
                transform->setOnDeliveryAgent(da);
                qCDebug(lcPick) << event->type() << "created ViewportTransformHelper on" << da;
            }
        } else if (event->type() != QEvent::HoverMove) {
            qCDebug(lcPick) << subsceneRoot << "didn't want" << event;
        }
        event->setAccepted(false); // reject implicit grab and let it keep propagating
    }
    if (visitedSubscenes.isEmpty()) {
        event->setAccepted(false);
    } else {
        for (int pointIndex = 0; pointIndex < event->pointCount(); ++pointIndex)
            QMutableEventPoint::setScenePosition(event->point(pointIndex), originalScenePositions.at(pointIndex));
    }

    // Normally this would occur in QQuickWindowPrivate::clearGrabbers(...) but
    // for ray based input, input never goes through QQuickWindow (since events
    // are generated from within scene space and not window/screen space).
    if (event->isEndEvent() && useRayPicking) {
        if (event->isSinglePointEvent()) {
            if (static_cast<QSinglePointEvent *>(event)->buttons() == Qt::NoButton) {
                auto &firstPt = event->point(0);
                event->setExclusiveGrabber(firstPt, nullptr);
                event->clearPassiveGrabbers(firstPt);
            }
        } else {
            for (auto &point : event->points()) {
                if (point.state() == QEventPoint::State::Released) {
                    event->setExclusiveGrabber(point, nullptr);
                    event->clearPassiveGrabbers(point);
                }
            }
        }
    }

    return ret;
}


bool QQuick3DViewport::internalPick(QPointerEvent *event, const QVector3D &origin, const QVector3D &direction) const
{
    QQuick3DSceneRenderer *renderer = getRenderer();
    if (!renderer || !event)
        return false;

    QFlatMap<QQuickItem*, SubsceneInfo> visitedSubscenes;
    const bool useRayPicking = !direction.isNull();

    for (int pointIndex = 0; pointIndex < event->pointCount(); ++pointIndex) {
        auto &eventPoint = event->point(pointIndex);
        QVarLengthArray<QSSGRenderPickResult, 20> pickResults;
        if (Q_UNLIKELY(useRayPicking))
            pickResults = getPickResults(renderer, origin, direction);
        else
            pickResults = getPickResults(renderer, eventPoint);

        if (!pickResults.isEmpty())
            for (const auto &pickResult : pickResults)
                processPickedObject(pickResult, pointIndex, event, visitedSubscenes);
        else
            eventPoint.setAccepted(false); // let it fall through the viewport to Items underneath
    }

    return forwardEventToSubscenes(event, useRayPicking, renderer, visitedSubscenes);
}

bool QQuick3DViewport::singlePointPick(QSinglePointEvent *event, const QVector3D &origin, const QVector3D &direction)
{
    QQuick3DSceneRenderer *renderer = getRenderer();
    if (!renderer || !event)
        return false;

    QSSGRenderRay ray(origin, direction);

    Q_ASSERT(event->pointCount() == 1);
    QPointF originalPosition = event->point(0).scenePosition();

    auto pickResults = renderer->syncPickAll(ray);

    bool delivered = false;

    constexpr float jitterLimit = 2.5; // TODO: add property for this?
    bool withinJitterLimit = false;

    for (const auto &pickResult : pickResults) {
        auto [item, position] = getItemAndPosition(pickResult);
        if (!item)
            break;
        if (item == m_prevMouseItem && (position - m_prevMousePos).manhattanLength() < jitterLimit && !event->button()) {
            withinJitterLimit = true;
            break;
        }
        auto da = QQuickItemPrivate::get(item)->deliveryAgent();
        QEventPoint &ep = event->point(0);
        QMutableEventPoint::setPosition(ep, position);
        QMutableEventPoint::setScenePosition(ep, position);
        if (da->event(event)) {
            delivered = true;
            if (event->type() == QEvent::MouseButtonPress) {
                m_prevMouseItem = item;
                m_prevMousePos = position;
                withinJitterLimit = true;
            }
            break;
        }
    }

    QMutableEventPoint::setScenePosition(event->point(0), originalPosition);
    if (!withinJitterLimit)
        m_prevMouseItem = nullptr;

    // Normally this would occur in QQuickWindowPrivate::clearGrabbers(...) but
    // for ray based input, input never goes through QQuickWindow (since events
    // are generated from within scene space and not window/screen space).
    if (event->isEndEvent()) {
        if (event->buttons() == Qt::NoButton) {
            auto &firstPt = event->point(0);
            event->setExclusiveGrabber(firstPt, nullptr);
            event->clearPassiveGrabbers(firstPt);
        }
    }

    return delivered;
}

QPair<QQuickItem *, QPointF> QQuick3DViewport::getItemAndPosition(const QSSGRenderPickResult &pickResult)
{
    QQuickItem *subsceneRootItem = nullptr;
    QPointF subscenePosition;
    const auto backendObject = pickResult.m_hitObject;
    const auto frontendObject = findFrontendNode(backendObject);
    if (!frontendObject)
        return {};
    auto frontendObjectPrivate = QQuick3DObjectPrivate::get(frontendObject);
    if (frontendObjectPrivate->type == QQuick3DObjectPrivate::Type::Item2D) {
        // Item2D, this is the case where there is just an embedded Qt Quick 2D Item
        // rendered directly to the scene.
        auto item2D = qobject_cast<QQuick3DItem2D *>(frontendObject);
        if (item2D)
            subsceneRootItem = item2D->contentItem();
        if (!subsceneRootItem || subsceneRootItem->childItems().isEmpty())
            return {}; // ignore empty 2D subscenes

        // In this case the "UV" coordinates are in pixels in the subscene root item's coordinate system.
        subscenePosition = pickResult.m_localUVCoords.toPointF();

        // The following code will account for custom input masking, as well any
        // transformations that might have been applied to the Item
        if (!subsceneRootItem->childAt(subscenePosition.x(), subscenePosition.y()))
            return {};
    } else if (frontendObjectPrivate->type == QQuick3DObjectPrivate::Type::Model) {
        // Model
        int materialSubset = pickResult.m_subset;
        const auto backendModel = static_cast<const QSSGRenderModel *>(backendObject);
        // Get material
        if (backendModel->materials.size() < (pickResult.m_subset + 1))
            materialSubset = backendModel->materials.size() - 1;
        if (materialSubset < 0)
            return {};
        const auto backendMaterial = backendModel->materials.at(materialSubset);
        const auto frontendMaterial = static_cast<QQuick3DMaterial *>(findFrontendNode(backendMaterial));
        subsceneRootItem = getSubSceneRootItem(frontendMaterial);

        if (subsceneRootItem) {
            // In this case the pick result really is using UV coordinates.
            subscenePosition = QPointF(subsceneRootItem->x() + pickResult.m_localUVCoords.x() * subsceneRootItem->width(),
                                       subsceneRootItem->y() - pickResult.m_localUVCoords.y() * subsceneRootItem->height() + subsceneRootItem->height());
        }
    }
    return {subsceneRootItem, subscenePosition};
}

QVarLengthArray<QSSGRenderPickResult, 20> QQuick3DViewport::getPickResults(QQuick3DSceneRenderer *renderer,
                                                                           const QVector3D &origin,
                                                                           const QVector3D &direction) const
{
    const QSSGRenderRay ray(origin, direction);
    return renderer->syncPickAll(ray);
}

QVarLengthArray<QSSGRenderPickResult, 20> QQuick3DViewport::getPickResults(QQuick3DSceneRenderer *renderer, const QEventPoint &eventPoint) const
{
    QVarLengthArray<QSSGRenderPickResult, 20> pickResults;
    QPointF realPosition = eventPoint.position() * window()->effectiveDevicePixelRatio();
    // correct when mapping is not 1:1 due to explicit backing texture size
    realPosition.rx() *= m_widthMultiplier;
    realPosition.ry() *= m_heightMultiplier;
    std::optional<QSSGRenderRay> rayResult = renderer->getRayFromViewportPos(realPosition);
    if (rayResult.has_value())
        pickResults = renderer->syncPickAll(rayResult.value());
    return pickResults;
}

/*!
    \internal

    This provides a way to lookup frontendNodes with a backend node taking into consideration both
    the scene and the importScene
*/
QQuick3DObject *QQuick3DViewport::findFrontendNode(const QSSGRenderGraphObject *backendObject) const
{
    if (!backendObject)
        return nullptr;

    const auto sceneManager = QQuick3DObjectPrivate::get(m_sceneRoot)->sceneManager;
    QQuick3DObject *frontendObject = sceneManager->lookUpNode(backendObject);
    if (!frontendObject && m_importScene) {
        const auto importSceneManager = QQuick3DObjectPrivate::get(m_importScene)->sceneManager;
        frontendObject = importSceneManager->lookUpNode(backendObject);
    }
    return frontendObject;
}

QQuick3DPickResult QQuick3DViewport::processPickResult(const QSSGRenderPickResult &pickResult) const
{
    if (!pickResult.m_hitObject)
        return QQuick3DPickResult();

    QQuick3DObject *frontendObject = findFrontendNode(pickResult.m_hitObject);

    QQuick3DModel *model = qobject_cast<QQuick3DModel *>(frontendObject);
    if (model)
        return QQuick3DPickResult(model,
                                  ::sqrtf(pickResult.m_distanceSq),
                                  pickResult.m_localUVCoords,
                                  pickResult.m_scenePosition,
                                  pickResult.m_localPosition,
                                  pickResult.m_faceNormal,
                                  pickResult.m_instanceIndex);

    QQuick3DItem2D *frontend2DItem = qobject_cast<QQuick3DItem2D *>(frontendObject);
    if (frontend2DItem && frontend2DItem->contentItem()) {
        // Check if the pick is inside the content item (since the ray just intersected on the items plane)
        const QPointF subscenePosition = pickResult.m_localUVCoords.toPointF();
        const auto child = frontend2DItem->contentItem()->childAt(subscenePosition.x(), subscenePosition.y());
        if (child) {
            return QQuick3DPickResult(child,
                                      ::sqrtf(pickResult.m_distanceSq),
                                      QVector2D(frontend2DItem->contentItem()->mapToItem(child, subscenePosition)),
                                      pickResult.m_scenePosition,
                                      pickResult.m_localPosition,
                                      pickResult.m_faceNormal);
        }
    }

    return QQuick3DPickResult();

}

// Returns the first found scene manager of objects children
QQuick3DSceneManager *QQuick3DViewport::findChildSceneManager(QQuick3DObject *inObject, QQuick3DSceneManager *manager)
{
    if (manager)
        return manager;

    auto children = QQuick3DObjectPrivate::get(inObject)->childItems;
    for (auto child : children) {
        if (auto m = QQuick3DObjectPrivate::get(child)->sceneManager) {
            manager = m;
            break;
        }
        manager = findChildSceneManager(child, manager);
    }
    return manager;
}

void QQuick3DViewport::updateInputProcessing()
{
    // This should be called from the gui thread.
    setAcceptTouchEvents(m_enableInputProcessing);
    setAcceptHoverEvents(m_enableInputProcessing);
    setAcceptedMouseButtons(m_enableInputProcessing ? Qt::AllButtons : Qt::NoButton);
}

void QQuick3DViewport::onReleaseCachedResources()
{
    if (auto renderer = getRenderer())
        renderer->releaseCachedResources();
}

/*!
    \qmlproperty List<QtQuick3D::Object3D> View3D::extensions

    This property contains a list of user extensions that should be used with this \l View3D.

    \sa RenderExtension
*/
QQmlListProperty<QQuick3DObject> QQuick3DViewport::extensions()
{
    return QQmlListProperty<QQuick3DObject>{ this,
                                              &m_extensionListDirty,
                                              &QQuick3DExtensionListHelper::extensionAppend,
                                              &QQuick3DExtensionListHelper::extensionCount,
                                              &QQuick3DExtensionListHelper::extensionAt,
                                              &QQuick3DExtensionListHelper::extensionClear,
                                              &QQuick3DExtensionListHelper::extensionReplace,
                                              &QQuick3DExtensionListHelper::extensionRemoveLast};
}

/*!
    \internal
 */
void QQuick3DViewport::rebuildExtensionList()
{
    m_extensionListDirty = true;
    update();
}

/*!
    \internal

    Private constructor for the QQuick3DViewport class so we can differentiate between
    a regular QQuick3DViewport and one created for a specific usage, like XR.
 */
QQuick3DViewport::QQuick3DViewport(PrivateInstanceType type, QQuickItem *parent)
    : QQuick3DViewport(parent)
{
    m_isXrViewInstance = type == PrivateInstanceType::XrViewInstance;
}

void QQuick3DViewport::updateCameraForLayer(const QQuick3DViewport &view3D, QSSGRenderLayer &layerNode)
{
    layerNode.explicitCameras.clear();
    if (!view3D.m_multiViewCameras.isEmpty()) {
        for (QQuick3DCamera *camera : std::as_const(view3D.m_multiViewCameras))
            layerNode.explicitCameras.append(static_cast<QSSGRenderCamera *>(QQuick3DObjectPrivate::get(camera)->spatialNode));
    } else if (view3D.camera()) {
        layerNode.explicitCameras.append(static_cast<QSSGRenderCamera *>(QQuick3DObjectPrivate::get(view3D.camera())->spatialNode));
    }
}

QT_END_NAMESPACE
