/****************************************************************************
**
** Copyright (C) 2014 Klaralvdalens Datakonsult AB (KDAB).
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt3D module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "qrenderaspect.h"
#include "qrenderaspect_p.h"

#include <Qt3DRender/private/nodemanagers_p.h>
#include <Qt3DRender/private/abstractrenderer_p.h>
#include <Qt3DRender/private/scenemanager_p.h>
#include <Qt3DRender/private/geometryrenderermanager_p.h>

#include <Qt3DRender/qsceneloader.h>
#include <Qt3DRender/qcamera.h>
#include <Qt3DRender/qcameraselector.h>
#include <Qt3DRender/qlayer.h>
#include <Qt3DRender/qlayerfilter.h>
#include <Qt3DRender/qlevelofdetail.h>
#include <Qt3DRender/qlevelofdetailswitch.h>
#include <Qt3DRender/qmaterial.h>
#include <Qt3DRender/qmesh.h>
#include <Qt3DRender/qparameter.h>
#include <Qt3DRender/qrenderpassfilter.h>
#include <Qt3DRender/qrenderpass.h>
#include <Qt3DRender/qrendertargetselector.h>
#include <Qt3DRender/qtechniquefilter.h>
#include <Qt3DRender/qtechnique.h>
#include <Qt3DRender/qviewport.h>
#include <Qt3DRender/qrendertarget.h>
#include <Qt3DRender/qclearbuffers.h>
#include <Qt3DRender/qtexture.h>
#include <Qt3DRender/qeffect.h>
#include <Qt3DRender/qshaderdata.h>
#include <Qt3DRender/qrenderstateset.h>
#include <Qt3DRender/qnodraw.h>
#include <Qt3DRender/qnopicking.h>
#include <Qt3DRender/qcameralens.h>
#include <Qt3DRender/qattribute.h>
#include <Qt3DRender/qbuffer.h>
#include <Qt3DRender/qgeometry.h>
#include <Qt3DRender/qgeometryrenderer.h>
#include <Qt3DRender/qobjectpicker.h>
#include <Qt3DRender/qraycaster.h>
#include <Qt3DRender/qscreenraycaster.h>
#include <Qt3DRender/qfrustumculling.h>
#include <Qt3DRender/qabstractlight.h>
#include <Qt3DRender/qenvironmentlight.h>
#include <Qt3DRender/qdispatchcompute.h>
#include <Qt3DRender/qcomputecommand.h>
#include <Qt3DRender/qrendersurfaceselector.h>
#include <Qt3DRender/qrendersettings.h>
#include <Qt3DRender/qrendercapture.h>
#include <Qt3DRender/qbuffercapture.h>
#include <Qt3DRender/qmemorybarrier.h>
#include <Qt3DRender/qproximityfilter.h>
#include <Qt3DRender/qshaderprogrambuilder.h>
#include <Qt3DRender/qblitframebuffer.h>
#include <Qt3DRender/qsetfence.h>
#include <Qt3DRender/qwaitfence.h>
#include <Qt3DRender/qshaderimage.h>
#include <Qt3DRender/qsubtreeenabler.h>
#include <Qt3DRender/qdebugoverlay.h>
#include <Qt3DCore/qarmature.h>
#include <Qt3DCore/qjoint.h>
#include <Qt3DCore/qskeletonloader.h>

#include <Qt3DRender/private/backendnode_p.h>
#include <Qt3DRender/private/cameraselectornode_p.h>
#include <Qt3DRender/private/layerfilternode_p.h>
#include <Qt3DRender/private/cameralens_p.h>
#include <Qt3DRender/private/filterkey_p.h>
#include <Qt3DRender/private/entity_p.h>
#include <Qt3DRender/private/abstractrenderer_p.h>
#include <Qt3DRender/private/shaderdata_p.h>
#include <Qt3DRender/private/renderpassfilternode_p.h>
#include <Qt3DRender/private/rendertargetselectornode_p.h>
#include <Qt3DRender/private/techniquefilternode_p.h>
#include <Qt3DRender/private/viewportnode_p.h>
#include <Qt3DRender/private/rendertarget_p.h>
#include <Qt3DRender/private/scenemanager_p.h>
#include <Qt3DRender/private/clearbuffers_p.h>
#include <Qt3DRender/private/sortpolicy_p.h>
#include <Qt3DRender/private/renderlogging_p.h>
#include <Qt3DRender/private/nodefunctor_p.h>
#include <Qt3DRender/private/framegraphnode_p.h>
#include <Qt3DRender/private/textureimage_p.h>
#include <Qt3DRender/private/statesetnode_p.h>
#include <Qt3DRender/private/nodraw_p.h>
#include <Qt3DRender/private/nopicking_p.h>
#include <Qt3DRender/private/vsyncframeadvanceservice_p.h>
#include <Qt3DRender/private/attribute_p.h>
#include <Qt3DRender/private/buffer_p.h>
#include <Qt3DRender/private/geometry_p.h>
#include <Qt3DRender/private/geometryrenderer_p.h>
#include <Qt3DRender/private/objectpicker_p.h>
#include <Qt3DRender/private/raycaster_p.h>
#include <Qt3DRender/private/boundingvolumedebug_p.h>
#include <Qt3DRender/private/nodemanagers_p.h>
#include <Qt3DRender/private/calcgeometrytrianglevolumes_p.h>
#include <Qt3DRender/private/handle_types_p.h>
#include <Qt3DRender/private/buffermanager_p.h>
#include <Qt3DRender/private/geometryrenderermanager_p.h>
#include <Qt3DRender/private/loadgeometryjob_p.h>
#include <Qt3DRender/private/qsceneimportfactory_p.h>
#include <Qt3DRender/private/qsceneimporter_p.h>
#include <Qt3DRender/private/frustumculling_p.h>
#include <Qt3DRender/private/light_p.h>
#include <Qt3DRender/private/environmentlight_p.h>
#include <Qt3DRender/private/dispatchcompute_p.h>
#include <Qt3DRender/private/computecommand_p.h>
#include <Qt3DRender/private/rendersurfaceselector_p.h>
#include <Qt3DRender/private/rendersettings_p.h>
#include <Qt3DRender/private/backendnode_p.h>
#include <Qt3DRender/private/rendercapture_p.h>
#include <Qt3DRender/private/buffercapture_p.h>
#include <Qt3DRender/private/technique_p.h>
#include <Qt3DRender/private/offscreensurfacehelper_p.h>
#include <Qt3DRender/private/memorybarrier_p.h>
#include <Qt3DRender/private/shaderbuilder_p.h>
#include <Qt3DRender/private/blitframebuffer_p.h>
#include <Qt3DRender/private/subtreeenabler_p.h>
#include <Qt3DRender/private/armature_p.h>
#include <Qt3DRender/private/skeleton_p.h>
#include <Qt3DRender/private/joint_p.h>
#include <Qt3DRender/private/loadskeletonjob_p.h>
#include <Qt3DRender/private/proximityfilter_p.h>
#include <Qt3DRender/private/setfence_p.h>
#include <Qt3DRender/private/waitfence_p.h>
#include <Qt3DRender/private/shaderimage_p.h>
#include <Qt3DRender/private/debugoverlay_p.h>
#include <Qt3DRender/private/qrendererpluginfactory_p.h>
#include <Qt3DRender/private/updatelevelofdetailjob_p.h>
#include <Qt3DRender/private/job_common_p.h>
#include <Qt3DRender/private/pickeventfilter_p.h>
#include <Qt3DRender/private/loadbufferjob_p.h>
#include <Qt3DRender/private/techniquemanager_p.h>
#include <Qt3DRender/private/qgraphicsapifilter_p.h>

#include <private/qrenderpluginfactory_p.h>
#include <private/qrenderplugin_p.h>

#include <Qt3DCore/qentity.h>
#include <Qt3DCore/qtransform.h>

#include <Qt3DCore/qnode.h>
#include <Qt3DCore/QAspectEngine>
#include <Qt3DCore/private/qservicelocator_p.h>
#include <Qt3DCore/private/qscene_p.h>
#include <Qt3DCore/private/qentity_p.h>
#include <Qt3DCore/private/qaspectmanager_p.h>
#include <Qt3DCore/private/qeventfilterservice_p.h>

#include <QThread>
#include <QOpenGLContext>

QT_BEGIN_NAMESPACE

using namespace Qt3DCore;

namespace {

QString dumpNode(const Qt3DCore::QEntity *n) {
    auto formatNode = [](const Qt3DCore::QNode *n) {
        QString res = QString(QLatin1String("%1{%2}"))
                          .arg(QLatin1String(n->metaObject()->className()))
                          .arg(n->id().id());
        if (!n->objectName().isEmpty())
            res += QString(QLatin1String(" (%1)")).arg(n->objectName());
        if (!n->isEnabled())
            res += QLatin1String(" [D]");
        return res;
    };

    return formatNode(n);
}

QString dumpNodeFilters(const QString &filterType, const QVector<Qt3DRender::QFilterKey*> &filters) {
    QString res;

    QStringList kv;
    for (auto filter: filters)
        kv.push_back(QString(QLatin1String("%1: %2")).arg(filter->name(), filter->value().toString()));
    if (kv.size())
        res += QString(QLatin1String("%1 <%2>")).arg(filterType, kv.join(QLatin1String(", ")));

    return res;
}

QStringList dumpSGFilterState(Qt3DRender::Render::TechniqueManager *manager,
                              const Qt3DRender::GraphicsApiFilterData *contextData,
                              const Qt3DCore::QNode *n, int level = 0)
{
    using namespace Qt3DRender;

    QStringList reply;
    const auto *entity = qobject_cast<const Qt3DCore::QEntity *>(n);
    if (entity != nullptr) {
        QString res = dumpNode(entity);
        auto materials = entity->componentsOfType<QMaterial>();
        if (materials.size() && materials.front()->effect()) {
            auto m = materials.front();
            const auto techniques = m->effect()->techniques();
            for (auto t: m->effect()->techniques()) {
                auto apiFilter = t->graphicsApiFilter();
                if (apiFilter) {
                    auto backendTechnique = manager->lookupResource(t->id());
                    if (backendTechnique &&
                        !(*contextData == *backendTechnique->graphicsApiFilter()))
                        continue; // skip technique that doesn't match current renderer
                }

                QStringList filters;
                filters += dumpNodeFilters(QLatin1String("T"), t->filterKeys());

                const auto &renderPasses = t->renderPasses();
                for (auto r: renderPasses)
                    filters += dumpNodeFilters(QLatin1String("RP"), r->filterKeys());

                if (filters.size())
                    res += QLatin1String(" [ %1 ]").arg(filters.join(QLatin1String(" ")));
            }
        }
        reply += res.rightJustified(res.length() + level * 2, ' ');
        level++;
    }

    const auto children = n->childNodes();
    for (auto *child: children)
        reply += dumpSGFilterState(manager, contextData, child, level);

    return reply;
}

}
namespace Qt3DRender {

#define CreateSynchronizerJobPtr(lambda, type) \
    Render::SynchronizerJobPtr::create(lambda, type, #type)

/*!
 * \class Qt3DRender::QRenderAspect
 * \inheaderfile Qt3DRender/QRenderAspect
 * \brief The QRenderAspect class.
 * \since 5.7
 * \inmodule Qt3DRender
 */

