// Copyright (C) 2014 Klaralvdalens Datakonsult AB (KDAB).
// Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

#include "renderview_p.h"
#include <Qt3DRender/qmaterial.h>
#include <Qt3DRender/qrenderaspect.h>
#include <Qt3DRender/qrendertarget.h>
#include <Qt3DRender/qabstractlight.h>
#include <Qt3DRender/private/sphere_p.h>

#include <Qt3DRender/private/cameraselectornode_p.h>
#include <Qt3DRender/private/framegraphnode_p.h>
#include <Qt3DRender/private/layerfilternode_p.h>
#include <Qt3DRender/private/qparameter_p.h>
#include <Qt3DRender/private/cameralens_p.h>
#include <Qt3DRender/private/effect_p.h>
#include <Qt3DRender/private/entity_p.h>
#include <Qt3DRender/private/nodemanagers_p.h>
#include <Qt3DRender/private/layer_p.h>
#include <Qt3DRender/private/renderlogging_p.h>
#include <Qt3DRender/private/renderpassfilternode_p.h>
#include <Qt3DRender/private/renderpass_p.h>
#include <Qt3DRender/private/geometryrenderer_p.h>
#include <Qt3DRender/private/techniquefilternode_p.h>
#include <Qt3DRender/private/viewportnode_p.h>
#include <Qt3DRender/private/buffermanager_p.h>
#include <Qt3DRender/private/geometryrenderermanager_p.h>
#include <Qt3DRender/private/rendercapture_p.h>
#include <Qt3DRender/private/buffercapture_p.h>
#include <Qt3DRender/private/stringtoint_p.h>
#include <Qt3DRender/private/renderlogging_p.h>
#include <Qt3DRender/private/renderstateset_p.h>
#include <Qt3DRender/private/renderviewjobutils_p.h>
#include <Qt3DRender/private/uniformblockbuilder_p.h>
#include <Qt3DRender/private/clearbuffers_p.h>
#include <Qt3DRender/private/rendertargetselectornode_p.h>
#include <Qt3DRender/private/sortpolicy_p.h>
#include <Qt3DRender/private/techniquefilternode_p.h>
#include <Qt3DRender/private/managers_p.h>
#include <Qt3DRender/private/shaderdata_p.h>
#include <Qt3DRender/private/statesetnode_p.h>
#include <Qt3DRender/private/dispatchcompute_p.h>
#include <Qt3DRender/private/rendersurfaceselector_p.h>
#include <Qt3DRender/private/rendercapture_p.h>
#include <Qt3DRender/private/buffercapture_p.h>
#include <Qt3DRender/private/techniquemanager_p.h>
#include <Qt3DRender/private/memorybarrier_p.h>
#include <Qt3DRender/private/blitframebuffer_p.h>
#include <Qt3DRender/private/waitfence_p.h>
#include <Qt3DRender/private/rendercapture_p.h>

#include <rendercommand_p.h>
#include <renderer_p.h>
#include <graphicscontext_p.h>
#include <submissioncontext_p.h>
#include <glresourcemanagers_p.h>
#include <Qt3DCore/qentity.h>
#include <QtGui/qsurface.h>
#include <algorithm>
#include <atomic>
#include <gllights_p.h>
#include <QDebug>
#if defined(QT3D_RENDER_VIEW_JOB_TIMINGS)
#include <QElapsedTimer>
#endif

QT_BEGIN_NAMESPACE