/*!
    \namespace Qt3DRender::Render
    \inmodule Qt3DRender

    \brief Namespace used for accessing the classes
    Renderer and QRenderPlugin.
*/
/*! \internal */
QRenderAspectPrivate::QRenderAspectPrivate(QRenderAspect::RenderType type)
    : QAbstractAspectPrivate()
    , m_nodeManagers(nullptr)
    , m_renderer(nullptr)
    , m_initialized(false)
    , m_renderAfterJobs(false)
    , m_renderType(type)
    , m_offscreenHelper(nullptr)
    , m_updateTreeEnabledJob(Render::UpdateTreeEnabledJobPtr::create())
    , m_worldTransformJob(Render::UpdateWorldTransformJobPtr::create())
    , m_expandBoundingVolumeJob(Render::ExpandBoundingVolumeJobPtr::create())
    , m_calculateBoundingVolumeJob(Render::CalculateBoundingVolumeJobPtr::create())
    , m_updateWorldBoundingVolumeJob(Render::UpdateWorldBoundingVolumeJobPtr::create())
    , m_updateSkinningPaletteJob(Render::UpdateSkinningPaletteJobPtr::create())
    , m_updateLevelOfDetailJob(Render::UpdateLevelOfDetailJobPtr::create())
    , m_updateEntityLayersJob(Render::UpdateEntityLayersJobPtr::create())
    , m_syncLoadingJobs(CreateSynchronizerJobPtr([] {}, Render::JobTypes::SyncLoadingJobs))
    , m_pickBoundingVolumeJob(Render::PickBoundingVolumeJobPtr::create())
    , m_rayCastingJob(Render::RayCastingJobPtr::create())
    , m_pickEventFilter(new Render::PickEventFilter())
{
    m_instances.append(this);
    loadSceneParsers();
    if (m_renderType == QRenderAspect::Threaded && !QOpenGLContext::supportsThreadedOpenGL()) {
        m_renderType = QRenderAspect::Synchronous;
        m_renderAfterJobs = true;
    }

    m_updateWorldBoundingVolumeJob->addDependency(m_worldTransformJob);
    m_updateWorldBoundingVolumeJob->addDependency(m_calculateBoundingVolumeJob);
    m_calculateBoundingVolumeJob->addDependency(m_updateTreeEnabledJob);
    m_expandBoundingVolumeJob->addDependency(m_updateWorldBoundingVolumeJob);
    m_updateLevelOfDetailJob->addDependency(m_expandBoundingVolumeJob);
    m_pickBoundingVolumeJob->addDependency(m_expandBoundingVolumeJob);
    m_pickBoundingVolumeJob->addDependency(m_updateEntityLayersJob);
    m_rayCastingJob->addDependency(m_expandBoundingVolumeJob);
    m_rayCastingJob->addDependency(m_updateEntityLayersJob);
}

/*! \internal */
QRenderAspectPrivate::~QRenderAspectPrivate()
{
    // The renderer should have been shutdown as part of onUnregistered().
    // If it still exists then this aspect is being deleted before the aspect
    // engine is finished with it.
    if (m_renderer != nullptr)
        qWarning() << Q_FUNC_INFO << "The renderer should have been deleted when reaching this point (this warning may be normal when running tests)";
    delete m_nodeManagers;
    m_instances.removeAll(this);
    qDeleteAll(m_sceneImporter);
}

QRenderAspectPrivate *QRenderAspectPrivate::findPrivate(Qt3DCore::QAspectEngine *engine)
{
    const QVector<QAbstractAspect*> aspects = engine->aspects();
    for (QAbstractAspect* aspect : aspects) {
        QRenderAspect *renderAspect = qobject_cast<QRenderAspect *>(aspect);
        if (renderAspect)
            return static_cast<QRenderAspectPrivate *>(renderAspect->d_ptr.data());
    }
    return nullptr;
}

QRenderAspectPrivate *QRenderAspectPrivate::get(QRenderAspect *q)
{
    return q->d_func();
}

void QRenderAspectPrivate::syncDirtyFrontEndNode(QNode *node, QBackendNode *backend, bool firstTime) const
{
    Render::BackendNode *renderBackend = static_cast<Render::BackendNode *>(backend);
    renderBackend->syncFromFrontEnd(node, firstTime);
}

void QRenderAspectPrivate::jobsDone()
{
    m_renderer->jobsDone(m_aspectManager);
}

void QRenderAspectPrivate::frameDone()
{
    m_renderer->setJobsInLastFrame(m_aspectManager->jobsInLastFrame());
    if (m_renderAfterJobs)
        m_renderer->doRender(true);
}

void QRenderAspectPrivate::createNodeManagers()
{
    m_nodeManagers = new Render::NodeManagers();

    m_updateTreeEnabledJob->setManagers(m_nodeManagers);
    m_worldTransformJob->setManagers(m_nodeManagers);
    m_expandBoundingVolumeJob->setManagers(m_nodeManagers);
    m_calculateBoundingVolumeJob->setManagers(m_nodeManagers);
    m_updateWorldBoundingVolumeJob->setManager(m_nodeManagers->renderNodesManager());
    m_updateSkinningPaletteJob->setManagers(m_nodeManagers);
    m_updateLevelOfDetailJob->setManagers(m_nodeManagers);
    m_updateEntityLayersJob->setManager(m_nodeManagers);
    m_pickBoundingVolumeJob->setManagers(m_nodeManagers);
    m_rayCastingJob->setManagers(m_nodeManagers);
}

void QRenderAspectPrivate::onEngineStartup()
{
    Render::Entity *rootEntity = m_nodeManagers->lookupResource<Render::Entity, Render::EntityManager>(m_rootId);
    Q_ASSERT(rootEntity);
    m_renderer->setSceneRoot(rootEntity);

    m_worldTransformJob->setRoot(rootEntity);
    m_expandBoundingVolumeJob->setRoot(rootEntity);
    m_calculateBoundingVolumeJob->setRoot(rootEntity);
    m_updateLevelOfDetailJob->setRoot(rootEntity);
    m_updateSkinningPaletteJob->setRoot(rootEntity);
    m_updateTreeEnabledJob->setRoot(rootEntity);
    m_pickBoundingVolumeJob->setRoot(rootEntity);
    m_rayCastingJob->setRoot(rootEntity);

    // Ensures all skeletons are loaded before we try to update them
    m_updateSkinningPaletteJob->addDependency(m_syncLoadingJobs);
}