namespace Qt3DRender {
namespace Render {
namespace OpenGL {

namespace  {

// register our QNodeId's as a metatype during program loading
Q_DECL_UNUSED const int qNodeIdTypeId = qMetaTypeId<Qt3DCore::QNodeId>();

std::atomic_bool wasInitialized{};

} // anonymous namespace

RenderView::StandardUniformsNameToTypeHash RenderView::ms_standardUniformSetters;


RenderView::StandardUniformsNameToTypeHash RenderView::initializeStandardUniformSetters()
{
    RenderView::StandardUniformsNameToTypeHash setters;

    setters.insert(Shader::modelMatrixNameId, ModelMatrix);
    setters.insert(Shader::viewMatrixNameId, ViewMatrix);
    setters.insert(Shader::projectionMatrixNameId, ProjectionMatrix);
    setters.insert(Shader::modelViewMatrixNameId, ModelViewMatrix);
    setters.insert(Shader::viewProjectionMatrixNameId, ViewProjectionMatrix);
    setters.insert(Shader::modelViewProjectionNameId, ModelViewProjectionMatrix);
    setters.insert(Shader::mvpNameId, ModelViewProjectionMatrix);
    setters.insert(Shader::inverseModelMatrixNameId, InverseModelMatrix);
    setters.insert(Shader::inverseViewMatrixNameId, InverseViewMatrix);
    setters.insert(Shader::inverseProjectionMatrixNameId, InverseProjectionMatrix);
    setters.insert(Shader::inverseModelViewNameId, InverseModelViewMatrix);
    setters.insert(Shader::inverseViewProjectionMatrixNameId, InverseViewProjectionMatrix);
    setters.insert(Shader::inverseModelViewProjectionNameId, InverseModelViewProjectionMatrix);
    setters.insert(Shader::modelNormalMatrixNameId, ModelNormalMatrix);
    setters.insert(Shader::modelViewNormalNameId, ModelViewNormalMatrix);
    setters.insert(Shader::viewportMatrixNameId, ViewportMatrix);
    setters.insert(Shader::inverseViewportMatrixNameId, InverseViewportMatrix);
    setters.insert(Shader::aspectRatioNameId, AspectRatio);
    setters.insert(Shader::exposureNameId, Exposure);
    setters.insert(Shader::gammaNameId, Gamma);
    setters.insert(Shader::timeNameId, Time);
    setters.insert(Shader::eyePositionNameId, EyePosition);
    setters.insert(Shader::skinningPaletteNameId, SkinningPalette);
    setters.insert(Shader::yUpInFBOId, YUpInFBO);
    setters.insert(Shader::yUpInNDCId, YUpInNDC);

    return setters;
}

// TODO: Move this somewhere global where GraphicsContext::setViewport() can use it too
static QRectF resolveViewport(const QRectF &fractionalViewport, const QSize &surfaceSize)
{
    return QRectF(fractionalViewport.x() * surfaceSize.width(),
                  (1.0 - fractionalViewport.y() - fractionalViewport.height()) * surfaceSize.height(),
                  fractionalViewport.width() * surfaceSize.width(),
                  fractionalViewport.height() * surfaceSize.height());
}

static Matrix4x4 getProjectionMatrix(const CameraLens *lens)
{
    return lens ? lens->projection() : Matrix4x4();
}

UniformValue RenderView::standardUniformValue(RenderView::StandardUniform standardUniformType,
                                              const Entity *entity) const
{
    const Matrix4x4 &model = *(entity->worldTransform());

    switch (standardUniformType) {
    case ModelMatrix:
        return UniformValue(model);
    case ViewMatrix:
        return UniformValue(m_viewMatrix);
    case ProjectionMatrix:
        return UniformValue(getProjectionMatrix(m_renderCameraLens));
    case ModelViewMatrix:
        return UniformValue(m_viewMatrix * model);
    case ViewProjectionMatrix:
        return UniformValue(getProjectionMatrix(m_renderCameraLens) * m_viewMatrix);
    case ModelViewProjectionMatrix:
        return UniformValue(m_viewProjectionMatrix * model);
    case InverseModelMatrix:
        return UniformValue(model.inverted());
    case InverseViewMatrix:
        return UniformValue(m_viewMatrix.inverted());
    case InverseProjectionMatrix: {
        return UniformValue(getProjectionMatrix(m_renderCameraLens).inverted());
    }
    case InverseModelViewMatrix:
        return UniformValue((m_viewMatrix * model).inverted());
    case InverseViewProjectionMatrix: {
        const Matrix4x4 viewProjectionMatrix = getProjectionMatrix(m_renderCameraLens) * m_viewMatrix;
        return UniformValue(viewProjectionMatrix.inverted());
    }
    case InverseModelViewProjectionMatrix:
        return UniformValue((m_viewProjectionMatrix * model).inverted());
    case ModelNormalMatrix:
        return UniformValue(convertToQMatrix4x4(model).normalMatrix());
    case ModelViewNormalMatrix:
        return UniformValue(convertToQMatrix4x4(m_viewMatrix * model).normalMatrix());
    case ViewportMatrix: {
        QMatrix4x4 viewportMatrix;
        // TO DO: Implement on Matrix4x4
        viewportMatrix.viewport(resolveViewport(m_viewport, m_surfaceSize));
        return UniformValue(Matrix4x4(viewportMatrix));
    }
    case InverseViewportMatrix: {
        QMatrix4x4 viewportMatrix;
        // TO DO: Implement on Matrix4x4
        viewportMatrix.viewport(resolveViewport(m_viewport, m_surfaceSize));
        return UniformValue(Matrix4x4(viewportMatrix.inverted()));
    }
    case AspectRatio:
        return float(m_surfaceSize.width()) / std::max(1.f, float(m_surfaceSize.height()));
    case Exposure:
        return UniformValue(m_renderCameraLens ? m_renderCameraLens->exposure() : 0.0f);
    case Gamma:
        return UniformValue(m_gamma);
    case Time:
        return UniformValue(float(m_renderer->time() / 1000000000.0f));
    case EyePosition:
        return UniformValue(m_eyePos);
    case SkinningPalette: {
        const Armature *armature = entity->renderComponent<Armature>();
        if (!armature) {
            qCWarning(Jobs, "Requesting skinningPalette uniform but no armature set on entity");
            return UniformValue();
        }
        return armature->skinningPaletteUniform();
    }
    case YUpInNDC:
        return UniformValue(0.0f);
    case YUpInFBO:
        return UniformValue(0.0f);
    default:
        Q_UNREACHABLE_RETURN(UniformValue());
    }
}

/*!
    \internal
    Walks up the framegraph tree from \a fgLeaf and builds up as much state
    as possible and populates \a rv. For cases where we can't get the specific state
    (e.g. because it depends upon more than just the framegraph) we store the data from
    the framegraph that will be needed to later when the rest of the data becomes available
*/
void RenderView::setRenderViewConfigFromFrameGraphLeafNode(RenderView *rv, const FrameGraphNode *fgLeaf)
{
    // The specific RenderPass to be used is also dependent upon the Effect and TechniqueFilter
    // which is referenced by the Material which is referenced by the RenderMesh. So we can
    // only store the filter info in the RenderView structure and use it to do the resolving
    // when we build the RenderCommand list.
    const NodeManagers *manager = rv->nodeManagers();
    const FrameGraphNode *node = fgLeaf;

    while (node) {
        FrameGraphNode::FrameGraphNodeType type = node->nodeType();
        if (node->isEnabled())
            switch (type) {
            case FrameGraphNode::InvalidNodeType:
                // A base FrameGraphNode, can be used for grouping purposes
                break;
            case FrameGraphNode::CameraSelector:
                // Can be set only once and we take camera nearest to the leaf node
                if (!rv->renderCameraLens()) {
                    const CameraSelector *cameraSelector = static_cast<const CameraSelector *>(node);
                    Entity *camNode = manager->renderNodesManager()->lookupResource(cameraSelector->cameraUuid());
                    if (camNode) {
                        CameraLens *lens = camNode->renderComponent<CameraLens>();
                        rv->setRenderCameraEntity(camNode);
                        if (lens && lens->isEnabled()) {
                            rv->setRenderCameraLens(lens);
                            // ViewMatrix and ProjectionMatrix are computed
                            // later in updateMatrices()
                            // since at this point the transformation matrices
                            // may not yet have been updated
                        }
                    }
                }
                break;

            case FrameGraphNode::LayerFilter: // Can be set multiple times in the tree
                rv->appendLayerFilter(static_cast<const LayerFilterNode *>(node)->peerId());
                break;

            case FrameGraphNode::ProximityFilter: // Can be set multiple times in the tree
                rv->appendProximityFilterId(node->peerId());
                break;

            case FrameGraphNode::RenderPassFilter:
                // Can be set once
                // TODO: Amalgamate all render pass filters from leaf to root
                if (!rv->renderPassFilter())
                    rv->setRenderPassFilter(static_cast<const RenderPassFilter *>(node));
                break;

            case FrameGraphNode::RenderTarget: {
                // Can be set once and we take render target nearest to the leaf node
                const RenderTargetSelector *targetSelector = static_cast<const RenderTargetSelector *>(node);
                Qt3DCore::QNodeId renderTargetUid = targetSelector->renderTargetUuid();
                HTarget renderTargetHandle = manager->renderTargetManager()->lookupHandle(renderTargetUid);

                // Add renderTarget Handle and build renderCommand AttachmentPack
                if (!rv->renderTargetId()) {
                    rv->setRenderTargetId(renderTargetUid);

                    RenderTarget *renderTarget = manager->renderTargetManager()->data(renderTargetHandle);
                    if (renderTarget)
                        rv->setAttachmentPack(AttachmentPack(renderTarget, manager->attachmentManager()));
                }
                break;
            }

            case FrameGraphNode::ClearBuffers: {
                const ClearBuffers *cbNode = static_cast<const ClearBuffers *>(node);
                rv->addClearBuffers(cbNode);
                break;
            }

            case FrameGraphNode::TechniqueFilter:
                // Can be set once
                // TODO Amalgamate all technique filters from leaf to root
                if (!rv->techniqueFilter())
                    rv->setTechniqueFilter(static_cast<const TechniqueFilter *>(node));
                break;

            case FrameGraphNode::Viewport: {
                // If the Viewport has already been set in a lower node
                // Make it so that the new viewport is actually
                // a subregion relative to that of the parent viewport
                const ViewportNode *vpNode = static_cast<const ViewportNode *>(node);
                rv->setViewport(ViewportNode::computeViewport(rv->viewport(), vpNode));
                rv->setGamma(vpNode->gamma());
                break;
            }

            case FrameGraphNode::SortMethod: {
                const Render::SortPolicy *sortPolicy = static_cast<const Render::SortPolicy *>(node);
                rv->addSortType(sortPolicy->sortTypes());
                break;
            }

            case FrameGraphNode::SubtreeEnabler:
                // Has no meaning here. SubtreeEnabler was used
                // in a prior step to filter the list of RenderViewJobs
                break;

            case FrameGraphNode::StateSet: {
                const Render::StateSetNode *rStateSet = static_cast<const Render::StateSetNode *>(node);
                // Add states from new stateSet we might be missing
                // but don' t override existing states (lower StateSetNode always has priority)
                if (rStateSet->hasRenderStates()) {
                    // Create global RenderStateSet for renderView if no stateSet was set before
                    RenderStateSet *stateSet = rv->getOrCreateStateSet();
                    addStatesToRenderStateSet(stateSet, rStateSet->renderStates(), manager->renderStateManager());
                }
                break;
            }

            case FrameGraphNode::NoDraw: {
                rv->setNoDraw(true);
                break;
            }

            case FrameGraphNode::FrustumCulling: {
                rv->setFrustumCulling(true);
                break;
            }

            case FrameGraphNode::ComputeDispatch: {
                const Render::DispatchCompute *dispatchCompute = static_cast<const Render::DispatchCompute *>(node);
                rv->setCompute(true);
                rv->setComputeWorkgroups(dispatchCompute->x(),
                                         dispatchCompute->y(),
                                         dispatchCompute->z());
                break;
            }

            case FrameGraphNode::Lighting: {
                // TODO
                break;
            }

            case FrameGraphNode::Surface: {
                // Use the surface closest to leaf node
                if (rv->surface() == nullptr) {
                    const Render::RenderSurfaceSelector *surfaceSelector
                            = static_cast<const Render::RenderSurfaceSelector *>(node);
                    rv->setSurface(surfaceSelector->surface());
                    rv->setSurfaceSize(surfaceSelector->renderTargetSize() * surfaceSelector->devicePixelRatio());
                    rv->setDevicePixelRatio(surfaceSelector->devicePixelRatio());
                }
                break;
            }

            case FrameGraphNode::DebugOverlay:
                rv->setShowDebugOverlay(true);
                break;

            case FrameGraphNode::RenderCapture: {
                auto *renderCapture = const_cast<Render::RenderCapture *>(
                                            static_cast<const Render::RenderCapture *>(node));
                if (rv->renderCaptureNodeId().isNull() && renderCapture->wasCaptureRequested()) {
                    rv->setRenderCaptureNodeId(renderCapture->peerId());
                    rv->setRenderCaptureRequest(renderCapture->takeCaptureRequest());
                }
                break;
            }

            case FrameGraphNode::MemoryBarrier: {
                const Render::MemoryBarrier *barrier = static_cast<const Render::MemoryBarrier *>(node);
                rv->setMemoryBarrier(barrier->waitOperations()|rv->memoryBarrier());
                break;
            }

            case FrameGraphNode::BufferCapture: {
                auto *bufferCapture = const_cast<Render::BufferCapture *>(
                                            static_cast<const Render::BufferCapture *>(node));
                if (bufferCapture != nullptr)
                     rv->setIsDownloadBuffersEnable(bufferCapture->isEnabled());
                break;
            }

            case FrameGraphNode::BlitFramebuffer: {
                const Render::BlitFramebuffer *blitFramebufferNode =
                        static_cast<const Render::BlitFramebuffer *>(node);
                rv->setHasBlitFramebufferInfo(true);
                BlitFramebufferInfo bfbInfo;
                bfbInfo.sourceRenderTargetId = blitFramebufferNode->sourceRenderTargetId();
                bfbInfo.destinationRenderTargetId = blitFramebufferNode->destinationRenderTargetId();
                bfbInfo.sourceRect = blitFramebufferNode->sourceRect();
                bfbInfo.destinationRect = blitFramebufferNode->destinationRect();
                bfbInfo.sourceAttachmentPoint = blitFramebufferNode->sourceAttachmentPoint();
                bfbInfo.destinationAttachmentPoint = blitFramebufferNode->destinationAttachmentPoint();
                bfbInfo.interpolationMethod = blitFramebufferNode->interpolationMethod();
                rv->setBlitFrameBufferInfo(bfbInfo);
                break;
            }

            case FrameGraphNode::WaitFence: {
                const Render::WaitFence *waitFence = static_cast<const Render::WaitFence *>(node);
                rv->appendWaitFence(waitFence->data());
                break;
            }

            case FrameGraphNode::SetFence: {
                rv->appendInsertFenceId(node->peerId());
                break;
            }

            case FrameGraphNode::NoPicking:
                // Nothing to do RenderView wise for NoPicking
                break;

            default:
                // Should never get here
                qCWarning(Backend) << "Unhandled FrameGraphNode type";
            }

        node = node->parent();
    }
}


RenderView::RenderView()
{
    if (Q_UNLIKELY(!wasInitialized.exchange(true))) {
        // Needed as we can control the init order of static/global variables across compile units
        // and this hash relies on the static StringToInt class

        RenderView::ms_standardUniformSetters = RenderView::initializeStandardUniformSetters();
    }
}

RenderView::~RenderView()
{
}

namespace {

template<int SortType>
struct AdjacentSubRangeFinder
{
    static bool adjacentSubRange(const RenderCommand &, const RenderCommand &)
    {
        Q_UNREACHABLE_RETURN(false);
    }
};

template<>
struct AdjacentSubRangeFinder<QSortPolicy::StateChangeCost>
{
    static bool adjacentSubRange(const RenderCommand &a, const RenderCommand &b)
    {
        return a.m_changeCost == b.m_changeCost;
    }
};

template<>
struct AdjacentSubRangeFinder<QSortPolicy::BackToFront>
{
    static bool adjacentSubRange(const RenderCommand &a, const RenderCommand &b)
    {
        return qFuzzyCompare(a.m_depth, b.m_depth);
    }
};

template<>
struct AdjacentSubRangeFinder<QSortPolicy::Material>
{
    static bool adjacentSubRange(const RenderCommand &a, const RenderCommand &b)
    {
        return a.m_glShader == b.m_glShader;
    }
};

template<>
struct AdjacentSubRangeFinder<QSortPolicy::FrontToBack>
{
    static bool adjacentSubRange(const RenderCommand &a, const RenderCommand &b)
    {
        return qFuzzyCompare(a.m_depth, b.m_depth);
    }
};

template<>
struct AdjacentSubRangeFinder<QSortPolicy::Texture>
{
    static bool adjacentSubRange(const RenderCommand &a, const RenderCommand &b)
    {
        // Two renderCommands are adjacent if one contains all the other command's textures
        const std::vector<ShaderParameterPack::NamedResource> &texturesA = a.m_parameterPack.textures();
        const std::vector<ShaderParameterPack::NamedResource> &texturesB = b.m_parameterPack.textures();

        const bool bBigger = texturesB.size() > texturesA.size();
        const std::vector<ShaderParameterPack::NamedResource> &smallestVector = bBigger ? texturesA : texturesB;
        const std::vector<ShaderParameterPack::NamedResource> &biggestVector = bBigger ? texturesB : texturesA;

        const auto e = biggestVector.cend();
        for (const ShaderParameterPack::NamedResource &tex : smallestVector) {
            if (std::find(biggestVector.begin(), e, tex) == e)
                return false;
        }

        return true;
    }
};

template<typename Predicate>
int advanceUntilNonAdjacent(const EntityRenderCommandDataView *view,
                            const size_t beg, const size_t end, Predicate pred)
{
    const std::vector<size_t> &commandIndices = view->indices;
    const std::vector<RenderCommand> &commands = view->data.commands;
    size_t i = beg + 1;
    if (i < end) {
        const size_t startIdx = commandIndices[beg];
        while (i < end) {
            const size_t targetIdx = commandIndices[i];
            if (!pred(commands[startIdx], commands[targetIdx]))
                break;
            ++i;
        }
    }
    return int(i);
}


template<int SortType>
struct SubRangeSorter
{
    static void sortSubRange(EntityRenderCommandDataView *view, size_t begin, const size_t end)
    {
        Q_UNUSED(view);
        Q_UNUSED(begin);
        Q_UNUSED(end);
        Q_UNREACHABLE();
    }
};

template<>
struct SubRangeSorter<QSortPolicy::StateChangeCost>
{
    static void sortSubRange(EntityRenderCommandDataView *view, size_t begin, const size_t end)
    {
        std::vector<size_t> &commandIndices = view->indices;
        const std::vector<RenderCommand> &commands = view->data.commands;
        std::stable_sort(commandIndices.begin() + begin, commandIndices.begin() + end,
                         [&commands] (const size_t &iA, const size_t &iB) {
            const RenderCommand &a = commands[iA];
            const RenderCommand &b = commands[iB];
            return a.m_changeCost > b.m_changeCost;
        });
    }
};

template<>
struct SubRangeSorter<QSortPolicy::BackToFront>
{
    static void sortSubRange(EntityRenderCommandDataView *view, size_t begin, const size_t end)
    {
        std::vector<size_t> &commandIndices = view->indices;
        const std::vector<RenderCommand> &commands = view->data.commands;
        std::stable_sort(commandIndices.begin() + begin, commandIndices.begin() + end,
                         [&commands] (const size_t &iA, const size_t &iB) {
            const RenderCommand &a = commands[iA];
            const RenderCommand &b = commands[iB];
            return a.m_depth > b.m_depth;
        });
    }
};

template<>
struct SubRangeSorter<QSortPolicy::Material>
{
    static void sortSubRange(EntityRenderCommandDataView *view, size_t begin, const size_t end)
    {
        std::vector<size_t> &commandIndices = view->indices;
        const std::vector<RenderCommand> &commands = view->data.commands;
        std::stable_sort(commandIndices.begin() + begin, commandIndices.begin() + end,
                         [&commands] (const size_t &iA, const size_t &iB) {
            const RenderCommand &a = commands[iA];
            const RenderCommand &b = commands[iB];
            return a.m_glShader > b.m_glShader;
        });
    }
};

template<>
struct SubRangeSorter<QSortPolicy::FrontToBack>
{
    static void sortSubRange(EntityRenderCommandDataView *view, size_t begin, const size_t end)
    {
        std::vector<size_t> &commandIndices = view->indices;
        const std::vector<RenderCommand> &commands = view->data.commands;
        std::stable_sort(commandIndices.begin() + begin, commandIndices.begin() + end,
                         [&commands] (const size_t &iA, const size_t &iB) {
            const RenderCommand &a = commands[iA];
            const RenderCommand &b = commands[iB];
            return a.m_depth < b.m_depth;
        });
    }
};

template<>
struct SubRangeSorter<QSortPolicy::Texture>
{
    static void sortSubRange(EntityRenderCommandDataView *view, size_t begin, const size_t end)
    {
#ifndef Q_OS_WIN
        std::vector<size_t> &commandIndices = view->indices;
        const std::vector<RenderCommand> &commands = view->data.commands;
        std::stable_sort(commandIndices.begin() + begin, commandIndices.begin() + end,
                         [&commands] (const int &iA, const int &iB) {
            const RenderCommand &a = commands[iA];
            const RenderCommand &b = commands[iB];
            const std::vector<ShaderParameterPack::NamedResource> &texturesA = a.m_parameterPack.textures();
            const std::vector<ShaderParameterPack::NamedResource> &texturesB = b.m_parameterPack.textures();

            const bool bBigger = texturesB.size() > texturesA.size();
            const std::vector<ShaderParameterPack::NamedResource> &smallestVector = bBigger ? texturesA : texturesB;
            const std::vector<ShaderParameterPack::NamedResource> &biggestVector = bBigger ? texturesB : texturesA;

            size_t identicalTextureCount = 0;
            const auto e = biggestVector.cend();
            for (const ShaderParameterPack::NamedResource &tex : smallestVector) {
                if (std::find(biggestVector.begin(), e, tex) != e)
                    ++identicalTextureCount;
            }

            return identicalTextureCount < smallestVector.size();
        });
#else
        Q_UNUSED(view);
        Q_UNUSED(begin);
        Q_UNUSED(end);
#endif
    }
};

int findSubRange(const EntityRenderCommandDataView *view,
                 const int begin, const int end,
                 const QSortPolicy::SortType sortType)
{
    switch (sortType) {
    case QSortPolicy::StateChangeCost:
        return advanceUntilNonAdjacent(view, begin, end, AdjacentSubRangeFinder<QSortPolicy::StateChangeCost>::adjacentSubRange);
    case QSortPolicy::BackToFront:
        return advanceUntilNonAdjacent(view, begin, end, AdjacentSubRangeFinder<QSortPolicy::BackToFront>::adjacentSubRange);
    case QSortPolicy::Material:
        return advanceUntilNonAdjacent(view, begin, end, AdjacentSubRangeFinder<QSortPolicy::Material>::adjacentSubRange);
    case QSortPolicy::FrontToBack:
        return advanceUntilNonAdjacent(view, begin, end, AdjacentSubRangeFinder<QSortPolicy::FrontToBack>::adjacentSubRange);
    case QSortPolicy::Texture:
        return advanceUntilNonAdjacent(view, begin, end, AdjacentSubRangeFinder<QSortPolicy::Texture>::adjacentSubRange);
    case QSortPolicy::Uniform:
        return end;
    default:
        Q_UNREACHABLE_RETURN(end);
    }
}

void sortByMaterial(EntityRenderCommandDataView *view, int begin, const int end)
{
    // We try to arrange elements so that their rendering cost is minimized for a given shader
    std::vector<size_t> &commandIndices = view->indices;
    const std::vector<RenderCommand> &commands = view->data.commands;
    int rangeEnd = advanceUntilNonAdjacent(view, begin, end, AdjacentSubRangeFinder<QSortPolicy::Material>::adjacentSubRange);
    while (begin != end) {
        if (begin + 1 < rangeEnd) {
            std::stable_sort(commandIndices.begin() + begin + 1, commandIndices.begin() + rangeEnd,
                             [&commands] (size_t iA, size_t iB) {
                const RenderCommand &a = commands[iA];
                const RenderCommand &b = commands[iB];
                return a.m_material.handle() < b.m_material.handle();
            });
        }
        begin = rangeEnd;
        rangeEnd = advanceUntilNonAdjacent(view, begin, end, AdjacentSubRangeFinder<QSortPolicy::Material>::adjacentSubRange);
    }
}

void sortCommandRange(EntityRenderCommandDataView *view, int begin, int end, const int level,
                      const QList<Qt3DRender::QSortPolicy::SortType> &sortingTypes)
{
    if (level >= sortingTypes.size())
        return;

    switch (sortingTypes.at(level)) {
    case QSortPolicy::StateChangeCost:
        SubRangeSorter<QSortPolicy::StateChangeCost>::sortSubRange(view, begin, end);
        break;
    case QSortPolicy::BackToFront:
        SubRangeSorter<QSortPolicy::BackToFront>::sortSubRange(view, begin, end);
        break;
    case QSortPolicy::Material:
        // Groups all same shader DNA together
        SubRangeSorter<QSortPolicy::Material>::sortSubRange(view, begin, end);
        // Group all same material together (same parameters most likely)
        sortByMaterial(view, begin, end);
        break;
    case QSortPolicy::FrontToBack:
        SubRangeSorter<QSortPolicy::FrontToBack>::sortSubRange(view, begin, end);
        break;
    case QSortPolicy::Texture:
        SubRangeSorter<QSortPolicy::Texture>::sortSubRange(view, begin, end);
        break;
    case QSortPolicy::Uniform:
        break;
    default:
        Q_UNREACHABLE();
    }

    // For all sub ranges of adjacent item for sortType[i]
    // Perform filtering with sortType[i + 1]
    int rangeEnd = findSubRange(view, begin, end, sortingTypes.at(level));
    while (begin != end) {
        sortCommandRange(view, begin, rangeEnd, level + 1, sortingTypes);
        begin = rangeEnd;
        rangeEnd = findSubRange(view, begin, end, sortingTypes.at(level));
    }
}

} // anonymous

void RenderView::sort()
{
    assert(m_renderCommandDataView);
    // Compares the bitsetKey of the RenderCommands
    // Key[Depth | StateCost | Shader]
    sortCommandRange(m_renderCommandDataView.data(), 0, int(m_renderCommandDataView->size()), 0, m_sortingTypes);

    // For RenderCommand with the same shader
    // We compute the adjacent change cost

     // Only perform uniform minimization if we explicitly asked for it
     if (!m_sortingTypes.contains(QSortPolicy::Uniform))
        return;

    // Minimize uniform changes
    size_t i = 0;
    std::vector<RenderCommand> &commands = m_renderCommandDataView->data.commands;
    const std::vector<size_t> &indices = m_renderCommandDataView->indices;
    const size_t commandSize = indices.size();

    while (i < commandSize) {
        size_t j = i;

        // Advance while commands share the same shader
        while (i < commandSize &&
               commands[indices[j]].m_glShader == commands[indices[i]].m_glShader)
            ++i;

        if (i - j > 0) { // Several commands have the same shader, so we minimize uniform changes
            PackUniformHash cachedUniforms = commands[indices[j++]].m_parameterPack.uniforms();

            while (j < i) {
                // We need the reference here as we are modifying the original container
                // not the copy
                PackUniformHash &uniforms = commands[indices[j]].m_parameterPack.m_uniforms;

                for (size_t u = 0; u < uniforms.keys.size();) {
                    // We are comparing the values:
                    // - raw uniform values
                    // - the texture Node id if the uniform represents a texture
                    // since all textures are assigned texture units before the RenderCommands
                    // sharing the same material (shader) are rendered, we can't have the case
                    // where two uniforms, referencing the same texture eventually have 2 different
                    // texture unit values
                    const int uniformNameId = uniforms.keys.at(u);
                    const UniformValue &refValue = cachedUniforms.value(uniformNameId);
                    const UniformValue &newValue = uniforms.values.at(u);
                    if (newValue == refValue) {
                        uniforms.erase(int(u));
                    } else {
                        // Record updated value so that subsequent comparison
                        // for the next command will be made againts latest
                        // uniform value
                        cachedUniforms.insert(uniformNameId, newValue);
                        ++u;
                    }
                }
                ++j;
            }
        }
    }
}

void RenderView::setRenderer(Renderer *renderer)
{
    m_renderer = renderer;
    m_manager = renderer->nodeManagers();
}

RenderStateSet *RenderView::getOrCreateStateSet()
{
    if (!m_stateSet)
        m_stateSet.reset(new RenderStateSet());
    return m_stateSet.data();
}

void RenderView::addClearBuffers(const ClearBuffers *cb) {
    QClearBuffers::BufferTypeFlags type = cb->type();

    if (type & QClearBuffers::StencilBuffer) {
        m_clearStencilValue = cb->clearStencilValue();
        m_clearBuffer |= QClearBuffers::StencilBuffer;
    }
    if (type & QClearBuffers::DepthBuffer) {
        m_clearDepthValue = cb->clearDepthValue();
        m_clearBuffer |= QClearBuffers::DepthBuffer;
    }
    // keep track of global ClearColor (if set) and collect all DrawBuffer-specific
    // ClearColors
    if (type & QClearBuffers::ColorBuffer) {
        ClearBufferInfo clearBufferInfo;
        clearBufferInfo.clearColor = cb->clearColor();

        if (cb->clearsAllColorBuffers()) {
            m_globalClearColorBuffer = clearBufferInfo;
            m_clearBuffer |= QClearBuffers::ColorBuffer;
        } else {
            if (cb->bufferId()) {
                const RenderTargetOutput *targetOutput = m_manager->attachmentManager()->lookupResource(cb->bufferId());
                if (targetOutput) {
                    clearBufferInfo.attchmentPoint = targetOutput->point();
                    // Note: a job is later performed to find the drawIndex from the buffer attachment point
                    // using the AttachmentPack
                    m_specificClearColorBuffers.push_back(clearBufferInfo);
                }
            }
        }
    }
}

// If we are there, we know that entity had a GeometryRenderer + Material
EntityRenderCommandData RenderView::buildDrawRenderCommands(const Entity **entities,
                                                            int offset, int count) const
{
    GLShaderManager *glShaderManager = m_renderer->glResourceManagers()->glShaderManager();
    EntityRenderCommandData commands;

    commands.reserve(count);

    for (int i = 0; i < count; ++i) {
        const int idx = offset + i;
        const Entity *entity = entities[idx];
        GeometryRenderer *geometryRenderer = nullptr;
        HGeometryRenderer geometryRendererHandle = entity->componentHandle<GeometryRenderer>();

        // There is a geometry renderer with geometry
        if ((geometryRenderer = m_manager->geometryRendererManager()->data(geometryRendererHandle)) != nullptr
                && geometryRenderer->isEnabled()
                && !geometryRenderer->geometryId().isNull()) {

            const Qt3DCore::QNodeId materialComponentId = entity->componentUuid<Material>();
            const HMaterial materialHandle = entity->componentHandle<Material>();
            const  std::vector<RenderPassParameterData> &renderPassData = m_parameters.value(materialComponentId);

            HGeometry geometryHandle = m_manager->geometryManager()->lookupHandle(geometryRenderer->geometryId());
            Geometry *geometry = m_manager->geometryManager()->data(geometryHandle);

            // 1 RenderCommand per RenderPass pass on an Entity with a Mesh
            for (const RenderPassParameterData &passData : renderPassData) {
                // Add the RenderPass Parameters
                RenderCommand command = {};
                command.m_geometryRenderer = geometryRendererHandle;
                command.m_geometry = geometryHandle;

                command.m_material = materialHandle;
                // For RenderPass based states we use the globally set RenderState
                // if no renderstates are defined as part of the pass. That means:
                // RenderPass { renderStates: [] } will use the states defined by
                // StateSet in the FrameGraph
                RenderPass *pass = passData.pass;
                if (pass->hasRenderStates()) {
                    command.m_stateSet = RenderStateSetPtr::create();
                    addStatesToRenderStateSet(command.m_stateSet.data(), pass->renderStates(), m_manager->renderStateManager());
                    if (m_stateSet)
                        command.m_stateSet->merge(m_stateSet.data());
                    command.m_changeCost = m_renderer->defaultRenderState()->changeCost(command.m_stateSet.data());
                }
                command.m_shaderId = pass->shaderProgram();

                // We try to resolve the m_glShader here. If the shader exist,
                // it won't be null and will allow us to full process the
                // command over a single frame. Otherwise, the shader will be
                // loaded at the next submission time and the command will only
                // be fully valid on the next frame. Additionally, that way, if
                // a commands keeps being rebuilt, frame after frame, it will
                // still be visible on screen as long as the shader exists
                command.m_glShader = glShaderManager->lookupResource(command.m_shaderId);

                // It takes two frames to have a valid command as we can only
                // reference a glShader at frame n if it has been loaded at frame n - 1
                if (!command.m_glShader)
                    continue;

                { // Scoped to show extent

                    // Update the draw command with what's going to be needed for the drawing
                    int primitiveCount = geometryRenderer->vertexCount();
                    int estimatedCount = 0;
                    Attribute *indexAttribute = nullptr;
                    Attribute *indirectAttribute = nullptr;

                    const QList<Qt3DCore::QNodeId> attributeIds = geometry->attributes();
                    for (Qt3DCore::QNodeId attributeId : attributeIds) {
                        Attribute *attribute = m_manager->attributeManager()->lookupResource(attributeId);
                        switch (attribute->attributeType()) {
                        case Qt3DCore::QAttribute::IndexAttribute:
                            indexAttribute = attribute;
                            break;
                        case Qt3DCore::QAttribute::DrawIndirectAttribute:
                            indirectAttribute = attribute;
                            break;
                        case Qt3DCore::QAttribute::VertexAttribute:
                            estimatedCount = std::max(int(attribute->count()), estimatedCount);
                            break;
                        default:
                            Q_UNREACHABLE();
                            break;
                        }
                    }

                    command.m_drawIndexed = (indexAttribute != nullptr);
                    command.m_drawIndirect = (indirectAttribute != nullptr);

                    // Update the draw command with all the information required for the drawing
                    if (command.m_drawIndexed) {
                        command.m_indexAttributeDataType = GraphicsContext::glDataTypeFromAttributeDataType(indexAttribute->vertexBaseType());
                        command.m_indexAttributeByteOffset = indexAttribute->byteOffset() + geometryRenderer->indexBufferByteOffset();
                    }

                    // Note: we only care about the primitiveCount when using direct draw calls
                    // For indirect draw calls it is assumed the buffer was properly set already
                    if (command.m_drawIndirect) {
                        command.m_indirectAttributeByteOffset = indirectAttribute->byteOffset();
                        command.m_indirectDrawBuffer = m_manager->bufferManager()->lookupHandle(indirectAttribute->bufferId());
                    } else {
                        // Use the count specified by the GeometryRender
                        // If not specify use the indexAttribute count if present
                        // Otherwise tries to use the count from the attribute with the highest count
                        if (primitiveCount == 0) {
                            if (indexAttribute)
                                primitiveCount = indexAttribute->count();
                            else
                                primitiveCount = estimatedCount;
                        }
                    }

                    command.m_primitiveCount = primitiveCount;
                    command.m_primitiveType = geometryRenderer->primitiveType();
                    command.m_primitiveRestartEnabled = geometryRenderer->primitiveRestartEnabled();
                    command.m_restartIndexValue = geometryRenderer->restartIndexValue();
                    command.m_firstInstance = geometryRenderer->firstInstance();
                    command.m_instanceCount = geometryRenderer->instanceCount();
                    command.m_firstVertex = geometryRenderer->firstVertex();
                    command.m_indexOffset = geometryRenderer->indexOffset();
                    command.m_verticesPerPatch = geometryRenderer->verticesPerPatch();
                } // scope


                commands.push_back(entity,
                                   std::move(command),
                                   std::move(passData));
            }
        }
    }

    return commands;
}

EntityRenderCommandData RenderView::buildComputeRenderCommands(const Entity **entities,
                                                               int offset, int count) const
{
    // If the RenderView contains only a ComputeDispatch then it cares about
    // A ComputeDispatch is also implicitely a NoDraw operation
    // enabled flag
    // layer component
    // material/effect/technique/parameters/filters/
    EntityRenderCommandData commands;
    GLShaderManager *glShaderManager = m_renderer->glResourceManagers()->glShaderManager();

    commands.reserve(count);

    for (int i = 0; i < count; ++i) {
        const int idx = offset + i;
        const Entity *entity = entities[idx];
        ComputeCommand *computeJob = nullptr;
        HComputeCommand computeCommandHandle = entity->componentHandle<ComputeCommand>();
        if ((computeJob = nodeManagers()->computeJobManager()->data(computeCommandHandle)) != nullptr
                && computeJob->isEnabled()) {

            const Qt3DCore::QNodeId materialComponentId = entity->componentUuid<Material>();
            const  std::vector<RenderPassParameterData> &renderPassData = m_parameters.value(materialComponentId);

            // 1 RenderCommand per RenderPass pass on an Entity with a Mesh
            for (const RenderPassParameterData &passData : renderPassData) {
                // Add the RenderPass Parameters
                RenderCommand command = {};
                RenderPass *pass = passData.pass;

                if (pass->hasRenderStates()) {
                    command.m_stateSet = RenderStateSetPtr::create();
                    addStatesToRenderStateSet(command.m_stateSet.data(), pass->renderStates(), m_manager->renderStateManager());

                    // Merge per pass stateset with global stateset
                    // so that the local stateset only overrides
                    if (m_stateSet != nullptr)
                        command.m_stateSet->merge(m_stateSet.data());
                    command.m_changeCost = m_renderer->defaultRenderState()->changeCost(command.m_stateSet.data());
                }
                command.m_shaderId = pass->shaderProgram();
                command.m_glShader = glShaderManager->lookupResource(command.m_shaderId);

                // It takes two frames to have a valid command as we can only
                // reference a glShader at frame n if it has been loaded at frame n - 1
                if (!command.m_glShader)
                    continue;

                command.m_computeCommand = computeCommandHandle;
                command.m_type = RenderCommand::Compute;
                command.m_workGroups[0] = std::max(m_workGroups[0], computeJob->x());
                command.m_workGroups[1] = std::max(m_workGroups[1], computeJob->y());
                command.m_workGroups[2] = std::max(m_workGroups[2], computeJob->z());

                commands.push_back(entity,
                                   std::move(command),
                                   std::move(passData));
            }
        }
    }

    return commands;
}

void RenderView::updateRenderCommand(const EntityRenderCommandDataSubView &subView)
{
    subView.forEach([this] (const Entity *entity,
                            const RenderPassParameterData &passData,
                            RenderCommand &command) {
        if (command.m_type == RenderCommand::Draw) {
            // Project the camera-to-object-center vector onto the camera
            // view vector. This gives a depth value suitable as the key
            // for BackToFront sorting.
            command.m_depth = Vector3D::dotProduct(entity->worldBoundingVolume()->center() - m_eyePos, m_eyeViewDir);

            auto geometryRenderer = m_manager->geometryRendererManager()->data(command.m_geometryRenderer);
            if (geometryRenderer && !qFuzzyCompare(geometryRenderer->sortIndex(), -1.f))
                command.m_depth = geometryRenderer->sortIndex();
        } else { // Compute
            // Note: if frameCount has reached 0 in the previous frame, isEnabled
            // would be false
            ComputeCommand *computeJob = m_manager->computeJobManager()->data(command.m_computeCommand);
            if (computeJob->runType() == QComputeCommand::Manual)
                computeJob->updateFrameCount();
        }

        // setShaderAndUniforms can initialize a localData
        // make sure this is cleared before we leave this function
        setShaderAndUniforms(&command,
                             passData.parameterInfo,
                             entity);
    });
}

void RenderView::updateMatrices()
{
    if (m_renderCameraNode && m_renderCameraLens && m_renderCameraLens->isEnabled()) {
        auto transform = m_renderCameraNode->renderComponent<Transform>();
        if (m_renderCameraNode->isParentLessTransform() && transform && transform->hasViewMatrix()) {
            // optimization: if the entity is a QCamera and it doesn't have a parent with a transform component,
            // then we use the frontend version of the viewMatrix to avoid extra calculations that may introduce
            // rounding errors
            setViewMatrix(transform->viewMatrix());
        } else {
            const Matrix4x4 cameraWorld = *(m_renderCameraNode->worldTransform());
            setViewMatrix(m_renderCameraLens->viewMatrix(cameraWorld));
        }

        setViewProjectionMatrix(m_renderCameraLens->projection() * viewMatrix());
        //To get the eyePosition of the camera, we need to use the inverse of the
        //camera's worldTransform matrix.
        const Matrix4x4 inverseWorldTransform = viewMatrix().inverted();
        const Vector3D eyePosition(inverseWorldTransform.column(3));
        setEyePosition(eyePosition);

        // Get the viewing direction of the camera. Use the normal matrix to
        // ensure non-uniform scale works too.
        const QMatrix3x3 normalMat = convertToQMatrix4x4(m_viewMatrix).normalMatrix();
        // dir = normalize(QVector3D(0, 0, -1) * normalMat)
        setEyeViewDirection(Vector3D(-normalMat(2, 0), -normalMat(2, 1), -normalMat(2, 2)).normalized());
    }
}

void RenderView::setUniformValue(ShaderParameterPack &uniformPack, int nameId, const UniformValue &value) const
{
    // At this point a uniform value can only be a scalar type
    // or a Qt3DCore::QNodeId corresponding to a Texture or Image
    // ShaderData/Buffers would be handled as UBO/SSBO and would therefore
    // not be in the default uniform block
    if (value.valueType() == UniformValue::NodeId) {
        const Qt3DCore::QNodeId *nodeIds = value.constData<Qt3DCore::QNodeId>();

        const int uniformArraySize = value.byteSize() / sizeof(Qt3DCore::QNodeId);
        UniformValue::ValueType resourceType = UniformValue::TextureValue;

        for (int i = 0; i < uniformArraySize; ++i) {
            const Qt3DCore::QNodeId resourceId = nodeIds[i];

            const Texture *tex =  m_manager->textureManager()->lookupResource(resourceId);
            if (tex != nullptr) {
                uniformPack.setTexture(nameId, i, resourceId);
            } else {
                const ShaderImage *img = m_manager->shaderImageManager()->lookupResource(resourceId);
                if (img != nullptr) {
                    resourceType = UniformValue::ShaderImageValue;
                    uniformPack.setImage(nameId, i, resourceId);
                }
            }
        }

        // This uniform will be overridden in SubmissionContext::setParameters
        // and -1 values will be replaced by valid Texture or Image units
        UniformValue uniformValue(uniformArraySize * sizeof(int), resourceType);
        std::fill(uniformValue.data<int>(), uniformValue.data<int>() + uniformArraySize, -1);
        uniformPack.setUniform(nameId, uniformValue);
    } else {
        uniformPack.setUniform(nameId, value);
    }
}

void RenderView::setStandardUniformValue(ShaderParameterPack &uniformPack,
                                         int nameId,
                                         const Entity *entity) const
{
    uniformPack.setUniform(nameId, standardUniformValue(ms_standardUniformSetters[nameId], entity));
}

void RenderView::setUniformBlockValue(ShaderParameterPack &uniformPack,
                                      const ShaderUniformBlock &block,
                                      const UniformValue &value) const
{
    if (value.valueType() == UniformValue::NodeId) {

        Buffer *buffer = nullptr;
        if ((buffer = m_manager->bufferManager()->lookupResource(*value.constData<Qt3DCore::QNodeId>())) != nullptr) {
            BlockToUBO uniformBlockUBO;
            uniformBlockUBO.m_blockIndex = block.m_index;
            uniformBlockUBO.m_bufferID = buffer->peerId();
            uniformBlockUBO.m_needsUpdate = false;
            uniformPack.setUniformBuffer(std::move(uniformBlockUBO));
            // Buffer update to GL buffer will be done at render time
        }
    }
}

void RenderView::setShaderStorageValue(ShaderParameterPack &uniformPack,
                                       const ShaderStorageBlock &block,
                                       const UniformValue &value) const
{
    if (value.valueType() == UniformValue::NodeId) {
        Buffer *buffer = nullptr;
        if ((buffer = m_manager->bufferManager()->lookupResource(*value.constData<Qt3DCore::QNodeId>())) != nullptr) {
            BlockToSSBO shaderStorageBlock;
            shaderStorageBlock.m_blockIndex = block.m_index;
            shaderStorageBlock.m_bufferID = buffer->peerId();
            shaderStorageBlock.m_bindingIndex = block.m_binding;
            uniformPack.setShaderStorageBuffer(shaderStorageBlock);
            // Buffer update to GL buffer will be done at render time
        }
    }
}

void RenderView::setDefaultUniformBlockShaderDataValue(ShaderParameterPack &uniformPack,
                                                       const std::vector<int> &uniformsNamesIds,
                                                       ShaderData *shaderData,
                                                       const QString &structName) const
{
    UniformBlockValueBuilder builder(uniformsNamesIds,
                                     m_manager->shaderDataManager(),
                                     m_manager->textureManager(),
                                     m_viewMatrix);

    // Build name-value map for the block
    builder.buildActiveUniformNameValueMapStructHelper(shaderData, structName);
    // Set uniform values for each entrie of the block name-value map
    QHash<int, QVariant>::const_iterator activeValuesIt = builder.activeUniformNamesToValue.constBegin();
    const QHash<int, QVariant>::const_iterator activeValuesEnd = builder.activeUniformNamesToValue.constEnd();

    // TO DO: Make the ShaderData store UniformValue
    while (activeValuesIt != activeValuesEnd) {
        setUniformValue(uniformPack, activeValuesIt.key(), UniformValue::fromVariant(activeValuesIt.value()));
        ++activeValuesIt;
    }
}

void RenderView::applyParameter(const Parameter *param,
                                RenderCommand *command,
                                const GLShader *shader) const noexcept
{
    const int nameId = param->nameId();
    const UniformValue &uniformValue = param->uniformValue();
    const GLShader::ParameterKind kind = shader->categorizeVariable(nameId);

    switch (kind) {
    case GLShader::Uniform: {
        setUniformValue(command->m_parameterPack, nameId, uniformValue);
        break;
    }
    case GLShader::UBO: {
        setUniformBlockValue(command->m_parameterPack, shader->uniformBlockForBlockNameId(nameId), uniformValue);
        break;
    }
    case GLShader::SSBO: {
        setShaderStorageValue(command->m_parameterPack, shader->storageBlockForBlockNameId(nameId), uniformValue);
        break;
    }
    case GLShader::Struct: {
        ShaderData *shaderData = nullptr;
        if (uniformValue.valueType() == UniformValue::NodeId &&
                (shaderData = m_manager->shaderDataManager()->lookupResource(*uniformValue.constData<Qt3DCore::QNodeId>())) != nullptr) {
            // Try to check if we have a struct or array matching a QShaderData parameter
            setDefaultUniformBlockShaderDataValue(command->m_parameterPack, shader->uniformsNamesIds(), shaderData, StringToInt::lookupString(nameId));
        }
        break;
    }
    default:
        break;
    }
}


void RenderView::setShaderAndUniforms(RenderCommand *command,
                                      const ParameterInfoList &parameters,
                                      const Entity *entity) const
{
    // The VAO Handle is set directly in the renderer thread so as to avoid having to use a mutex here
    // Set shader, technique, and effect by basically doing :
    // ShaderProgramManager[MaterialManager[frontentEntity->id()]->Effect->Techniques[TechniqueFilter->name]->RenderPasses[RenderPassFilter->name]];
    // The Renderer knows that if one of those is null, a default material / technique / effect as to be used

    // Find all RenderPasses (in order) matching values set in the RenderPassFilter
    // Get list of parameters for the Material, Effect, and Technique
    // For each ParameterBinder in the RenderPass -> create a QUniformPack
    // Once that works, improve that to try and minimize QUniformPack updates

    GLShader *shader = command->m_glShader;
    if (shader == nullptr || !shader->isLoaded())
        return;

    // If we have already build the uniforms previously, we should
    // only update values of uniforms that have changed
    // If parameters add been added/removed, the command would have been rebuild
    // and the parameter pack would be empty
    const bool updateUniformsOnly = command->m_parameterPack.submissionUniformIndices().size() > 0;

    if (!updateUniformsOnly) {
        // Builds the QUniformPack, sets shader standard uniforms and store attributes name / glname bindings
        // If a parameter is defined and not found in the bindings it is assumed to be a binding of Uniform type with the glsl name
        // equals to the parameter name

        // Set fragData Name and index
        // Later on we might want to relink the shader if attachments have changed
        // But for now we set them once and for all
        if (!m_renderTarget.isNull() && !shader->isLoaded()) {
            QHash<QString, int> fragOutputs;
            const auto atts = m_attachmentPack.attachments();
            for (const Attachment &att : atts) {
                if (att.m_point <= QRenderTargetOutput::Color15)
                    fragOutputs.insert(att.m_name, att.m_point);
            }
            // Set frag outputs in the shaders if hash not empty
            if (!fragOutputs.isEmpty())
                shader->setFragOutputs(fragOutputs);
        }

        // Set default attributes
        command->m_activeAttributes = shader->attributeNamesIds();

        // At this point we know whether the command is a valid draw command or not
        // We still need to process the uniforms as the command could be a compute command
        command->m_isValid = !command->m_activeAttributes.empty();

        // Reserve amount of uniforms we are going to need
        command->m_parameterPack.reserve(shader->parameterPackSize());
    }

    const size_t previousUniformCount = command->m_parameterPack.uniforms().size();
    if (shader->hasActiveVariables()) {
        const std::vector<int> &standardUniformNamesIds = shader->standardUniformNameIds();

        // It only makes sense to update the standard uniforms if:
        // - Camera changed
        // - Entity transform changed
        // - Viewport/Surface changed

        for (const int uniformNameId : standardUniformNamesIds)
            setStandardUniformValue(command->m_parameterPack, uniformNameId, entity);

        ParameterInfoList::const_iterator it = parameters.cbegin();
        const ParameterInfoList::const_iterator parametersEnd = parameters.cend();

        while (it != parametersEnd) {
            const Parameter *param = m_manager->data<Parameter, ParameterManager>(it->handle);
            applyParameter(param, command, shader);
            ++it;
        }

        // Lights
        updateLightUniforms(command, entity);
    }

    const size_t actualUniformCount = command->m_parameterPack.uniforms().size();
    // Prepare the ShaderParameterPack based on the active uniforms of the shader
    if (!updateUniformsOnly || previousUniformCount != actualUniformCount)
        shader->prepareUniforms(command->m_parameterPack);
}

void RenderView::updateLightUniforms(RenderCommand *command, const Entity *entity) const
{
    GLShader *shader = command->m_glShader;
    const std::vector<int> &lightUniformNamesIds = shader->lightUniformsNamesIds();
    if (!lightUniformNamesIds.empty()) {
        // Pick which lights to take in to account.
        // For now decide based on the distance by taking the MAX_LIGHTS closest lights.
        // Replace with more sophisticated mechanisms later.
        // Copy vector so that we can sort it concurrently and we only want to sort the one for the current command
        std::vector<LightSource> lightSources = m_lightSources;

        if (lightSources.size() > 1) {
            const Vector3D entityCenter = entity->worldBoundingVolume()->center();
            std::sort(lightSources.begin(), lightSources.end(),
                      [&] (const LightSource &a, const LightSource &b) {
                const float distA = entityCenter.distanceToPoint(a.entity->worldBoundingVolume()->center());
                const float distB = entityCenter.distanceToPoint(b.entity->worldBoundingVolume()->center());
                return distA < distB;
            });
            m_lightSources = {lightSources.begin(), lightSources.begin() + std::min(lightSources.size(), size_t(MAX_LIGHTS)) };
        }

        int lightIdx = 0;
        for (const LightSource &lightSource : m_lightSources) {
            if (lightIdx == MAX_LIGHTS)
                break;
            const Entity *lightEntity = lightSource.entity;
            const Matrix4x4 lightWorldTransform = *(lightEntity->worldTransform());
            const Vector3D worldPos = lightWorldTransform.map(Vector3D(0.0f, 0.0f, 0.0f));
            for (Light *light : lightSource.lights) {
                if (!light->isEnabled())
                    continue;

                ShaderData *shaderData = m_manager->shaderDataManager()->lookupResource(light->shaderData());
                if (!shaderData)
                    continue;

                if (lightIdx == MAX_LIGHTS)
                    break;

                // Note: implicit conversion of values to UniformValue
                if (Qt3DCore::contains(lightUniformNamesIds, GLLights::LIGHT_TYPE_NAMES[lightIdx])) {
                    setUniformValue(command->m_parameterPack, GLLights::LIGHT_POSITION_NAMES[lightIdx], worldPos);
                    setUniformValue(command->m_parameterPack, GLLights::LIGHT_TYPE_NAMES[lightIdx], int(QAbstractLight::PointLight));
                    setUniformValue(command->m_parameterPack, GLLights::LIGHT_COLOR_NAMES[lightIdx], Vector3D(1.0f, 1.0f, 1.0f));
                    setUniformValue(command->m_parameterPack, GLLights::LIGHT_INTENSITY_NAMES[lightIdx], 0.5f);
                } else if (Qt3DCore::contains(lightUniformNamesIds, GLLights::LIGHT_TYPE_UNROLL_NAMES[lightIdx])) {
                    setUniformValue(command->m_parameterPack, GLLights::LIGHT_POSITION_UNROLL_NAMES[lightIdx], worldPos);
                    setUniformValue(command->m_parameterPack, GLLights::LIGHT_TYPE_UNROLL_NAMES[lightIdx], int(QAbstractLight::PointLight));
                    setUniformValue(command->m_parameterPack, GLLights::LIGHT_COLOR_UNROLL_NAMES[lightIdx], Vector3D(1.0f, 1.0f, 1.0f));
                    setUniformValue(command->m_parameterPack, GLLights::LIGHT_INTENSITY_UNROLL_NAMES[lightIdx], 0.5f);
                }

                // There is no risk in doing that even if multithreaded
                // since we are sure that a shaderData is unique for a given light
                // and won't ever be referenced as a Component either
                const Matrix4x4 *worldTransform = lightEntity->worldTransform();
                if (worldTransform)
                    shaderData->updateWorldTransform(*worldTransform);

                setDefaultUniformBlockShaderDataValue(command->m_parameterPack, shader->lightUniformsNamesIds(), shaderData, GLLights::LIGHT_STRUCT_NAMES[lightIdx]);
                setDefaultUniformBlockShaderDataValue(command->m_parameterPack, shader->lightUniformsNamesIds(), shaderData, GLLights::LIGHT_STRUCT_UNROLL_NAMES[lightIdx]);
                ++lightIdx;
            }
        }

        setUniformValue(command->m_parameterPack, GLLights::LIGHT_COUNT_NAME_ID, UniformValue(qMax((m_environmentLight ? 0 : 1), lightIdx)));

        // If no active light sources and no environment light, add a default light
        if (m_lightSources.empty() && !m_environmentLight) {
            // Note: implicit conversion of values to UniformValue
            if (Qt3DCore::contains(lightUniformNamesIds, GLLights::LIGHT_TYPE_NAMES[lightIdx])) {
                setUniformValue(command->m_parameterPack, GLLights::LIGHT_POSITION_NAMES[0], Vector3D(10.0f, 10.0f, 0.0f));
                setUniformValue(command->m_parameterPack, GLLights::LIGHT_TYPE_NAMES[0], int(QAbstractLight::PointLight));
                setUniformValue(command->m_parameterPack, GLLights::LIGHT_COLOR_NAMES[0], Vector3D(1.0f, 1.0f, 1.0f));
                setUniformValue(command->m_parameterPack, GLLights::LIGHT_INTENSITY_NAMES[0], 0.5f);
            } else if (Qt3DCore::contains(lightUniformNamesIds, GLLights::LIGHT_TYPE_UNROLL_NAMES[lightIdx])) {
                setUniformValue(command->m_parameterPack, GLLights::LIGHT_POSITION_UNROLL_NAMES[0], Vector3D(10.0f, 10.0f, 0.0f));
                setUniformValue(command->m_parameterPack, GLLights::LIGHT_TYPE_UNROLL_NAMES[0], int(QAbstractLight::PointLight));
                setUniformValue(command->m_parameterPack, GLLights::LIGHT_COLOR_UNROLL_NAMES[0], Vector3D(1.0f, 1.0f, 1.0f));
                setUniformValue(command->m_parameterPack, GLLights::LIGHT_INTENSITY_UNROLL_NAMES[0], 0.5f);
            }
        }
    }

    // Environment Light
    int envLightCount = 0;
    static const int irradianceStructId = StringToInt::lookupId(QLatin1String("envLight.irradiance"));
    static const int specularStructId = StringToInt::lookupId(QLatin1String("envLight.specular"));
    static const int irradianceId = StringToInt::lookupId(QLatin1String("envLightIrradiance"));
    static const int specularId = StringToInt::lookupId(QLatin1String("envLightSpecular"));
    if (m_environmentLight && m_environmentLight->isEnabled()) {
        ShaderData *shaderData = m_manager->shaderDataManager()->lookupResource(m_environmentLight->shaderData());
        if (shaderData) {
            // EnvLight isn't part of the light uniform name ids
            setDefaultUniformBlockShaderDataValue(command->m_parameterPack, shader->uniformsNamesIds(), shaderData, QStringLiteral("envLight"));
            auto irr =
                    shaderData->properties()["irradiance"].value.value<Qt3DCore::QNodeId>();
            auto spec =
                    shaderData->properties()["specular"].value.value<Qt3DCore::QNodeId>();
            setUniformValue(command->m_parameterPack, irradianceId, irr);
            setUniformValue(command->m_parameterPack, specularId, spec);
            envLightCount = 1;
        }
    } else {
        // with some drivers, samplers (like the envbox sampler) need to be bound even though
        // they may not be actually used, otherwise draw calls can fail
        setUniformValue(command->m_parameterPack, irradianceId, m_renderer->submissionContext()->maxTextureUnitsCount());
        setUniformValue(command->m_parameterPack, irradianceStructId, m_renderer->submissionContext()->maxTextureUnitsCount());
        setUniformValue(command->m_parameterPack, specularId, m_renderer->submissionContext()->maxTextureUnitsCount());
        setUniformValue(command->m_parameterPack, specularStructId, m_renderer->submissionContext()->maxTextureUnitsCount());
    }
    setUniformValue(command->m_parameterPack, StringToInt::lookupId(QStringLiteral("envLightCount")), envLightCount);
}

bool RenderView::hasBlitFramebufferInfo() const
{
    return m_hasBlitFramebufferInfo;
}

void RenderView::setHasBlitFramebufferInfo(bool hasBlitFramebufferInfo)
{
    m_hasBlitFramebufferInfo = hasBlitFramebufferInfo;
}

bool RenderView::shouldSkipSubmission() const
{
    if (commandCount() > 0)
        return false;

    if (m_hasBlitFramebufferInfo)
        return false;

    if (m_isDownloadBuffersEnable)
        return false;

    if (m_showDebugOverlay)
        return false;

    if (!m_waitFences.empty() || !m_insertFenceIds.empty())
        return false;

    if (m_clearBuffer != QClearBuffers::None)
        return false;

    if (!m_renderCaptureNodeId.isNull())
        return false;

    return true;
}

BlitFramebufferInfo RenderView::blitFrameBufferInfo() const
{
    return m_blitFrameBufferInfo;
}

void RenderView::setBlitFrameBufferInfo(const BlitFramebufferInfo &blitFrameBufferInfo)
{
    m_blitFrameBufferInfo = blitFrameBufferInfo;
}

bool RenderView::isDownloadBuffersEnable() const
{
    return m_isDownloadBuffersEnable;
}

void RenderView::setIsDownloadBuffersEnable(bool isDownloadBuffersEnable)
{
    m_isDownloadBuffersEnable = isDownloadBuffersEnable;
}

} // namespace OpenGL
} // namespace Render
} // namespace Qt3DRender

QT_END_NAMESPACE