/*! \internal */
void QRenderAspectPrivate::registerBackendTypes()
{
    Q_Q(QRenderAspect);

    qRegisterMetaType<Qt3DRender::QBuffer*>();
    qRegisterMetaType<Qt3DRender::QEffect*>();
    qRegisterMetaType<Qt3DRender::QFrameGraphNode *>();
    qRegisterMetaType<Qt3DRender::QCamera*>();
    qRegisterMetaType<Qt3DRender::QShaderProgram*>();
    qRegisterMetaType<Qt3DRender::QViewport*>();
    qRegisterMetaType<Qt3DCore::QJoint*>();

    q->registerBackendType<Qt3DCore::QEntity, true>(QSharedPointer<Render::RenderEntityFunctor>::create(m_renderer, m_nodeManagers));
    q->registerBackendType<Qt3DCore::QTransform, true>(QSharedPointer<Render::NodeFunctor<Render::Transform, Render::TransformManager> >::create(m_renderer));

    q->registerBackendType<Qt3DRender::QCameraLens, true>(QSharedPointer<Render::CameraLensFunctor>::create(m_renderer, q));
    q->registerBackendType<QLayer, true>(QSharedPointer<Render::NodeFunctor<Render::Layer, Render::LayerManager> >::create(m_renderer));
    q->registerBackendType<QLevelOfDetail, true>(QSharedPointer<Render::NodeFunctor<Render::LevelOfDetail, Render::LevelOfDetailManager> >::create(m_renderer));
    q->registerBackendType<QLevelOfDetailSwitch, true>(QSharedPointer<Render::NodeFunctor<Render::LevelOfDetail, Render::LevelOfDetailManager> >::create(m_renderer));
    q->registerBackendType<QSceneLoader, true>(QSharedPointer<Render::RenderSceneFunctor>::create(m_renderer, m_nodeManagers->sceneManager()));
    q->registerBackendType<QRenderTarget, true>(QSharedPointer<Render::RenderTargetFunctor>::create(m_renderer, m_nodeManagers->renderTargetManager()));
    q->registerBackendType<QRenderTargetOutput, true>(QSharedPointer<Render::NodeFunctor<Render::RenderTargetOutput, Render::AttachmentManager> >::create(m_renderer));
    q->registerBackendType<QRenderSettings, true>(QSharedPointer<Render::RenderSettingsFunctor>::create(m_renderer));
    q->registerBackendType<QRenderState, true>(QSharedPointer<Render::NodeFunctor<Render::RenderStateNode, Render::RenderStateManager> >::create(m_renderer));

    // Geometry + Compute
    q->registerBackendType<QAttribute, true>(QSharedPointer<Render::NodeFunctor<Render::Attribute, Render::AttributeManager> >::create(m_renderer));
    q->registerBackendType<QBuffer, true>(QSharedPointer<Render::BufferFunctor>::create(m_renderer, m_nodeManagers->bufferManager()));
    q->registerBackendType<QComputeCommand, true>(QSharedPointer<Render::NodeFunctor<Render::ComputeCommand, Render::ComputeCommandManager> >::create(m_renderer));
    q->registerBackendType<QGeometry, true>(QSharedPointer<Render::NodeFunctor<Render::Geometry, Render::GeometryManager> >::create(m_renderer));
    q->registerBackendType<QGeometryRenderer, true>(QSharedPointer<Render::GeometryRendererFunctor>::create(m_renderer, m_nodeManagers->geometryRendererManager()));
    q->registerBackendType<Qt3DCore::QArmature, true>(QSharedPointer<Render::NodeFunctor<Render::Armature, Render::ArmatureManager>>::create(m_renderer));
    q->registerBackendType<Qt3DCore::QAbstractSkeleton, true>(QSharedPointer<Render::SkeletonFunctor>::create(m_renderer, m_nodeManagers->skeletonManager(), m_nodeManagers->jointManager()));
    q->registerBackendType<Qt3DCore::QJoint, true>(QSharedPointer<Render::JointFunctor>::create(m_renderer, m_nodeManagers->jointManager(), m_nodeManagers->skeletonManager()));

    // Textures
    q->registerBackendType<QAbstractTexture, true>(QSharedPointer<Render::TextureFunctor>::create(m_renderer, m_nodeManagers->textureManager()));
    q->registerBackendType<QAbstractTextureImage, true>(QSharedPointer<Render::TextureImageFunctor>::create(m_renderer,
                                                                                                      m_nodeManagers->textureImageManager()));

    // Material system
    q->registerBackendType<QEffect, true>(QSharedPointer<Render::NodeFunctor<Render::Effect, Render::EffectManager> >::create(m_renderer));
    q->registerBackendType<QFilterKey, true>(QSharedPointer<Render::NodeFunctor<Render::FilterKey, Render::FilterKeyManager> >::create(m_renderer));
    q->registerBackendType<QAbstractLight, true>(QSharedPointer<Render::RenderLightFunctor>::create(m_renderer, m_nodeManagers));
    q->registerBackendType<QEnvironmentLight, true>(QSharedPointer<Render::NodeFunctor<Render::EnvironmentLight, Render::EnvironmentLightManager> >::create(m_renderer));
    q->registerBackendType<QMaterial, true>(QSharedPointer<Render::NodeFunctor<Render::Material, Render::MaterialManager> >::create(m_renderer));
    q->registerBackendType<QParameter, true>(QSharedPointer<Render::NodeFunctor<Render::Parameter, Render::ParameterManager> >::create(m_renderer));
    q->registerBackendType<QRenderPass, true>(QSharedPointer<Render::NodeFunctor<Render::RenderPass, Render::RenderPassManager> >::create(m_renderer));
    q->registerBackendType<QShaderData, true>(QSharedPointer<Render::RenderShaderDataFunctor>::create(m_renderer, m_nodeManagers));
    q->registerBackendType<QShaderProgram, true>(QSharedPointer<Render::ShaderFunctor>::create(m_renderer, m_nodeManagers->shaderManager()));
    q->registerBackendType<QShaderProgramBuilder, true>(QSharedPointer<Render::NodeFunctor<Render::ShaderBuilder, Render::ShaderBuilderManager> >::create(m_renderer));
    q->registerBackendType<QTechnique, true>(QSharedPointer<Render::TechniqueFunctor>::create(m_renderer, m_nodeManagers));
    q->registerBackendType<QShaderImage, true>(QSharedPointer<Render::NodeFunctor<Render::ShaderImage, Render::ShaderImageManager>>::create(m_renderer));

    // Framegraph
    q->registerBackendType<QFrameGraphNode, true>(QSharedPointer<Render::FrameGraphNodeFunctor<Render::FrameGraphNode, QFrameGraphNode> >::create(m_renderer));
    q->registerBackendType<QCameraSelector, true>(QSharedPointer<Render::FrameGraphNodeFunctor<Render::CameraSelector, QCameraSelector> >::create(m_renderer));
    q->registerBackendType<QClearBuffers, true>(QSharedPointer<Render::FrameGraphNodeFunctor<Render::ClearBuffers, QClearBuffers> >::create(m_renderer));
    q->registerBackendType<QDispatchCompute, true>(QSharedPointer<Render::FrameGraphNodeFunctor<Render::DispatchCompute, QDispatchCompute> >::create(m_renderer));
    q->registerBackendType<QFrustumCulling, true>(QSharedPointer<Render::FrameGraphNodeFunctor<Render::FrustumCulling, QFrustumCulling> >::create(m_renderer));
    q->registerBackendType<QLayerFilter, true>(QSharedPointer<Render::FrameGraphNodeFunctor<Render::LayerFilterNode, QLayerFilter> >::create(m_renderer));
    q->registerBackendType<QNoDraw, true>(QSharedPointer<Render::FrameGraphNodeFunctor<Render::NoDraw, QNoDraw> >::create(m_renderer));
    q->registerBackendType<QRenderPassFilter, true>(QSharedPointer<Render::FrameGraphNodeFunctor<Render::RenderPassFilter, QRenderPassFilter> >::create(m_renderer));
    q->registerBackendType<QRenderStateSet, true>(QSharedPointer<Render::FrameGraphNodeFunctor<Render::StateSetNode, QRenderStateSet> >::create(m_renderer));
    q->registerBackendType<QRenderSurfaceSelector, true>(QSharedPointer<Render::FrameGraphNodeFunctor<Render::RenderSurfaceSelector, QRenderSurfaceSelector> >::create(m_renderer));
    q->registerBackendType<QRenderTargetSelector, true>(QSharedPointer<Render::FrameGraphNodeFunctor<Render::RenderTargetSelector, QRenderTargetSelector> >::create(m_renderer));
    q->registerBackendType<QSortPolicy, true>(QSharedPointer<Render::FrameGraphNodeFunctor<Render::SortPolicy, QSortPolicy> >::create(m_renderer));
    q->registerBackendType<QTechniqueFilter, true>(QSharedPointer<Render::FrameGraphNodeFunctor<Render::TechniqueFilter, QTechniqueFilter> >::create(m_renderer));
    q->registerBackendType<QViewport, true>(QSharedPointer<Render::FrameGraphNodeFunctor<Render::ViewportNode, QViewport> >::create(m_renderer));
    q->registerBackendType<QRenderCapture, true>(QSharedPointer<Render::FrameGraphNodeFunctor<Render::RenderCapture, QRenderCapture> >::create(m_renderer));
    q->registerBackendType<QBufferCapture, true>(QSharedPointer<Render::FrameGraphNodeFunctor<Render::BufferCapture, QBufferCapture> >::create(m_renderer));
    q->registerBackendType<QMemoryBarrier, true>(QSharedPointer<Render::FrameGraphNodeFunctor<Render::MemoryBarrier, QMemoryBarrier> >::create(m_renderer));
    q->registerBackendType<QProximityFilter, true>(QSharedPointer<Render::FrameGraphNodeFunctor<Render::ProximityFilter, QProximityFilter> >::create(m_renderer));
    q->registerBackendType<QBlitFramebuffer, true>(QSharedPointer<Render::FrameGraphNodeFunctor<Render::BlitFramebuffer, QBlitFramebuffer> >::create(m_renderer));
    q->registerBackendType<QSetFence, true>(QSharedPointer<Render::FrameGraphNodeFunctor<Render::SetFence, QSetFence> >::create(m_renderer));
    q->registerBackendType<QWaitFence, true>(QSharedPointer<Render::FrameGraphNodeFunctor<Render::WaitFence, QWaitFence> >::create(m_renderer));
    q->registerBackendType<QNoPicking, true>(QSharedPointer<Render::FrameGraphNodeFunctor<Render::NoPicking, QNoPicking> >::create(m_renderer));
    q->registerBackendType<QSubtreeEnabler, true>(QSharedPointer<Render::FrameGraphNodeFunctor<Render::SubtreeEnabler, QSubtreeEnabler> >::create(m_renderer));
    q->registerBackendType<QDebugOverlay, true>(QSharedPointer<Render::FrameGraphNodeFunctor<Render::DebugOverlay, QDebugOverlay> >::create(m_renderer));

    // Picking
    q->registerBackendType<QObjectPicker, true>(QSharedPointer<Render::NodeFunctor<Render::ObjectPicker, Render::ObjectPickerManager> >::create(m_renderer));
    q->registerBackendType<QRayCaster, true>(QSharedPointer<Render::NodeFunctor<Render::RayCaster, Render::RayCasterManager> >::create(m_renderer));
    q->registerBackendType<QScreenRayCaster, true>(QSharedPointer<Render::NodeFunctor<Render::RayCaster, Render::RayCasterManager> >::create(m_renderer));

    // Plugins
    for (const QString &plugin : qAsConst(m_pluginConfig))
        loadRenderPlugin(plugin);
}

/*! \internal */
void QRenderAspectPrivate::unregisterBackendTypes()
{
    Q_Q(QRenderAspect);
    unregisterBackendType<Qt3DCore::QEntity>();
    unregisterBackendType<Qt3DCore::QTransform>();

    unregisterBackendType<Qt3DRender::QCameraLens>();
    unregisterBackendType<QLayer>();
    unregisterBackendType<QSceneLoader>();
    unregisterBackendType<QRenderTarget>();
    unregisterBackendType<QRenderTargetOutput>();
    unregisterBackendType<QRenderSettings>();
    unregisterBackendType<QRenderState>();

    // Geometry + Compute
    unregisterBackendType<QAttribute>();
    unregisterBackendType<QBuffer>();
    unregisterBackendType<QComputeCommand>();
    unregisterBackendType<QGeometry>();
    unregisterBackendType<QGeometryRenderer>();
    unregisterBackendType<Qt3DCore::QArmature>();
    unregisterBackendType<Qt3DCore::QAbstractSkeleton>();
    unregisterBackendType<Qt3DCore::QJoint>();

    // Textures
    unregisterBackendType<QAbstractTexture>();
    unregisterBackendType<QAbstractTextureImage>();

    // Material system
    unregisterBackendType<QEffect>();
    unregisterBackendType<QFilterKey>();
    unregisterBackendType<QAbstractLight>();
    unregisterBackendType<QEnvironmentLight>();
    unregisterBackendType<QMaterial>();
    unregisterBackendType<QParameter>();
    unregisterBackendType<QRenderPass>();
    unregisterBackendType<QShaderData>();
    unregisterBackendType<QShaderProgram>();
    unregisterBackendType<QShaderProgramBuilder>();
    unregisterBackendType<QTechnique>();
    unregisterBackendType<QShaderImage>();

    // Framegraph
    unregisterBackendType<QCameraSelector>();
    unregisterBackendType<QClearBuffers>();
    unregisterBackendType<QDispatchCompute>();
    unregisterBackendType<QFrustumCulling>();
    unregisterBackendType<QLayerFilter>();
    unregisterBackendType<QNoDraw>();
    unregisterBackendType<QRenderPassFilter>();
    unregisterBackendType<QRenderStateSet>();
    unregisterBackendType<QRenderSurfaceSelector>();
    unregisterBackendType<QRenderTargetSelector>();
    unregisterBackendType<QSortPolicy>();
    unregisterBackendType<QTechniqueFilter>();
    unregisterBackendType<QViewport>();
    unregisterBackendType<QRenderCapture>();
    unregisterBackendType<QBufferCapture>();
    unregisterBackendType<QMemoryBarrier>();
    unregisterBackendType<QSetFence>();
    unregisterBackendType<QWaitFence>();
    unregisterBackendType<QSubtreeEnabler>();
    unregisterBackendType<QDebugOverlay>();

    // Picking
    unregisterBackendType<QObjectPicker>();
    unregisterBackendType<QRayCaster>();
    unregisterBackendType<QScreenRayCaster>();

    // Plugins
    for (Render::QRenderPlugin *plugin : qAsConst(m_renderPlugins))
        plugin->unregisterBackendTypes(q);
}

void QRenderAspectPrivate::registerBackendType(const QMetaObject &obj,
                                               const QBackendNodeMapperPtr &functor)
{
    Q_Q(QRenderAspect);
    q->registerBackendType(obj, functor);
}

/*!
 * The constructor creates a new QRenderAspect::QRenderAspect instance with the
 * specified \a parent.
 * \param parent
 */
QRenderAspect::QRenderAspect(QObject *parent)
    : QRenderAspect(Threaded, parent) {}

/*!
 * The constructor creates a new QRenderAspect::QRenderAspect instance with the
 * specified \a type and \a parent.
 * \param type
 * \param parent
 */
QRenderAspect::QRenderAspect(QRenderAspect::RenderType type, QObject *parent)
    : QRenderAspect(*new QRenderAspectPrivate(type), parent) {}

/*! \internal */
QRenderAspect::QRenderAspect(QRenderAspectPrivate &dd, QObject *parent)
    : QAbstractAspect(dd, parent)
{
    setObjectName(QStringLiteral("Render Aspect"));
}

/*! \internal */
QRenderAspect::~QRenderAspect()
{
}

// Called by Scene3DRenderer only
void QRenderAspectPrivate::renderInitialize(QOpenGLContext *context)
{
    if (m_renderer->api() == API::OpenGL)
        m_renderer->setOpenGLContext(context);
    m_renderer->initialize();
}

/*! \internal */
void QRenderAspectPrivate::renderSynchronous(bool swapBuffers)
{
    m_renderer->doRender(swapBuffers);
}

/*
 * \internal
 * Only called when rendering with QtQuick 2 and a Scene3D item
 */
void QRenderAspectPrivate::renderShutdown()
{
    if (m_renderer != nullptr)
        m_renderer->shutdown();
}

QVector<Qt3DCore::QAspectJobPtr> QRenderAspect::jobsToExecute(qint64 time)
{
    using namespace Render;

    Q_D(QRenderAspect);
    d->m_renderer->setTime(time);

#if defined(QT3D_RENDER_DUMP_BACKEND_NODES)
    d->m_renderer->dumpInfo();
#endif

    // Create jobs that will get executed by the threadpool
    QVector<QAspectJobPtr> jobs;

    // 1 LoadBufferJobs, GeometryJobs, SceneLoaderJobs, LoadTextureJobs
    // 2 CalculateBoundingVolumeJob (depends on LoadBuffer)
    // 3 WorldTransformJob
    // 4 UpdateBoundingVolume, FramePreparationJob (depend on WorlTransformJob)
    // 5 CalcGeometryTriangleVolumes (frame preparation job), RenderViewJobs
    // 6 PickBoundingVolumeJob
    // 7 Cleanup Job (depends on RV)

    // Ensure we have a settings object. It may get deleted by the call to
    // QChangeArbiter::syncChanges() that happens just before the render aspect is
    // asked for jobs to execute (this function). If that is the case, the RenderSettings will
    // be null and we should not generate any jobs.
    if (d->m_renderer->isRunning() && d->m_renderer->settings()) {
        NodeManagers *manager = d->m_nodeManagers;
        d->m_syncLoadingJobs->removeDependency(QWeakPointer<QAspectJob>());
        d->m_calculateBoundingVolumeJob->removeDependency(QWeakPointer<QAspectJob>());
        d->m_updateLevelOfDetailJob->setFrameGraphRoot(d->m_renderer->frameGraphRoot());

        // Launch skeleton loader jobs once all loading jobs have completed.
        const QVector<Render::HSkeleton> skeletonsToLoad =
                manager->skeletonManager()->takeDirtySkeletons(Render::SkeletonManager::SkeletonDataDirty);
        for (const auto &skeletonHandle : skeletonsToLoad) {
            auto loadSkeletonJob = Render::LoadSkeletonJobPtr::create(skeletonHandle);
            loadSkeletonJob->setNodeManagers(manager);
            d->m_syncLoadingJobs->addDependency(loadSkeletonJob);
            jobs.append(loadSkeletonJob);
        }

        // TO DO: Have 2 jobs queue
        // One for urgent jobs that are mandatory for the rendering of a frame
        // Another for jobs that can span across multiple frames (Scene/Mesh loading)
        const QVector<Render::LoadSceneJobPtr> sceneJobs = manager->sceneManager()->takePendingSceneLoaderJobs();
        for (const Render::LoadSceneJobPtr &job : sceneJobs) {
            job->setNodeManagers(d->m_nodeManagers);
            job->setSceneImporters(d->m_sceneImporter);
            jobs.append(job);
        }

        const QVector<QAspectJobPtr> geometryJobs = d->createGeometryRendererJobs();
        jobs.append(geometryJobs);

        const QVector<QAspectJobPtr> preRenderingJobs = d->createPreRendererJobs();
        jobs.append(preRenderingJobs);

        // Don't spawn any rendering jobs, if the renderer decides to skip this frame
        // Note: this only affects rendering jobs (jobs that load buffers,
        // perform picking,... must still be run)
        if (!d->m_renderer->shouldRender()) {
            d->m_renderer->skipNextFrame();
            QThread::msleep(1);
            return jobs;
        }

        // Traverse the current framegraph and create jobs to populate
        // RenderBins with RenderCommands
        // All jobs needed to create the frame and their dependencies are set by
        // renderBinJobs()

        const AbstractRenderer::BackendNodeDirtySet dirtyBitsForFrame = d->m_renderer->dirtyBits();

        // Create the jobs to build the frame
        const QVector<QAspectJobPtr> bufferJobs = d->createRenderBufferJobs();
        for (const QAspectJobPtr &bufferJob : bufferJobs)
            d->m_calculateBoundingVolumeJob->addDependency(bufferJob);
        jobs.append(bufferJobs);

        const bool entitiesEnabledDirty = dirtyBitsForFrame & AbstractRenderer::EntityEnabledDirty;
        if (entitiesEnabledDirty)
            jobs.push_back(d->m_updateTreeEnabledJob);

        if (entitiesEnabledDirty ||
            dirtyBitsForFrame & AbstractRenderer::TransformDirty) {
            jobs.push_back(d->m_worldTransformJob);
            jobs.push_back(d->m_updateWorldBoundingVolumeJob);
        }

        if (entitiesEnabledDirty ||
            dirtyBitsForFrame & AbstractRenderer::GeometryDirty ||
            dirtyBitsForFrame & AbstractRenderer::BuffersDirty) {
            jobs.push_back(d->m_calculateBoundingVolumeJob);
        }

        if (entitiesEnabledDirty ||
            dirtyBitsForFrame & AbstractRenderer::GeometryDirty ||
            dirtyBitsForFrame & AbstractRenderer::TransformDirty) {
            jobs.push_back(d->m_expandBoundingVolumeJob);
        }

        // TO DO: Conditionally add if skeletons dirty
        jobs.push_back(d->m_syncLoadingJobs);
        d->m_updateSkinningPaletteJob->setDirtyJoints(manager->jointManager()->dirtyJoints());
        jobs.push_back(d->m_updateSkinningPaletteJob);
        jobs.push_back(d->m_updateLevelOfDetailJob);

        // Rebuild Entity Layers list if layers are dirty
        const bool layersDirty = dirtyBitsForFrame & AbstractRenderer::LayersDirty;
        if (layersDirty)
            jobs.push_back(d->m_updateEntityLayersJob);

        const QVector<QAspectJobPtr> renderBinJobs = d->m_renderer->renderBinJobs();
        jobs.append(renderBinJobs);
    }

    return jobs;
}

QVariant QRenderAspect::executeCommand(const QStringList &args)
{
    Q_D(QRenderAspect);

    if (args.size() == 1) {
        Render::RenderSettings *settings = d->m_renderer->settings();
        auto *droot = static_cast<Qt3DCore::QEntityPrivate *>(Qt3DCore::QNodePrivate::get(d->m_root));
        auto *fg = qobject_cast<Qt3DRender::QFrameGraphNode *>(droot->m_scene->lookupNode(settings->activeFrameGraphID()));
        if (fg) {
            if (args.front() == QLatin1String("framegraph"))
                return Qt3DRender::QFrameGraphNodePrivate::get(fg)->dumpFrameGraph();
            if (args.front() == QLatin1String("framepaths"))
                return Qt3DRender::QFrameGraphNodePrivate::get(fg)->dumpFrameGraphPaths().join(QLatin1String("\n"));
            if (args.front() == QLatin1String("filterstates")) {
                const auto activeContextInfo = d->m_renderer->contextInfo();
                QString res = QLatin1String("Active Graphics API: ") + activeContextInfo->toString() + QLatin1String("\n");
                res += QLatin1String("Render Views:\n  ") + Qt3DRender::QFrameGraphNodePrivate::get(fg)->dumpFrameGraphFilterState().join(QLatin1String("\n  ")) + QLatin1String("\n");
                res += QLatin1String("Scene Graph:\n  ") + dumpSGFilterState(d->m_nodeManagers->techniqueManager(), activeContextInfo, d->m_root).join(QLatin1String("\n  "));
                return res;
            }
        }
        if (args.front() == QLatin1String("scenegraph"))
            return droot->dumpSceneGraph();
    }

    return d->m_renderer->executeCommand(args);
}

void QRenderAspect::onEngineStartup()
{
    Q_D(QRenderAspect);
    if (d->m_renderAfterJobs)   // synchronous rendering but using QWindow
        d->m_renderer->initialize();
    d->onEngineStartup();
}

void QRenderAspect::onRegistered()
{
    // Create a renderer each time as this is destroyed in onUnregistered below. If
    // using a threaded renderer, this blocks until the render thread has been created
    // and started.
    Q_D(QRenderAspect);
    d->createNodeManagers();

    // Load proper Renderer class based on Qt configuration preferences
    d->m_renderer = d->loadRendererPlugin();
    Q_ASSERT(d->m_renderer);
    d->m_renderer->setScreen(d->m_screen);
    d->m_renderer->setAspect(this);
    d->m_renderer->setNodeManagers(d->m_nodeManagers);

    // Create a helper for deferring creation of an offscreen surface used during cleanup
    // to the main thread, after we know what the surface format in use is.
    d->m_offscreenHelper = new Render::OffscreenSurfaceHelper(d->m_renderer);
    d->m_offscreenHelper->moveToThread(QCoreApplication::instance()->thread());
    d->m_renderer->setOffscreenSurfaceHelper(d->m_offscreenHelper);

    // Register backend types now that we have a renderer
    d->registerBackendTypes();

    if (!d->m_initialized) {
        // Register the VSyncFrameAdvanceService to drive the aspect manager loop
        // depending on the vsync
        if (d->m_aspectManager) {
            QAbstractFrameAdvanceService *advanceService = d->m_renderer->frameAdvanceService();
            if (advanceService)
                d->services()->registerServiceProvider(Qt3DCore::QServiceLocator::FrameAdvanceService,
                                                       advanceService);
        }

        if (d->services())
            d->m_renderer->setServices(d->services());
        d->m_initialized = true;
    }

    if (d->m_aspectManager)
        d->services()->eventFilterService()->registerEventFilter(d->m_pickEventFilter.data(), 1024);
}

void QRenderAspect::onUnregistered()
{
    Q_D(QRenderAspect);
    if (d->m_renderer) {
        // Request the renderer shuts down. In the threaded renderer case, the
        // Renderer destructor is the synchronization point where we wait for the
        // thread to join (see below).
        d->m_renderer->shutdown();
    }

    d->unregisterBackendTypes();

    d->m_renderer->releaseGraphicsResources();

    if (d->m_aspectManager)
        d->services()->eventFilterService()->unregisterEventFilter(d->m_pickEventFilter.data());

    delete d->m_nodeManagers;
    d->m_nodeManagers = nullptr;

    // Waits for the render thread to join (if using threaded renderer)
    delete d->m_renderer;
    d->m_renderer = nullptr;

    // Queue the offscreen surface helper for deletion on the main thread.
    // That will take care of deleting the offscreen surface itself.
    d->m_offscreenHelper->deleteLater();
    d->m_offscreenHelper = nullptr;
}

QVector<Qt3DCore::QAspectJobPtr> QRenderAspectPrivate::createGeometryRendererJobs() const
{
    Render::GeometryRendererManager *geomRendererManager = m_nodeManagers->geometryRendererManager();
    const QVector<QNodeId> dirtyGeometryRenderers = geomRendererManager->dirtyGeometryRenderers();
    QVector<QAspectJobPtr> dirtyGeometryRendererJobs;
    dirtyGeometryRendererJobs.reserve(dirtyGeometryRenderers.size());

    for (const QNodeId &geoRendererId : dirtyGeometryRenderers) {
        Render::HGeometryRenderer geometryRendererHandle = geomRendererManager->lookupHandle(geoRendererId);
        if (!geometryRendererHandle.isNull()) {
            auto job = Render::LoadGeometryJobPtr::create(geometryRendererHandle);
            job->setNodeManagers(m_nodeManagers);
            dirtyGeometryRendererJobs.push_back(job);
        }
    }

    return dirtyGeometryRendererJobs;
}

QVector<QAspectJobPtr> QRenderAspectPrivate::createPreRendererJobs() const
{
    if (!m_renderer)
        return {};

    const auto frameMouseEvents = m_pickEventFilter->pendingMouseEvents();
    const auto frameKeyEvents = m_pickEventFilter->pendingKeyEvents();
    m_renderer->setPendingEvents(frameMouseEvents, frameKeyEvents);

    auto jobs = m_renderer->preRenderingJobs();

    // Set values on picking jobs
    Render::RenderSettings *renderSetting = m_renderer->settings();
    if (renderSetting != nullptr) {
        m_pickBoundingVolumeJob->setRenderSettings(renderSetting);
        m_pickBoundingVolumeJob->setFrameGraphRoot(m_renderer->frameGraphRoot());
        m_pickBoundingVolumeJob->setMouseEvents(frameMouseEvents);
        m_pickBoundingVolumeJob->setKeyEvents(frameKeyEvents);

        m_rayCastingJob->setRenderSettings(renderSetting);
        m_rayCastingJob->setFrameGraphRoot(m_renderer->frameGraphRoot());
    }

    jobs.append(m_pickBoundingVolumeJob);
    jobs.append(m_rayCastingJob);

    return jobs;
}

// Returns a vector of jobs to be performed for dirty buffers
// 1 dirty buffer == 1 job, all job can be performed in parallel
QVector<Qt3DCore::QAspectJobPtr> QRenderAspectPrivate::createRenderBufferJobs() const
{
    const QVector<QNodeId> dirtyBuffers = m_nodeManagers->bufferManager()->takeDirtyBuffers();
    QVector<QAspectJobPtr> dirtyBuffersJobs;
    dirtyBuffersJobs.reserve(dirtyBuffers.size());

    for (const QNodeId &bufId : dirtyBuffers) {
        Render::HBuffer bufferHandle = m_nodeManagers->lookupHandle<Render::Buffer, Render::BufferManager, Render::HBuffer>(bufId);
        if (!bufferHandle.isNull()) {
            // Create new buffer job
            auto job = Render::LoadBufferJobPtr::create(bufferHandle);
            job->setNodeManager(m_nodeManagers);
            dirtyBuffersJobs.push_back(job);
        }
    }

    return dirtyBuffersJobs;
}

void QRenderAspectPrivate::loadSceneParsers()
{
    const QStringList keys = QSceneImportFactory::keys();
    for (const QString &key : keys) {
        QSceneImporter *sceneIOHandler = QSceneImportFactory::create(key, QStringList());
        if (sceneIOHandler != nullptr)
            m_sceneImporter.append(sceneIOHandler);
    }
}

Render::AbstractRenderer *QRenderAspectPrivate::loadRendererPlugin()
{
    // Note: for now we load the first renderer plugin that is successfully loaded
    // In the future we might want to offer the user a way to hint at which renderer
    // plugin would best be loaded

    const QByteArray envTarget = qgetenv("QT3D_RENDERER");
    const QString targetKey = !envTarget.isEmpty() ? QString::fromLatin1(envTarget) : QStringLiteral("opengl");
    const QStringList keys = Render::QRendererPluginFactory::keys();
    for (const QString &key : keys) {
        if (key != targetKey)
            continue;
        Render::AbstractRenderer *renderer = Render::QRendererPluginFactory::create(key, m_renderType);
        if (renderer)
            return renderer;
    }
    const QByteArray targetKeyName = targetKey.toLatin1();
    qFatal("Unable to find renderer plugin for %s", targetKeyName.constData());
    return nullptr;
}

void QRenderAspectPrivate::loadRenderPlugin(const QString &pluginName)
{
    Q_Q(QRenderAspect);
    const QStringList keys = Render::QRenderPluginFactory::keys();
    if (!keys.contains(pluginName))
        return;

    if (m_pluginConfig.contains(pluginName) && !m_loadedPlugins.contains(pluginName)) {
        Render::QRenderPlugin *plugin
                = Render::QRenderPluginFactory::create(pluginName, QStringList());
        if (plugin != nullptr) {
            m_loadedPlugins.append(pluginName);
            m_renderPlugins.append(plugin);
            plugin->registerBackendTypes(q, m_renderer);
        }
    }
}

QVector<QString> QRenderAspectPrivate::m_pluginConfig;
QMutex QRenderAspectPrivate::m_pluginLock;
QVector<QRenderAspectPrivate *> QRenderAspectPrivate::m_instances;

void QRenderAspectPrivate::configurePlugin(const QString &plugin)
{
    QMutexLocker lock(&m_pluginLock);
    if (!m_pluginConfig.contains(plugin)) {
        m_pluginConfig.append(plugin);

        for (QRenderAspectPrivate *instance : qAsConst(m_instances))
            instance->loadRenderPlugin(plugin);
    }
}

} // namespace Qt3DRender

QT_END_NAMESPACE

QT3D_REGISTER_NAMESPACED_ASPECT("render", QT_PREPEND_NAMESPACE(Qt3DRender), QRenderAspect)

#include "moc_qrenderaspect.cpp"
