/****************************************************************************
**
** Copyright (C) 2008-2012 NVIDIA Corporation.
** Copyright (C) 2019 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Quick 3D.
**
** $QT_BEGIN_LICENSE:GPL$
** 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 General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 or (at your option) 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.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-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include <QtQuick3DRuntimeRender/private/qssgrendererimpl_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrenderlight_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrendercontextcore_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrendershadercache_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrenderdynamicobjectsystem_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrendershadercodegeneratorv2_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrenderdefaultmaterialshadergenerator_p.h>
#include <QtQuick3DRuntimeRender/private/qssgvertexpipelineimpl_p.h>

// This adds support for the depth buffers in the shader so we can do depth
// texture-based effects.
#define QSSG_RENDER_SUPPORT_DEPTH_TEXTURE 1

QT_BEGIN_NAMESPACE

#if 0
static inline void addVertexDepth(QSSGShaderVertexCodeGenerator &vertexShader)
{
    // near plane, far plane
    vertexShader.addInclude("viewProperties.glsllib");
    vertexShader.addVarying("vertex_depth", "float");
    // the w coordinate is the unormalized distance to the object from the camera
    // We want the normalized distance, with 0 representing the far plane and 1 representing
    // the near plane, of the object in the vertex depth variable.

    vertexShader << "    vertex_depth = calculateVertexDepth( cameraProperties, gl_Position );"
                 << "\n";
}
#endif

// Helper implements the vertex pipeline for mesh subsets when bound to the default material.
// Should be completely possible to use for custom materials with a bit of refactoring.
struct QSSGSubsetMaterialVertexPipeline : public QSSGVertexPipelineImpl
{
    QSSGRendererImpl &renderer;
    QSSGSubsetRenderable &renderable;
    TessellationModeValues tessMode;

    QSSGSubsetMaterialVertexPipeline(QSSGRendererImpl &inRenderer, QSSGSubsetRenderable &inRenderable, bool inWireframeRequested)
        : QSSGVertexPipelineImpl(inRenderer.contextInterface()->defaultMaterialShaderGenerator(),
                                   inRenderer.contextInterface()->shaderProgramGenerator(),
                                   false)
        , renderer(inRenderer)
        , renderable(inRenderable)
        , tessMode(TessellationModeValues::NoTessellation)
    {
        if (inRenderer.context()->supportsTessellation())
            tessMode = inRenderable.tessellationMode;

        if (inRenderer.context()->supportsGeometryStage() && tessMode != TessellationModeValues::NoTessellation)
            m_wireframe = inWireframeRequested;
    }

    void initializeTessControlShader()
    {
        if (tessMode == TessellationModeValues::NoTessellation
                || programGenerator()->getStage(QSSGShaderGeneratorStage::TessControl) == nullptr) {
            return;
        }

        QSSGShaderStageGeneratorInterface &tessCtrlShader(*programGenerator()->getStage(QSSGShaderGeneratorStage::TessControl));

        tessCtrlShader.addUniform("tessLevelInner", "float");
        tessCtrlShader.addUniform("tessLevelOuter", "float");

        setupTessIncludes(QSSGShaderGeneratorStage::TessControl, tessMode);

        tessCtrlShader.append("void main() {\n");

        tessCtrlShader.append("    ctWorldPos[0] = varWorldPos[0];");
        tessCtrlShader.append("    ctWorldPos[1] = varWorldPos[1];");
        tessCtrlShader.append("    ctWorldPos[2] = varWorldPos[2];");

        if (tessMode == TessellationModeValues::Phong || tessMode == TessellationModeValues::NPatch) {
            tessCtrlShader.append("    ctNorm[0] = varObjectNormal[0];");
            tessCtrlShader.append("    ctNorm[1] = varObjectNormal[1];");
            tessCtrlShader.append("    ctNorm[2] = varObjectNormal[2];");
        }
        if (tessMode == TessellationModeValues::NPatch) {
            tessCtrlShader.append("    ctTangent[0] = varTangent[0];");
            tessCtrlShader.append("    ctTangent[1] = varTangent[1];");
            tessCtrlShader.append("    ctTangent[2] = varTangent[2];");
        }

        tessCtrlShader.append("    gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;");
        tessCtrlShader.append("    tessShader( tessLevelOuter, tessLevelInner);\n");
    }
    void initializeTessEvaluationShader()
    {
        if (tessMode == TessellationModeValues::NoTessellation
                || programGenerator()->getStage(QSSGShaderGeneratorStage::TessEval) == nullptr) {
            return;
        }

        QSSGShaderStageGeneratorInterface &tessEvalShader(*programGenerator()->getStage(QSSGShaderGeneratorStage::TessEval));

        setupTessIncludes(QSSGShaderGeneratorStage::TessEval, tessMode);

        if (tessMode == TessellationModeValues::Linear)
            renderer.contextInterface()->defaultMaterialShaderGenerator()->addDisplacementImageUniforms(tessEvalShader,
                                                                                                          m_displacementIdx,
                                                                                                          m_displacementImage);

        tessEvalShader.addUniform("modelViewProjection", "mat4");
        tessEvalShader.addUniform("normalMatrix", "mat3");

        tessEvalShader.append("void main() {");

        if (tessMode == TessellationModeValues::NPatch) {
            tessEvalShader.append("    ctNorm[0] = varObjectNormalTC[0];");
            tessEvalShader.append("    ctNorm[1] = varObjectNormalTC[1];");
            tessEvalShader.append("    ctNorm[2] = varObjectNormalTC[2];");

            tessEvalShader.append("    ctTangent[0] = varTangentTC[0];");
            tessEvalShader.append("    ctTangent[1] = varTangentTC[1];");
            tessEvalShader.append("    ctTangent[2] = varTangentTC[2];");
        }

        tessEvalShader.append("    vec4 pos = tessShader( );\n");
    }

    void finalizeTessControlShader()
    {
        QSSGShaderStageGeneratorInterface &tessCtrlShader(*programGenerator()->getStage(QSSGShaderGeneratorStage::TessControl));
        // add varyings we must pass through
        typedef TStrTableStrMap::const_iterator TParamIter;
        for (TParamIter iter = m_interpolationParameters.begin(), end = m_interpolationParameters.end(); iter != end; ++iter) {
            tessCtrlShader << "    " << iter.key() << "TC[gl_InvocationID] = " << iter.key() << "[gl_InvocationID];\n";
        }
    }

    void finalizeTessEvaluationShader()
    {
        QSSGShaderStageGeneratorInterface &tessEvalShader(*programGenerator()->getStage(QSSGShaderGeneratorStage::TessEval));

        QByteArray outExt;
        if (programGenerator()->getEnabledStages() & QSSGShaderGeneratorStage::Geometry)
            outExt = "TE";

        // add varyings we must pass through
        typedef TStrTableStrMap::const_iterator TParamIter;
        if (tessMode == TessellationModeValues::NPatch) {
            for (TParamIter iter = m_interpolationParameters.begin(), end = m_interpolationParameters.end(); iter != end; ++iter) {
                tessEvalShader << "    " << iter.key() << outExt << " = gl_TessCoord.z * " << iter.key() << "TC[0] + ";
                tessEvalShader << "gl_TessCoord.x * " << iter.key() << "TC[1] + ";
                tessEvalShader << "gl_TessCoord.y * " << iter.key() << "TC[2];\n";
            }

            // transform the normal
            if (m_generationFlags & GenerationFlag::WorldNormal)
                tessEvalShader << "\n    varNormal" << outExt << " = normalize(normalMatrix * teNorm);\n";
            // transform the tangent
            if (m_generationFlags & GenerationFlag::TangentBinormal) {
                tessEvalShader << "\n    varTangent" << outExt << " = normalize(normalMatrix * teTangent);\n";
                // transform the binormal
                tessEvalShader << "\n    varBinormal" << outExt << " = normalize(normalMatrix * teBinormal);\n";
            }
        } else {
            for (TParamIter iter = m_interpolationParameters.begin(), end = m_interpolationParameters.end(); iter != end; ++iter) {
                tessEvalShader << "    " << iter.key() << outExt << " = gl_TessCoord.x * " << iter.key() << "TC[0] + ";
                tessEvalShader << "gl_TessCoord.y * " << iter.key() << "TC[1] + ";
                tessEvalShader << "gl_TessCoord.z * " << iter.key() << "TC[2];\n";
            }

            // displacement mapping makes only sense with linear tessellation
            if (tessMode == TessellationModeValues::Linear && m_displacementImage) {
                QSSGDefaultMaterialShaderGeneratorInterface::ImageVariableNames
                        theNames = renderer.contextInterface()->defaultMaterialShaderGenerator()->getImageVariableNames(m_displacementIdx);
                tessEvalShader << "    pos.xyz = defaultMaterialFileDisplacementTexture( " << theNames.m_imageSampler
                               << ", displaceAmount, " << theNames.m_imageFragCoords << outExt;
                tessEvalShader << ", varObjectNormal" << outExt << ", pos.xyz );"
                               << "\n";
                tessEvalShader << "    varWorldPos" << outExt << "= (modelMatrix * pos).xyz;"
                               << "\n";
                tessEvalShader << "    varViewVector" << outExt << "= normalize(cameraPosition - "
                               << "varWorldPos" << outExt << ");"
                               << "\n";
            }

            // transform the normal
            tessEvalShader << "\n    varNormal" << outExt << " = normalize(normalMatrix * varObjectNormal" << outExt << ");\n";
        }

        tessEvalShader.append("    gl_Position = modelViewProjection * pos;\n");
    }

    void beginVertexGeneration(quint32 displacementImageIdx, QSSGRenderableImage *displacementImage) override
    {
        m_displacementIdx = displacementImageIdx;
        m_displacementImage = displacementImage;

        QSSGShaderGeneratorStageFlags theStages(QSSGShaderProgramGeneratorInterface::defaultFlags());
        if (tessMode != TessellationModeValues::NoTessellation) {
            theStages |= QSSGShaderGeneratorStage::TessControl;
            theStages |= QSSGShaderGeneratorStage::TessEval;
        }
        if (m_wireframe) {
            theStages |= QSSGShaderGeneratorStage::Geometry;
        }
        programGenerator()->beginProgram(theStages);
        if (tessMode != TessellationModeValues::NoTessellation) {
            initializeTessControlShader();
            initializeTessEvaluationShader();
        }
        if (m_wireframe) {
            initializeWireframeGeometryShader();
        }
        // Open up each stage.
        QSSGShaderStageGeneratorInterface &vertexShader(vertex());
        vertexShader.addIncoming("attr_pos", "vec3");
        vertexShader << "void main()"
                     << "\n"
                     << "{"
                     << "\n";
        vertexShader << "    vec3 uTransform;"
                     << "\n";
        vertexShader << "    vec3 vTransform;"
                     << "\n";

        if (displacementImage) {
            generateUVCoords();
            materialGenerator()->generateImageUVCoordinates(*this, displacementImageIdx, 0, *displacementImage);
            if (!hasTessellation()) {
                vertexShader.addUniform("displaceAmount", "float");
                // we create the world position setup here
                // because it will be replaced with the displaced position
                setCode(GenerationFlag::WorldPosition);
                vertexShader.addUniform("modelMatrix", "mat4");

                vertexShader.addInclude("defaultMaterialFileDisplacementTexture.glsllib");
                QSSGDefaultMaterialShaderGeneratorInterface::ImageVariableNames theVarNames = materialGenerator()->getImageVariableNames(
                        displacementImageIdx);

                vertexShader.addUniform(theVarNames.m_imageSampler, "sampler2D");

                vertexShader << "    vec3 displacedPos = defaultMaterialFileDisplacementTexture( " << theVarNames.m_imageSampler
                             << ", displaceAmount, " << theVarNames.m_imageFragCoords << ", attr_norm, attr_pos );"
                             << "\n";
                addInterpolationParameter("varWorldPos", "vec3");
                vertexShader.append("    vec3 local_model_world_position = (modelMatrix * "
                                    "vec4(displacedPos, 1.0)).xyz;");
                assignOutput("varWorldPos", "local_model_world_position");
            }
        }
        // for tessellation we pass on the position in object coordinates
        // Also note that gl_Position is written in the tess eval shader
        if (hasTessellation())
            vertexShader.append("    gl_Position = vec4(attr_pos, 1.0);");
        else {
            vertexShader.addUniform("modelViewProjection", "mat4");
            if (displacementImage)
                vertexShader.append("    gl_Position = modelViewProjection * vec4(displacedPos, 1.0);");
            else
                vertexShader.append("    gl_Position = modelViewProjection * vec4(attr_pos, 1.0);");
        }

        if (hasTessellation()) {
            generateWorldPosition();
            generateWorldNormal();
            generateObjectNormal();
            generateVarTangentAndBinormal();
        }
    }

    void beginFragmentGeneration() override
    {
        fragment().addUniform("material_properties", "vec4");
        fragment() << "void main()"
                   << "\n"
                   << "{"
                   << "\n";
        // We do not pass object opacity through the pipeline.
        fragment() << "    float objectOpacity = material_properties.a;"
                   << "\n";
    }

    void assignOutput(const QByteArray &inVarName, const QByteArray &inVarValue) override
    {
        vertex() << "    " << inVarName << " = " << inVarValue << ";\n";
    }
    void doGenerateUVCoords(quint32 inUVSet = 0) override
    {
        Q_ASSERT(inUVSet == 0 || inUVSet == 1);

        if (inUVSet == 0) {
            vertex().addIncoming("attr_uv0", "vec2");
            vertex() << "    varTexCoord0 = attr_uv0;"
                     << "\n";
        } else if (inUVSet == 1) {
            vertex().addIncoming("attr_uv1", "vec2");
            vertex() << "    varTexCoord1 = attr_uv1;"
                     << "\n";
        }
    }

    // fragment shader expects varying vertex normal
    // lighting in vertex pipeline expects world_normal
    void doGenerateWorldNormal() override
    {
        QSSGShaderStageGeneratorInterface &vertexGenerator(vertex());
        vertexGenerator.addIncoming("attr_norm", "vec3");
        vertexGenerator.addUniform("normalMatrix", "mat3");
        if (hasTessellation() == false) {
            vertexGenerator.append("    vec3 world_normal = normalize(normalMatrix * attr_norm).xyz;");
            vertexGenerator.append("    varNormal = world_normal;");
        }
    }
    void doGenerateObjectNormal() override
    {
        addInterpolationParameter("varObjectNormal", "vec3");
        vertex().append("    varObjectNormal = attr_norm;");
    }
    void doGenerateWorldPosition() override
    {
        vertex().append("    vec3 local_model_world_position = (modelMatrix * vec4(attr_pos, 1.0)).xyz;");
        assignOutput("varWorldPos", "local_model_world_position");
    }

    void doGenerateVarTangentAndBinormal() override
    {
        vertex().addIncoming("attr_textan", "vec3");
        vertex().addIncoming("attr_binormal", "vec3");

        bool hasNPatchTessellation = tessMode == TessellationModeValues::NPatch;

        if (!hasNPatchTessellation) {
            vertex() << "    varTangent = normalMatrix * attr_textan;"
                     << "\n"
                     << "    varBinormal = normalMatrix * attr_binormal;"
                     << "\n";
        } else {
            vertex() << "    varTangent = attr_textan;"
                     << "\n"
                     << "    varBinormal = attr_binormal;"
                     << "\n";
        }
    }

    void doGenerateVertexColor() override
    {
        vertex().addIncoming("attr_color", "vec3");
        vertex().append("    varColor = attr_color;");
    }

    void endVertexGeneration(bool customShader) override
    {

        if (hasTessellation()) {
            // finalize tess control shader
            finalizeTessControlShader();
            // finalize tess evaluation shader
            finalizeTessEvaluationShader();

            tessControl().append("}");
            tessEval().append("}");
        }
        if (m_wireframe) {
            // finalize geometry shader
            finalizeWireframeGeometryShader();
            geometry().append("}");
        }
        if (!customShader)
            vertex().append("}");
    }

    void endFragmentGeneration(bool customShader) override
    {
        if (!customShader)
            fragment().append("}");
    }

    void addInterpolationParameter(const QByteArray &inName, const QByteArray &inType) override
    {
        m_interpolationParameters.insert(inName, inType);
        vertex().addOutgoing(inName, inType);
        fragment().addIncoming(inName, inType);
        if (hasTessellation()) {
            QByteArray nameBuilder(inName);
            nameBuilder.append("TC");
            tessControl().addOutgoing(nameBuilder, inType);

            nameBuilder = inName;
            if (programGenerator()->getEnabledStages() & QSSGShaderGeneratorStage::Geometry) {
                nameBuilder.append("TE");
                geometry().addOutgoing(inName, inType);
            }
            tessEval().addOutgoing(nameBuilder, inType);
        }
    }

    QSSGShaderStageGeneratorInterface &activeStage() override { return vertex(); }
};

static QByteArray logPrefix() { return QByteArrayLiteral("mesh subset pipeline-- "); }

QSSGRef<QSSGRenderShaderProgram> QSSGRendererImpl::generateShader(QSSGSubsetRenderable &inRenderable,
                                                                  const ShaderFeatureSetList &inFeatureSet)
{
    // build a string that allows us to print out the shader we are generating to the log.
    // This is time consuming but I feel like it doesn't happen all that often and is very
    // useful to users
    // looking at the log file.
    m_generatedShaderString = logPrefix();

    QSSGShaderDefaultMaterialKey theKey(inRenderable.shaderDescription);
    theKey.toString(m_generatedShaderString, m_defaultMaterialShaderKeyProperties);
    QSSGRef<QSSGShaderCache> theCache = m_contextInterface->shaderCache();
    const QSSGRef<QSSGRenderShaderProgram> &cachedProgram = theCache->getProgram(m_generatedShaderString, inFeatureSet);
    if (cachedProgram)
        return cachedProgram;

    QSSGSubsetMaterialVertexPipeline pipeline(*this,
                                                inRenderable,
                                                m_defaultMaterialShaderKeyProperties.m_wireframeMode.getValue(theKey));
    return m_contextInterface->defaultMaterialShaderGenerator()->generateShader(inRenderable.material,
                                                                               inRenderable.shaderDescription,
                                                                               pipeline,
                                                                               inFeatureSet,
                                                                               m_currentLayer->globalLights,
                                                                               inRenderable.firstImage,
                                                                               inRenderable.renderableFlags.hasTransparency(),
                                                                               logPrefix());
}

// --------------  Special cases for shadows  -------------------

QSSGRef<QSSGRenderableDepthPrepassShader> QSSGRendererImpl::getParaboloidDepthShader(TessellationModeValues inTessMode)
{
    if (m_contextInterface->renderContext()->supportsTessellation()
            && inTessMode != TessellationModeValues::NoTessellation) {
        if (inTessMode == TessellationModeValues::Linear)
            return getParaboloidDepthTessLinearShader();
        if (inTessMode == TessellationModeValues::Phong)
            return getParaboloidDepthTessPhongShader();
        if (inTessMode == TessellationModeValues::NPatch)
            return getParaboloidDepthTessNPatchShader();
    }

    return getParaboloidDepthNoTessShader();
}

QSSGRef<QSSGRenderableDepthPrepassShader> QSSGRendererImpl::getParaboloidDepthNoTessShader()
{
    QSSGRef<QSSGRenderableDepthPrepassShader> &theDepthShader = m_paraboloidDepthShader;

    if (theDepthShader.isNull()) {
        QByteArray name = "paraboloid depth shader";

        QSSGRef<QSSGShaderCache> theCache = m_contextInterface->shaderCache();
        QSSGRef<QSSGRenderShaderProgram> depthShaderProgram = theCache->getProgram(name, ShaderFeatureSetList());
        if (!depthShaderProgram) {
            getProgramGenerator()->beginProgram();
            QSSGShaderStageGeneratorInterface &vertexShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Vertex));
            QSSGShaderStageGeneratorInterface &fragmentShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Fragment));
            QSSGShaderProgramGeneratorInterface::outputParaboloidDepthVertex(vertexShader);
            QSSGShaderProgramGeneratorInterface::outputParaboloidDepthFragment(fragmentShader);
            depthShaderProgram = getProgramGenerator()->compileGeneratedShader(name, QSSGShaderCacheProgramFlags(), ShaderFeatureSetList());
        }

        if (depthShaderProgram) {
            theDepthShader = QSSGRef<QSSGRenderableDepthPrepassShader>(
                    new QSSGRenderableDepthPrepassShader(depthShaderProgram, context()));
        } else {
            theDepthShader = QSSGRef<QSSGRenderableDepthPrepassShader>();
        }
    }

    return theDepthShader;
}

QSSGRef<QSSGRenderableDepthPrepassShader> QSSGRendererImpl::getParaboloidDepthTessLinearShader()
{
    QSSGRef<QSSGRenderableDepthPrepassShader> &theDepthShader = m_paraboloidDepthTessLinearShader;

    if (theDepthShader.isNull()) {
        QByteArray name = "paraboloid depth tess linear shader";

        QSSGRef<QSSGShaderCache> theCache = m_contextInterface->shaderCache();
        QSSGRef<QSSGRenderShaderProgram> depthShaderProgram = theCache->getProgram(name, ShaderFeatureSetList());
        if (!depthShaderProgram) {
            getProgramGenerator()->beginProgram(
                    QSSGShaderGeneratorStageFlags(QSSGShaderGeneratorStage::Vertex | QSSGShaderGeneratorStage::TessControl
                                               | QSSGShaderGeneratorStage::TessEval | QSSGShaderGeneratorStage::Fragment));
            QSSGShaderStageGeneratorInterface &vertexShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Vertex));
            QSSGShaderStageGeneratorInterface &tessCtrlShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::TessControl));
            QSSGShaderStageGeneratorInterface &tessEvalShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::TessEval));
            QSSGShaderStageGeneratorInterface &fragmentShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Fragment));

            vertexShader.addIncoming("attr_pos", "vec3");
            // vertexShader.AddOutgoing("world_pos", "vec4");
            vertexShader.addUniform("modelViewProjection", "mat4");

            vertexShader.append("void main() {");
            vertexShader.append("    gl_Position = vec4(attr_pos, 1.0);");
            // vertexShader.Append("    world_pos = attr_pos;");
            vertexShader.append("}");

            tessCtrlShader.addInclude("tessellationLinear.glsllib");
            tessCtrlShader.addUniform("tessLevelInner", "float");
            tessCtrlShader.addUniform("tessLevelOuter", "float");
            // tessCtrlShader.AddOutgoing( "outUVTC", "vec2" );
            // tessCtrlShader.AddOutgoing( "outNormalTC", "vec3" );
            tessCtrlShader.append("void main() {\n");
            // tessCtrlShader.Append("    ctWorldPos[0] = outWorldPos[0];");
            // tessCtrlShader.Append("    ctWorldPos[1] = outWorldPos[1];");
            // tessCtrlShader.Append("    ctWorldPos[2] = outWorldPos[2];");
            tessCtrlShader.append("    gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;");
            tessCtrlShader.append("    tessShader( tessLevelOuter, tessLevelInner);\n");
            tessCtrlShader.append("}");

            tessEvalShader.addInclude("tessellationLinear.glsllib");
            tessEvalShader.addUniform("modelViewProjection", "mat4");
            tessEvalShader.addOutgoing("world_pos", "vec4");
            tessEvalShader.append("void main() {");
            tessEvalShader.append("    vec4 pos = tessShader( );\n");
            QSSGShaderProgramGeneratorInterface::outputParaboloidDepthTessEval(tessEvalShader);
            tessEvalShader.append("}");

            QSSGShaderProgramGeneratorInterface::outputParaboloidDepthFragment(fragmentShader);
            depthShaderProgram = getProgramGenerator()->compileGeneratedShader(name, QSSGShaderCacheProgramFlags(), ShaderFeatureSetList());
        }

        if (depthShaderProgram) {
            theDepthShader = QSSGRef<QSSGRenderableDepthPrepassShader>(
                    new QSSGRenderableDepthPrepassShader(depthShaderProgram, context()));
        } else {
            theDepthShader = QSSGRef<QSSGRenderableDepthPrepassShader>();
        }
    }

    return theDepthShader;
}

QSSGRef<QSSGRenderableDepthPrepassShader> QSSGRendererImpl::getParaboloidDepthTessPhongShader()
{
    QSSGRef<QSSGRenderableDepthPrepassShader> &theDepthShader = m_paraboloidDepthTessPhongShader;

    if (theDepthShader.isNull()) {
        QByteArray name = "paraboloid depth tess phong shader";

        QSSGRef<QSSGShaderCache> theCache = m_contextInterface->shaderCache();
        QSSGRef<QSSGRenderShaderProgram> depthShaderProgram = theCache->getProgram(name, ShaderFeatureSetList());
        if (!depthShaderProgram) {
            getProgramGenerator()->beginProgram(
                    QSSGShaderGeneratorStageFlags(QSSGShaderGeneratorStage::Vertex | QSSGShaderGeneratorStage::TessControl
                                               | QSSGShaderGeneratorStage::TessEval | QSSGShaderGeneratorStage::Fragment));
            QSSGShaderStageGeneratorInterface &vertexShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Vertex));
            QSSGShaderStageGeneratorInterface &tessCtrlShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::TessControl));
            QSSGShaderStageGeneratorInterface &tessEvalShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::TessEval));
            QSSGShaderStageGeneratorInterface &fragmentShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Fragment));

            vertexShader.addIncoming("attr_pos", "vec3");
            // vertexShader.AddOutgoing("world_pos", "vec4");
            vertexShader.addUniform("modelViewProjection", "mat4");

            vertexShader.append("void main() {");
            vertexShader.append("    gl_Position = vec4(attr_pos, 1.0);");
            // vertexShader.Append("    world_pos = attr_pos;");
            vertexShader.append("}");

            tessCtrlShader.addInclude("tessellationPhong.glsllib");
            tessCtrlShader.addUniform("tessLevelInner", "float");
            tessCtrlShader.addUniform("tessLevelOuter", "float");
            // tessCtrlShader.AddOutgoing( "outUVTC", "vec2" );
            // tessCtrlShader.AddOutgoing( "outNormalTC", "vec3" );
            tessCtrlShader.append("void main() {\n");
            // tessCtrlShader.Append("    ctWorldPos[0] = outWorldPos[0];");
            // tessCtrlShader.Append("    ctWorldPos[1] = outWorldPos[1];");
            // tessCtrlShader.Append("    ctWorldPos[2] = outWorldPos[2];");
            tessCtrlShader.append("    gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;");
            tessCtrlShader.append("    tessShader( tessLevelOuter, tessLevelInner);\n");
            tessCtrlShader.append("}");

            tessEvalShader.addInclude("tessellationPhong.glsllib");
            tessEvalShader.addUniform("modelViewProjection", "mat4");
            tessEvalShader.addOutgoing("world_pos", "vec4");
            tessEvalShader.append("void main() {");
            tessEvalShader.append("    vec4 pos = tessShader( );\n");
            QSSGShaderProgramGeneratorInterface::outputParaboloidDepthTessEval(tessEvalShader);
            tessEvalShader.append("}");

            QSSGShaderProgramGeneratorInterface::outputParaboloidDepthFragment(fragmentShader);
            depthShaderProgram = getProgramGenerator()->compileGeneratedShader(name, QSSGShaderCacheProgramFlags(), ShaderFeatureSetList());
        }

        if (depthShaderProgram) {
            theDepthShader = QSSGRef<QSSGRenderableDepthPrepassShader>(
                    new QSSGRenderableDepthPrepassShader(depthShaderProgram, context()));
        } else {
            theDepthShader = QSSGRef<QSSGRenderableDepthPrepassShader>();
        }
    }

    return theDepthShader;
}

QSSGRef<QSSGRenderableDepthPrepassShader> QSSGRendererImpl::getParaboloidDepthTessNPatchShader()
{
    QSSGRef<QSSGRenderableDepthPrepassShader> &theDepthShader = m_paraboloidDepthTessNPatchShader;

    if (theDepthShader.isNull()) {
        QByteArray name = "paraboloid depth tess NPatch shader";

        QSSGRef<QSSGShaderCache> theCache = m_contextInterface->shaderCache();
        QSSGRef<QSSGRenderShaderProgram> depthShaderProgram = theCache->getProgram(name, ShaderFeatureSetList());
        if (!depthShaderProgram) {
            getProgramGenerator()->beginProgram(
                    QSSGShaderGeneratorStageFlags(QSSGShaderGeneratorStage::Vertex | QSSGShaderGeneratorStage::TessControl
                                               | QSSGShaderGeneratorStage::TessEval | QSSGShaderGeneratorStage::Fragment));
            QSSGShaderStageGeneratorInterface &vertexShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Vertex));
            QSSGShaderStageGeneratorInterface &tessCtrlShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::TessControl));
            QSSGShaderStageGeneratorInterface &tessEvalShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::TessEval));
            QSSGShaderStageGeneratorInterface &fragmentShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Fragment));

            vertexShader.addIncoming("attr_pos", "vec3");
            // vertexShader.AddOutgoing("world_pos", "vec4");
            vertexShader.addUniform("modelViewProjection", "mat4");

            vertexShader.append("void main() {");
            vertexShader.append("    gl_Position = vec4(attr_pos, 1.0);");
            // vertexShader.Append("    world_pos = attr_pos;");
            vertexShader.append("}");

            tessCtrlShader.addInclude("tessellationNPatch.glsllib");
            tessCtrlShader.addUniform("tessLevelInner", "float");
            tessCtrlShader.addUniform("tessLevelOuter", "float");
            // tessCtrlShader.AddOutgoing( "outUVTC", "vec2" );
            // tessCtrlShader.AddOutgoing( "outNormalTC", "vec3" );
            tessCtrlShader.append("void main() {\n");
            // tessCtrlShader.Append("    ctWorldPos[0] = outWorldPos[0];");
            // tessCtrlShader.Append("    ctWorldPos[1] = outWorldPos[1];");
            // tessCtrlShader.Append("    ctWorldPos[2] = outWorldPos[2];");
            tessCtrlShader.append("    gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;");
            tessCtrlShader.append("    tessShader( tessLevelOuter, tessLevelInner);\n");
            tessCtrlShader.append("}");

            tessEvalShader.addInclude("tessellationNPatch.glsllib");
            tessEvalShader.addUniform("modelViewProjection", "mat4");
            tessEvalShader.addOutgoing("world_pos", "vec4");
            tessEvalShader.append("void main() {");
            tessEvalShader.append("    vec4 pos = tessShader( );\n");
            QSSGShaderProgramGeneratorInterface::outputParaboloidDepthTessEval(tessEvalShader);
            tessEvalShader.append("}");

            QSSGShaderProgramGeneratorInterface::outputParaboloidDepthFragment(fragmentShader);
            depthShaderProgram = getProgramGenerator()->compileGeneratedShader(name, QSSGShaderCacheProgramFlags(), ShaderFeatureSetList());
        }

        if (depthShaderProgram) {
            theDepthShader = QSSGRef<QSSGRenderableDepthPrepassShader>(
                    new QSSGRenderableDepthPrepassShader(depthShaderProgram, context()));
        } else {
            theDepthShader = QSSGRef<QSSGRenderableDepthPrepassShader>();
        }
    }

    return theDepthShader;
}

QSSGRef<QSSGRenderableDepthPrepassShader> QSSGRendererImpl::getCubeShadowDepthShader(TessellationModeValues inTessMode)
{
    if (m_contextInterface->renderContext()->supportsTessellation()
            && inTessMode != TessellationModeValues::NoTessellation) {
        if (inTessMode == TessellationModeValues::Linear)
            return getCubeDepthTessLinearShader();
        if (inTessMode == TessellationModeValues::Phong)
            return getCubeDepthTessPhongShader();
        if (inTessMode == TessellationModeValues::NPatch)
            return getCubeDepthTessNPatchShader();
    }

    return getCubeDepthNoTessShader();
}

QSSGRef<QSSGRenderableDepthPrepassShader> QSSGRendererImpl::getCubeDepthNoTessShader()
{
    QSSGRef<QSSGRenderableDepthPrepassShader> &theDepthShader = m_cubemapDepthShader;

    if (theDepthShader.isNull()) {
        QByteArray name = "cubemap face depth shader";

        QSSGRef<QSSGShaderCache> theCache = m_contextInterface->shaderCache();
        QSSGRef<QSSGRenderShaderProgram> depthShaderProgram = theCache->getProgram(name, ShaderFeatureSetList());

        if (!depthShaderProgram) {
            // GetProgramGenerator()->BeginProgram(
            // TShaderGeneratorStageFlags(ShaderGeneratorStages::Vertex |
            // ShaderGeneratorStages::Fragment | ShaderGeneratorStages::Geometry) );
            getProgramGenerator()->beginProgram();
            QSSGShaderStageGeneratorInterface &vertexShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Vertex));
            QSSGShaderStageGeneratorInterface &fragmentShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Fragment));
            // IShaderStageGenerator& geometryShader( *GetProgramGenerator()->GetStage(
            // ShaderGeneratorStages::Geometry ) );

            QSSGShaderProgramGeneratorInterface::outputCubeFaceDepthVertex(vertexShader);
            // IShaderProgramGenerator::OutputCubeFaceDepthGeometry( geometryShader );
            QSSGShaderProgramGeneratorInterface::outputCubeFaceDepthFragment(fragmentShader);
            depthShaderProgram = getProgramGenerator()->compileGeneratedShader(name, QSSGShaderCacheProgramFlags(), ShaderFeatureSetList());
        } else if (theCache->isShaderCachePersistenceEnabled()) {
            // we load from shader cache set default shader stages
            getProgramGenerator()->beginProgram();
            depthShaderProgram = getProgramGenerator()->compileGeneratedShader(name, QSSGShaderCacheProgramFlags(), ShaderFeatureSetList());
        }

        if (depthShaderProgram) {
            theDepthShader = QSSGRef<QSSGRenderableDepthPrepassShader>(
                    new QSSGRenderableDepthPrepassShader(depthShaderProgram, context()));
        } else {
            theDepthShader = QSSGRef<QSSGRenderableDepthPrepassShader>();
        }
    }

    return theDepthShader;
}

QSSGRef<QSSGRenderableDepthPrepassShader> QSSGRendererImpl::getCubeDepthTessLinearShader()
{
    QSSGRef<QSSGRenderableDepthPrepassShader> &theDepthShader = m_cubemapDepthTessLinearShader;

    if (theDepthShader.isNull()) {
        QByteArray name = "cubemap face depth linear tess shader";

        QSSGRef<QSSGShaderCache> theCache = m_contextInterface->shaderCache();
        QSSGRef<QSSGRenderShaderProgram> depthShaderProgram = theCache->getProgram(name, ShaderFeatureSetList());

        if (!depthShaderProgram) {
            // GetProgramGenerator()->BeginProgram(
            // TShaderGeneratorStageFlags(ShaderGeneratorStages::Vertex |
            // ShaderGeneratorStages::Fragment | ShaderGeneratorStages::Geometry) );
            getProgramGenerator()->beginProgram(
                    QSSGShaderGeneratorStageFlags(QSSGShaderGeneratorStage::Vertex | QSSGShaderGeneratorStage::TessControl
                                               | QSSGShaderGeneratorStage::TessEval | QSSGShaderGeneratorStage::Fragment));
            QSSGShaderStageGeneratorInterface &vertexShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Vertex));
            QSSGShaderStageGeneratorInterface &fragmentShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Fragment));
            // IShaderStageGenerator& geometryShader( *GetProgramGenerator()->GetStage(
            // ShaderGeneratorStages::Geometry ) );
            QSSGShaderStageGeneratorInterface &tessCtrlShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::TessControl));
            QSSGShaderStageGeneratorInterface &tessEvalShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::TessEval));

            vertexShader.addIncoming("attr_pos", "vec3");
            vertexShader.append("void main() {");
            vertexShader.append("    gl_Position = vec4(attr_pos, 1.0);");
            vertexShader.append("}");

            // IShaderProgramGenerator::OutputCubeFaceDepthGeometry( geometryShader );
            QSSGShaderProgramGeneratorInterface::outputCubeFaceDepthFragment(fragmentShader);

            tessCtrlShader.addInclude("tessellationLinear.glsllib");
            tessCtrlShader.addUniform("tessLevelInner", "float");
            tessCtrlShader.addUniform("tessLevelOuter", "float");
            tessCtrlShader.append("void main() {\n");
            tessCtrlShader.append("    gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;");
            tessCtrlShader.append("    tessShader( tessLevelOuter, tessLevelInner);\n");
            tessCtrlShader.append("}");

            tessEvalShader.addInclude("tessellationLinear.glsllib");
            tessEvalShader.addUniform("modelViewProjection", "mat4");
            tessEvalShader.addUniform("modelMatrix", "mat4");
            tessEvalShader.addOutgoing("world_pos", "vec4");
            tessEvalShader.append("void main() {");
            tessEvalShader.append("    vec4 pos = tessShader( );\n");
            tessEvalShader.append("    world_pos = modelMatrix * pos;");
            tessEvalShader.append("    world_pos /= world_pos.w;");
            tessEvalShader.append("    gl_Position = modelViewProjection * pos;");
            tessEvalShader.append("}");

            depthShaderProgram = getProgramGenerator()->compileGeneratedShader(name, QSSGShaderCacheProgramFlags(), ShaderFeatureSetList());
        }

        if (depthShaderProgram) {
            theDepthShader = QSSGRef<QSSGRenderableDepthPrepassShader>(
                    new QSSGRenderableDepthPrepassShader(depthShaderProgram, context()));
        } else {
            theDepthShader = QSSGRef<QSSGRenderableDepthPrepassShader>();
        }
    }

    return theDepthShader;
}

QSSGRef<QSSGRenderableDepthPrepassShader> QSSGRendererImpl::getCubeDepthTessPhongShader()
{
    QSSGRef<QSSGRenderableDepthPrepassShader> &theDepthShader = m_cubemapDepthTessPhongShader;

    if (theDepthShader.isNull()) {
        QByteArray name = "cubemap face depth phong tess shader";

        QSSGRef<QSSGShaderCache> theCache = m_contextInterface->shaderCache();
        QSSGRef<QSSGRenderShaderProgram> depthShaderProgram = theCache->getProgram(name, ShaderFeatureSetList());

        if (!depthShaderProgram) {
            // GetProgramGenerator()->BeginProgram(
            // TShaderGeneratorStageFlags(ShaderGeneratorStages::Vertex |
            // ShaderGeneratorStages::Fragment | ShaderGeneratorStages::Geometry) );
            getProgramGenerator()->beginProgram(
                    QSSGShaderGeneratorStageFlags(QSSGShaderGeneratorStage::Vertex | QSSGShaderGeneratorStage::TessControl
                                               | QSSGShaderGeneratorStage::TessEval | QSSGShaderGeneratorStage::Fragment));
            QSSGShaderStageGeneratorInterface &vertexShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Vertex));
            QSSGShaderStageGeneratorInterface &fragmentShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Fragment));
            // IShaderStageGenerator& geometryShader( *GetProgramGenerator()->GetStage(
            // ShaderGeneratorStages::Geometry ) );
            QSSGShaderStageGeneratorInterface &tessCtrlShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::TessControl));
            QSSGShaderStageGeneratorInterface &tessEvalShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::TessEval));

            vertexShader.addIncoming("attr_pos", "vec3");
            vertexShader.addIncoming("attr_norm", "vec3");
            vertexShader.addOutgoing("outNormal", "vec3");
            vertexShader.append("void main() {");
            vertexShader.append("    gl_Position = vec4(attr_pos, 1.0);");
            vertexShader.append("    outNormal = attr_norm;");
            vertexShader.append("}");

            // IShaderProgramGenerator::OutputCubeFaceDepthGeometry( geometryShader );
            QSSGShaderProgramGeneratorInterface::outputCubeFaceDepthFragment(fragmentShader);

            tessCtrlShader.addInclude("tessellationPhong.glsllib");
            tessCtrlShader.addUniform("tessLevelInner", "float");
            tessCtrlShader.addUniform("tessLevelOuter", "float");
            tessCtrlShader.append("void main() {\n");
            tessCtrlShader.append("    ctNorm[0] = outNormal[0];");
            tessCtrlShader.append("    ctNorm[1] = outNormal[1];");
            tessCtrlShader.append("    ctNorm[2] = outNormal[2];");
            tessCtrlShader.append("    gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;");
            tessCtrlShader.append("    tessShader( tessLevelOuter, tessLevelInner);\n");
            tessCtrlShader.append("}");

            tessEvalShader.addInclude("tessellationPhong.glsllib");
            tessEvalShader.addUniform("modelViewProjection", "mat4");
            tessEvalShader.addUniform("modelMatrix", "mat4");
            tessEvalShader.addOutgoing("world_pos", "vec4");
            tessEvalShader.append("void main() {");
            tessEvalShader.append("    vec4 pos = tessShader( );\n");
            tessEvalShader.append("    world_pos = modelMatrix * pos;");
            tessEvalShader.append("    world_pos /= world_pos.w;");
            tessEvalShader.append("    gl_Position = modelViewProjection * pos;");
            tessEvalShader.append("}");

            depthShaderProgram = getProgramGenerator()->compileGeneratedShader(name, QSSGShaderCacheProgramFlags(), ShaderFeatureSetList());
        }

        if (depthShaderProgram) {
            theDepthShader = QSSGRef<QSSGRenderableDepthPrepassShader>(
                    new QSSGRenderableDepthPrepassShader(depthShaderProgram, context()));
        } else {
            theDepthShader = QSSGRef<QSSGRenderableDepthPrepassShader>();
        }
    }

    return theDepthShader;
}

QSSGRef<QSSGRenderableDepthPrepassShader> QSSGRendererImpl::getCubeDepthTessNPatchShader()
{
    QSSGRef<QSSGRenderableDepthPrepassShader> &theDepthShader = m_cubemapDepthTessNPatchShader;

    if (theDepthShader.isNull()) {
        QByteArray name = "cubemap face depth npatch tess shader";

        QSSGRef<QSSGShaderCache> theCache = m_contextInterface->shaderCache();
        QSSGRef<QSSGRenderShaderProgram> depthShaderProgram = theCache->getProgram(name, ShaderFeatureSetList());

        if (!depthShaderProgram) {
            // GetProgramGenerator()->BeginProgram(
            // TShaderGeneratorStageFlags(ShaderGeneratorStages::Vertex |
            // ShaderGeneratorStages::Fragment | ShaderGeneratorStages::Geometry) );
            getProgramGenerator()->beginProgram(
                    QSSGShaderGeneratorStageFlags(QSSGShaderGeneratorStage::Vertex | QSSGShaderGeneratorStage::TessControl
                                               | QSSGShaderGeneratorStage::TessEval | QSSGShaderGeneratorStage::Fragment));
            QSSGShaderStageGeneratorInterface &vertexShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Vertex));
            QSSGShaderStageGeneratorInterface &fragmentShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Fragment));
            // IShaderStageGenerator& geometryShader( *GetProgramGenerator()->GetStage(
            // ShaderGeneratorStages::Geometry ) );
            QSSGShaderStageGeneratorInterface &tessCtrlShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::TessControl));
            QSSGShaderStageGeneratorInterface &tessEvalShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::TessEval));

            vertexShader.addIncoming("attr_pos", "vec3");
            vertexShader.addIncoming("attr_norm", "vec3");
            vertexShader.addOutgoing("outNormal", "vec3");
            vertexShader.append("void main() {");
            vertexShader.append("    gl_Position = vec4(attr_pos, 1.0);");
            vertexShader.append("    outNormal = attr_norm;");
            vertexShader.append("}");

            // IShaderProgramGenerator::OutputCubeFaceDepthGeometry( geometryShader );
            QSSGShaderProgramGeneratorInterface::outputCubeFaceDepthFragment(fragmentShader);

            tessCtrlShader.addOutgoing("outNormalTC", "vec3");
            tessCtrlShader.addInclude("tessellationNPatch.glsllib");
            tessCtrlShader.addUniform("tessLevelInner", "float");
            tessCtrlShader.addUniform("tessLevelOuter", "float");
            tessCtrlShader.append("void main() {\n");
            tessCtrlShader.append("    ctNorm[0] = outNormal[0];");
            tessCtrlShader.append("    ctNorm[1] = outNormal[1];");
            tessCtrlShader.append("    ctNorm[2] = outNormal[2];");
            tessCtrlShader.append("    gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;");
            tessCtrlShader.append("    tessShader( tessLevelOuter, tessLevelInner);\n");
            tessCtrlShader.append("    outNormalTC[gl_InvocationID] = outNormal[gl_InvocationID];\n");
            tessCtrlShader.append("}");

            tessEvalShader.addInclude("tessellationNPatch.glsllib");
            tessEvalShader.addUniform("modelViewProjection", "mat4");
            tessEvalShader.addUniform("modelMatrix", "mat4");
            tessEvalShader.addOutgoing("world_pos", "vec4");
            tessEvalShader.append("void main() {");
            tessEvalShader.append("    ctNorm[0] = outNormalTC[0];");
            tessEvalShader.append("    ctNorm[1] = outNormalTC[1];");
            tessEvalShader.append("    ctNorm[2] = outNormalTC[2];");
            tessEvalShader.append("    vec4 pos = tessShader( );\n");
            tessEvalShader.append("    world_pos = modelMatrix * pos;");
            tessEvalShader.append("    world_pos /= world_pos.w;");
            tessEvalShader.append("    gl_Position = modelViewProjection * pos;");
            tessEvalShader.append("}");

            depthShaderProgram = getProgramGenerator()->compileGeneratedShader(name, QSSGShaderCacheProgramFlags(), ShaderFeatureSetList());
        }

        if (depthShaderProgram) {
            theDepthShader = QSSGRef<QSSGRenderableDepthPrepassShader>(
                    new QSSGRenderableDepthPrepassShader(depthShaderProgram, context()));
        } else {
            theDepthShader = QSSGRef<QSSGRenderableDepthPrepassShader>();
        }
    }

    return theDepthShader;
}

QSSGRef<QSSGRenderableDepthPrepassShader> QSSGRendererImpl::getOrthographicDepthShader(TessellationModeValues inTessMode)
{
    if (m_contextInterface->renderContext()->supportsTessellation()
            && inTessMode != TessellationModeValues::NoTessellation) {
        if (inTessMode == TessellationModeValues::Linear)
            return getOrthographicDepthTessLinearShader();
        if (inTessMode == TessellationModeValues::Phong)
            return getOrthographicDepthTessPhongShader();
        if (inTessMode == TessellationModeValues::NPatch)
            return getOrthographicDepthTessNPatchShader();
    }

    return getOrthographicDepthNoTessShader();
}

QSSGRef<QSSGRenderableDepthPrepassShader> QSSGRendererImpl::getOrthographicDepthNoTessShader()
{
    QSSGRef<QSSGRenderableDepthPrepassShader> &theDepthShader = m_orthographicDepthShader;

    if (theDepthShader.isNull()) {
        QByteArray name = "orthographic depth shader";

        QSSGRef<QSSGShaderCache> theCache = m_contextInterface->shaderCache();
        QSSGRef<QSSGRenderShaderProgram> depthShaderProgram = theCache->getProgram(name, ShaderFeatureSetList());
        if (!depthShaderProgram) {
            getProgramGenerator()->beginProgram();
            QSSGShaderStageGeneratorInterface &vertexShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Vertex));
            QSSGShaderStageGeneratorInterface &fragmentShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Fragment));
            vertexShader.addIncoming("attr_pos", "vec3");
            vertexShader.addUniform("modelViewProjection", "mat4");
            vertexShader.addOutgoing("outDepth", "vec3");
            vertexShader.append("void main() {");
            vertexShader.append("   gl_Position = modelViewProjection * vec4( attr_pos, 1.0 );");
            vertexShader.append("   outDepth.x = gl_Position.z / gl_Position.w;");
            vertexShader.append("}");
            fragmentShader.append("void main() {");
            fragmentShader.append("    float depth = (outDepth.x + 1.0) * 0.5;");
            fragmentShader.append("    fragOutput = vec4(depth);");
            fragmentShader.append("}");

            depthShaderProgram = getProgramGenerator()->compileGeneratedShader(name, QSSGShaderCacheProgramFlags(), ShaderFeatureSetList());
        }

        if (depthShaderProgram) {
            theDepthShader = QSSGRef<QSSGRenderableDepthPrepassShader>(
                    new QSSGRenderableDepthPrepassShader(depthShaderProgram, context()));
        } else {
            theDepthShader = QSSGRef<QSSGRenderableDepthPrepassShader>();
        }
    }

    return theDepthShader;
}

QSSGRef<QSSGRenderableDepthPrepassShader> QSSGRendererImpl::getOrthographicDepthTessLinearShader()
{
    QSSGRef<QSSGRenderableDepthPrepassShader> &theDepthShader = m_orthographicDepthTessLinearShader;

    if (theDepthShader.isNull()) {
        QByteArray name = "orthographic depth tess linear shader";

        QSSGRef<QSSGShaderCache> theCache = m_contextInterface->shaderCache();
        QSSGRef<QSSGRenderShaderProgram> depthShaderProgram = theCache->getProgram(name, ShaderFeatureSetList());
        if (!depthShaderProgram) {
            getProgramGenerator()->beginProgram(
                    QSSGShaderGeneratorStageFlags(QSSGShaderGeneratorStage::Vertex | QSSGShaderGeneratorStage::TessControl
                                               | QSSGShaderGeneratorStage::TessEval | QSSGShaderGeneratorStage::Fragment));
            QSSGShaderStageGeneratorInterface &vertexShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Vertex));
            QSSGShaderStageGeneratorInterface &tessCtrlShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::TessControl));
            QSSGShaderStageGeneratorInterface &tessEvalShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::TessEval));
            QSSGShaderStageGeneratorInterface &fragmentShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Fragment));

            vertexShader.addIncoming("attr_pos", "vec3");
            vertexShader.addUniform("modelViewProjection", "mat4");

            vertexShader.append("void main() {");
            vertexShader.append("    gl_Position = vec4(attr_pos, 1.0);");
            vertexShader.append("}");
            fragmentShader.append("void main() {");
            fragmentShader.append("    float depth = (outDepth.x + 1.0) * 0.5;");
            fragmentShader.append("    fragOutput = vec4(depth);");
            fragmentShader.append("}");

            tessCtrlShader.addInclude("tessellationLinear.glsllib");
            tessCtrlShader.addUniform("tessLevelInner", "float");
            tessCtrlShader.addUniform("tessLevelOuter", "float");
            tessCtrlShader.append("void main() {\n");
            tessCtrlShader.append("    gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;");
            tessCtrlShader.append("    tessShader( tessLevelOuter, tessLevelInner);\n");
            tessCtrlShader.append("}");

            tessEvalShader.addInclude("tessellationLinear.glsllib");
            tessEvalShader.addUniform("modelViewProjection", "mat4");
            tessEvalShader.addOutgoing("outDepth", "vec3");
            tessEvalShader.append("void main() {");
            tessEvalShader.append("    vec4 pos = tessShader( );\n");
            tessEvalShader.append("    gl_Position = modelViewProjection * pos;");
            tessEvalShader.append("    outDepth.x = gl_Position.z / gl_Position.w;");
            tessEvalShader.append("}");

            depthShaderProgram = getProgramGenerator()->compileGeneratedShader(name, QSSGShaderCacheProgramFlags(), ShaderFeatureSetList());
        }

        if (depthShaderProgram) {
            theDepthShader = QSSGRef<QSSGRenderableDepthPrepassShader>(
                    new QSSGRenderableDepthPrepassShader(depthShaderProgram, context()));
        } else {
            theDepthShader = QSSGRef<QSSGRenderableDepthPrepassShader>();
        }
    }

    return theDepthShader;
}

QSSGRef<QSSGRenderableDepthPrepassShader> QSSGRendererImpl::getOrthographicDepthTessPhongShader()
{
    QSSGRef<QSSGRenderableDepthPrepassShader> &theDepthShader = m_orthographicDepthTessPhongShader;

    if (theDepthShader.isNull()) {
        QByteArray name = "orthographic depth tess phong shader";

        QSSGRef<QSSGShaderCache> theCache = m_contextInterface->shaderCache();
        QSSGRef<QSSGRenderShaderProgram> depthShaderProgram = theCache->getProgram(name, ShaderFeatureSetList());
        if (!depthShaderProgram) {
            getProgramGenerator()->beginProgram(
                    QSSGShaderGeneratorStageFlags(QSSGShaderGeneratorStage::Vertex | QSSGShaderGeneratorStage::TessControl
                                               | QSSGShaderGeneratorStage::TessEval | QSSGShaderGeneratorStage::Fragment));
            QSSGShaderStageGeneratorInterface &vertexShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Vertex));
            QSSGShaderStageGeneratorInterface &tessCtrlShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::TessControl));
            QSSGShaderStageGeneratorInterface &tessEvalShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::TessEval));
            QSSGShaderStageGeneratorInterface &fragmentShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Fragment));

            vertexShader.addIncoming("attr_pos", "vec3");
            vertexShader.addIncoming("attr_norm", "vec3");
            vertexShader.addOutgoing("outNormal", "vec3");
            vertexShader.addUniform("modelViewProjection", "mat4");

            vertexShader.append("void main() {");
            vertexShader.append("    gl_Position = vec4(attr_pos, 1.0);");
            vertexShader.append("    outNormal = attr_norm;");
            vertexShader.append("}");
            fragmentShader.append("void main() {");
            fragmentShader.append("    float depth = (outDepth.x + 1.0) * 0.5;");
            fragmentShader.append("    fragOutput = vec4(depth);");
            fragmentShader.append("}");

            tessCtrlShader.addInclude("tessellationPhong.glsllib");
            tessCtrlShader.addUniform("tessLevelInner", "float");
            tessCtrlShader.addUniform("tessLevelOuter", "float");
            tessCtrlShader.append("void main() {\n");
            tessCtrlShader.append("    ctNorm[0] = outNormal[0];");
            tessCtrlShader.append("    ctNorm[1] = outNormal[1];");
            tessCtrlShader.append("    ctNorm[2] = outNormal[2];");
            tessCtrlShader.append("    gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;");
            tessCtrlShader.append("    tessShader( tessLevelOuter, tessLevelInner);\n");
            tessCtrlShader.append("}");

            tessEvalShader.addInclude("tessellationPhong.glsllib");
            tessEvalShader.addUniform("modelViewProjection", "mat4");
            tessEvalShader.addOutgoing("outDepth", "vec3");
            tessEvalShader.append("void main() {");
            tessEvalShader.append("    vec4 pos = tessShader( );\n");
            tessEvalShader.append("    gl_Position = modelViewProjection * pos;");
            tessEvalShader.append("    outDepth.x = gl_Position.z / gl_Position.w;");
            tessEvalShader.append("}");

            depthShaderProgram = getProgramGenerator()->compileGeneratedShader(name, QSSGShaderCacheProgramFlags(), ShaderFeatureSetList());
        }

        if (depthShaderProgram) {
            theDepthShader = QSSGRef<QSSGRenderableDepthPrepassShader>(
                    new QSSGRenderableDepthPrepassShader(depthShaderProgram, context()));
        } else {
            theDepthShader = QSSGRef<QSSGRenderableDepthPrepassShader>();
        }
    }

    return theDepthShader;
}

QSSGRef<QSSGRenderableDepthPrepassShader> QSSGRendererImpl::getOrthographicDepthTessNPatchShader()
{
    QSSGRef<QSSGRenderableDepthPrepassShader> &theDepthShader = m_orthographicDepthTessNPatchShader;

    if (theDepthShader.isNull()) {
        QByteArray name = "orthographic depth tess npatch shader";

        QSSGRef<QSSGShaderCache> theCache = m_contextInterface->shaderCache();
        QSSGRef<QSSGRenderShaderProgram> depthShaderProgram = theCache->getProgram(name, ShaderFeatureSetList());
        if (!depthShaderProgram) {
            getProgramGenerator()->beginProgram(
                    QSSGShaderGeneratorStageFlags(QSSGShaderGeneratorStage::Vertex | QSSGShaderGeneratorStage::TessControl
                                               | QSSGShaderGeneratorStage::TessEval | QSSGShaderGeneratorStage::Fragment));
            QSSGShaderStageGeneratorInterface &vertexShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Vertex));
            QSSGShaderStageGeneratorInterface &tessCtrlShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::TessControl));
            QSSGShaderStageGeneratorInterface &tessEvalShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::TessEval));
            QSSGShaderStageGeneratorInterface &fragmentShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Fragment));

            vertexShader.addIncoming("attr_pos", "vec3");
            vertexShader.addIncoming("attr_norm", "vec3");
            vertexShader.addOutgoing("outNormal", "vec3");
            vertexShader.addUniform("modelViewProjection", "mat4");
            fragmentShader.addUniform("modelViewProjection", "mat4");
            fragmentShader.addUniform("cameraProperties", "vec2");
            fragmentShader.addUniform("cameraPosition", "vec3");
            fragmentShader.addUniform("cameraDirection", "vec3");
            fragmentShader.addInclude("depthpass.glsllib");

            vertexShader.append("void main() {");
            vertexShader.append("    gl_Position = vec4(attr_pos, 1.0);");
            vertexShader.append("    outNormal = attr_norm;");
            vertexShader.append("}");
            fragmentShader.append("void main() {");
            // fragmentShader.Append("    fragOutput = vec4(0.0, 0.0, 0.0, 0.0);");
            fragmentShader.append("    float depth = (outDepth.x - cameraProperties.x) / "
                                  "(cameraProperties.y - cameraProperties.x);");
            fragmentShader.append("    fragOutput = vec4(depth);");
            fragmentShader.append("}");

            tessCtrlShader.addInclude("tessellationNPatch.glsllib");
            tessCtrlShader.addUniform("tessLevelInner", "float");
            tessCtrlShader.addUniform("tessLevelOuter", "float");
            tessCtrlShader.addOutgoing("outNormalTC", "vec3");
            tessCtrlShader.append("void main() {\n");
            tessCtrlShader.append("    ctNorm[0] = outNormal[0];");
            tessCtrlShader.append("    ctNorm[1] = outNormal[1];");
            tessCtrlShader.append("    ctNorm[2] = outNormal[2];");
            tessCtrlShader.append("    gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;");
            tessCtrlShader.append("    tessShader( tessLevelOuter, tessLevelInner);\n");
            tessCtrlShader.append("}");

            tessEvalShader.addInclude("tessellationNPatch.glsllib");
            tessEvalShader.addUniform("modelViewProjection", "mat4");
            tessEvalShader.addUniform("modelMatrix", "mat4");
            tessEvalShader.addOutgoing("outDepth", "vec3");
            tessEvalShader.append("void main() {");
            tessEvalShader.append("    vec4 pos = tessShader( );\n");
            tessEvalShader.append("    gl_Position = modelViewProjection * pos;");
            tessEvalShader.append("    outDepth.x = gl_Position.z / gl_Position.w;");
            tessEvalShader.append("}");

            depthShaderProgram = getProgramGenerator()->compileGeneratedShader(name, QSSGShaderCacheProgramFlags(), ShaderFeatureSetList());
        }

        if (depthShaderProgram) {
            theDepthShader = QSSGRef<QSSGRenderableDepthPrepassShader>(
                    new QSSGRenderableDepthPrepassShader(depthShaderProgram, context()));
        } else {
            theDepthShader = QSSGRef<QSSGRenderableDepthPrepassShader>();
        }
    }

    return theDepthShader;
}

// ---------------------------------

const QSSGRef<QSSGRenderableDepthPrepassShader> &QSSGRendererImpl::getDepthPrepassShader(bool inDisplaced)
{
    QSSGRef<QSSGRenderableDepthPrepassShader> &theDepthPrePassShader = (!inDisplaced)
            ? m_depthPrepassShader
            : m_depthPrepassShaderDisplaced;

    if (theDepthPrePassShader.isNull()) {
        // check if we do displacement mapping
        QByteArray name = "depth prepass shader";
        if (inDisplaced)
            name.append(" displacement");

        QSSGRef<QSSGShaderCache> theCache = m_contextInterface->shaderCache();
        QSSGRef<QSSGRenderShaderProgram> depthShaderProgram = theCache->getProgram(name, ShaderFeatureSetList());
        if (!depthShaderProgram) {
            getProgramGenerator()->beginProgram();
            QSSGShaderStageGeneratorInterface &vertexShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Vertex));
            QSSGShaderStageGeneratorInterface &fragmentShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Fragment));
            vertexShader.addIncoming("attr_pos", "vec3");
            vertexShader.addUniform("modelViewProjection", "mat4");

            vertexShader.append("void main() {");

            if (inDisplaced) {
                contextInterface()->defaultMaterialShaderGenerator()->addDisplacementMappingForDepthPass(vertexShader);
            } else {
                vertexShader.append("    gl_Position = modelViewProjection * vec4(attr_pos, 1.0);");
            }
            vertexShader.append("}");
            fragmentShader.append("void main() {");
            fragmentShader.append("    fragOutput = vec4(0.0, 0.0, 0.0, 0.0);");
            fragmentShader.append("}");

            depthShaderProgram = getProgramGenerator()->compileGeneratedShader(name, QSSGShaderCacheProgramFlags(), ShaderFeatureSetList());
        } else if (theCache->isShaderCachePersistenceEnabled()) {
            // we load from shader cache set default shader stages
            getProgramGenerator()->beginProgram();
            depthShaderProgram = getProgramGenerator()->compileGeneratedShader(name, QSSGShaderCacheProgramFlags(), ShaderFeatureSetList());
        }

        if (depthShaderProgram) {
            theDepthPrePassShader = QSSGRef<QSSGRenderableDepthPrepassShader>(
                    new QSSGRenderableDepthPrepassShader(depthShaderProgram, context()));
        } else {
            theDepthPrePassShader = QSSGRef<QSSGRenderableDepthPrepassShader>();
        }
    }
    return theDepthPrePassShader;
}

const QSSGRef<QSSGRenderableDepthPrepassShader> &QSSGRendererImpl::getDepthTessPrepassShader(TessellationModeValues inTessMode, bool inDisplaced)
{
    if (m_contextInterface->renderContext()->supportsTessellation()
            && inTessMode != TessellationModeValues::NoTessellation) {
        if (inTessMode == TessellationModeValues::Linear)
            return getDepthTessLinearPrepassShader(inDisplaced);
        if (inTessMode == TessellationModeValues::Phong)
            return getDepthTessPhongPrepassShader();
        if (inTessMode == TessellationModeValues::NPatch)
            return getDepthTessNPatchPrepassShader();
    }

    return getDepthPrepassShader(inDisplaced);
}

const QSSGRef<QSSGRenderableDepthPrepassShader> &QSSGRendererImpl::getDepthTessLinearPrepassShader(bool inDisplaced)
{
    QSSGRef<QSSGRenderableDepthPrepassShader> &theDepthPrePassShader = (!inDisplaced)
            ? m_depthTessLinearPrepassShader
            : m_depthTessLinearPrepassShaderDisplaced;

    if (theDepthPrePassShader.isNull()) {
        // check if we do displacement mapping
        QByteArray name = "depth tess linear prepass shader";
        if (inDisplaced)
            name.append(" displacement");

        QSSGRef<QSSGShaderCache> theCache = m_contextInterface->shaderCache();
        QSSGRef<QSSGRenderShaderProgram> depthShaderProgram = theCache->getProgram(name, ShaderFeatureSetList());
        if (!depthShaderProgram) {
            getProgramGenerator()->beginProgram(
                    QSSGShaderGeneratorStageFlags(QSSGShaderGeneratorStage::Vertex | QSSGShaderGeneratorStage::TessControl
                                               | QSSGShaderGeneratorStage::TessEval | QSSGShaderGeneratorStage::Fragment));
            QSSGShaderStageGeneratorInterface &vertexShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Vertex));
            QSSGShaderStageGeneratorInterface &tessCtrlShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::TessControl));
            QSSGShaderStageGeneratorInterface &tessEvalShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::TessEval));
            QSSGShaderStageGeneratorInterface &fragmentShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Fragment));
            vertexShader.addIncoming("attr_pos", "vec3");
            if (inDisplaced) {
                vertexShader.addIncoming("attr_uv0", "vec2");
                vertexShader.addIncoming("attr_norm", "vec3");

                vertexShader.addUniform("displacementMap_rot", "vec4");
                vertexShader.addUniform("displacementMap_offset", "vec3");

                vertexShader.addOutgoing("outNormal", "vec3");
                vertexShader.addOutgoing("outUV", "vec2");
            }
            vertexShader.addOutgoing("outWorldPos", "vec3");
            vertexShader.addUniform("modelViewProjection", "mat4");
            vertexShader.addUniform("modelMatrix", "mat4");
            vertexShader.append("void main() {");
            vertexShader.append("    gl_Position = vec4(attr_pos, 1.0);");
            if (inDisplaced) {
                vertexShader.append("    outNormal = attr_norm;");
                vertexShader.append("    vec3 uTransform = vec3( displacementMap_rot.x, "
                                    "displacementMap_rot.y, displacementMap_offset.x );");
                vertexShader.append("    vec3 vTransform = vec3( displacementMap_rot.z, "
                                    "displacementMap_rot.w, displacementMap_offset.y );");
                vertexShader.addInclude("defaultMaterialLighting.glsllib"); // getTransformedUVCoords is in the
                // lighting code addition.
                vertexShader << "    vec2 uv_coords = attr_uv0;"
                             << "\n";
                vertexShader << "    outUV = getTransformedUVCoords( vec3( uv_coords, 1.0), "
                                "uTransform, vTransform );\n";
            }
            vertexShader.append("    outWorldPos = (modelMatrix * vec4(attr_pos, 1.0)).xyz;");
            vertexShader.append("}");
            fragmentShader.append("void main() {");
            fragmentShader.append("    fragOutput = vec4(0.0, 0.0, 0.0, 0.0);");
            fragmentShader.append("}");

            tessCtrlShader.addInclude("tessellationLinear.glsllib");
            tessCtrlShader.addUniform("tessLevelInner", "float");
            tessCtrlShader.addUniform("tessLevelOuter", "float");
            tessCtrlShader.addOutgoing("outUVTC", "vec2");
            tessCtrlShader.addOutgoing("outNormalTC", "vec3");
            tessCtrlShader.append("void main() {\n");
            tessCtrlShader.append("    ctWorldPos[0] = outWorldPos[0];");
            tessCtrlShader.append("    ctWorldPos[1] = outWorldPos[1];");
            tessCtrlShader.append("    ctWorldPos[2] = outWorldPos[2];");
            tessCtrlShader.append("    gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;");
            tessCtrlShader.append("    tessShader( tessLevelOuter, tessLevelInner);\n");

            if (inDisplaced) {
                tessCtrlShader.append("    outUVTC[gl_InvocationID] = outUV[gl_InvocationID];");
                tessCtrlShader.append("    outNormalTC[gl_InvocationID] = outNormal[gl_InvocationID];");
            }

            tessCtrlShader.append("}");

            tessEvalShader.addInclude("tessellationLinear.glsllib");
            tessEvalShader.addUniform("modelViewProjection", "mat4");
            if (inDisplaced) {
                tessEvalShader.addUniform("displacementSampler", "sampler2D");
                tessEvalShader.addUniform("displaceAmount", "float");
                tessEvalShader.addInclude("defaultMaterialFileDisplacementTexture.glsllib");
            }
            tessEvalShader.addOutgoing("outUV", "vec2");
            tessEvalShader.addOutgoing("outNormal", "vec3");
            tessEvalShader.append("void main() {");
            tessEvalShader.append("    vec4 pos = tessShader( );\n");

            if (inDisplaced) {
                tessEvalShader << "    outUV = gl_TessCoord.x * outUVTC[0] + gl_TessCoord.y * "
                                  "outUVTC[1] + gl_TessCoord.z * outUVTC[2];"
                               << "\n";
                tessEvalShader << "    outNormal = gl_TessCoord.x * outNormalTC[0] + gl_TessCoord.y * "
                                  "outNormalTC[1] + gl_TessCoord.z * outNormalTC[2];"
                               << "\n";
                tessEvalShader << "    vec3 displacedPos = defaultMaterialFileDisplacementTexture( "
                                  "displacementSampler , displaceAmount, outUV , outNormal, pos.xyz );"
                               << "\n";
                tessEvalShader.append("    gl_Position = modelViewProjection * vec4(displacedPos, 1.0);");
            } else
                tessEvalShader.append("    gl_Position = modelViewProjection * pos;");

            tessEvalShader.append("}");
            QSSGShaderCacheProgramFlags theFlags(ShaderCacheProgramFlagValues::TessellationEnabled);

            depthShaderProgram = getProgramGenerator()->compileGeneratedShader(name, theFlags, ShaderFeatureSetList());
        } else if (theCache->isShaderCachePersistenceEnabled()) {
            // we load from shader cache set default shader stages
            getProgramGenerator()->beginProgram(
                    QSSGShaderGeneratorStageFlags(QSSGShaderGeneratorStage::Vertex | QSSGShaderGeneratorStage::TessControl
                                               | QSSGShaderGeneratorStage::TessEval | QSSGShaderGeneratorStage::Fragment));
            QSSGShaderCacheProgramFlags theFlags(ShaderCacheProgramFlagValues::TessellationEnabled);

            depthShaderProgram = getProgramGenerator()->compileGeneratedShader(name, theFlags, ShaderFeatureSetList());
        }

        if (depthShaderProgram) {
            theDepthPrePassShader = QSSGRef<QSSGRenderableDepthPrepassShader>(
                    new QSSGRenderableDepthPrepassShader(depthShaderProgram, context()));
        } else {
            theDepthPrePassShader = QSSGRef<QSSGRenderableDepthPrepassShader>();
        }
    }
    return theDepthPrePassShader;
}

const QSSGRef<QSSGRenderableDepthPrepassShader> &QSSGRendererImpl::getDepthTessPhongPrepassShader()
{
    if (m_depthTessPhongPrepassShader.isNull()) {
        QSSGRef<QSSGShaderCache> theCache = m_contextInterface->shaderCache();
        QByteArray name = "depth tess phong prepass shader";
        QSSGRef<QSSGRenderShaderProgram> depthShaderProgram = theCache->getProgram(name, ShaderFeatureSetList());
        if (!depthShaderProgram) {
            getProgramGenerator()->beginProgram(
                    QSSGShaderGeneratorStageFlags(QSSGShaderGeneratorStage::Vertex | QSSGShaderGeneratorStage::TessControl
                                               | QSSGShaderGeneratorStage::TessEval | QSSGShaderGeneratorStage::Fragment));
            QSSGShaderStageGeneratorInterface &vertexShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Vertex));
            QSSGShaderStageGeneratorInterface &tessCtrlShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::TessControl));
            QSSGShaderStageGeneratorInterface &tessEvalShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::TessEval));
            QSSGShaderStageGeneratorInterface &fragmentShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Fragment));
            vertexShader.addIncoming("attr_pos", "vec3");
            vertexShader.addIncoming("attr_norm", "vec3");
            vertexShader.addOutgoing("outNormal", "vec3");
            vertexShader.addOutgoing("outWorldPos", "vec3");
            vertexShader.addUniform("modelViewProjection", "mat4");
            vertexShader.addUniform("modelMatrix", "mat4");
            vertexShader.append("void main() {");
            vertexShader.append("    gl_Position = vec4(attr_pos, 1.0);");
            vertexShader.append("    outWorldPos = (modelMatrix * vec4(attr_pos, 1.0)).xyz;");
            vertexShader.append("    outNormal = attr_norm;");
            vertexShader.append("}");
            fragmentShader.append("void main() {");
            fragmentShader.append("    fragOutput = vec4(0.0, 0.0, 0.0, 0.0);");
            fragmentShader.append("}");

            tessCtrlShader.addInclude("tessellationPhong.glsllib");
            tessCtrlShader.addUniform("tessLevelInner", "float");
            tessCtrlShader.addUniform("tessLevelOuter", "float");
            tessCtrlShader.append("void main() {\n");
            tessCtrlShader.append("    ctWorldPos[0] = outWorldPos[0];");
            tessCtrlShader.append("    ctWorldPos[1] = outWorldPos[1];");
            tessCtrlShader.append("    ctWorldPos[2] = outWorldPos[2];");
            tessCtrlShader.append("    ctNorm[0] = outNormal[0];");
            tessCtrlShader.append("    ctNorm[1] = outNormal[1];");
            tessCtrlShader.append("    ctNorm[2] = outNormal[2];");
            tessCtrlShader.append("    gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;");
            tessCtrlShader.append("    tessShader( tessLevelOuter, tessLevelInner);\n");
            tessCtrlShader.append("}");

            tessEvalShader.addInclude("tessellationPhong.glsllib");
            tessEvalShader.addUniform("modelViewProjection", "mat4");
            tessEvalShader.append("void main() {");
            tessEvalShader.append("    vec4 pos = tessShader( );\n");
            tessEvalShader.append("    gl_Position = modelViewProjection * pos;\n");
            tessEvalShader.append("}");
            QSSGShaderCacheProgramFlags theFlags(ShaderCacheProgramFlagValues::TessellationEnabled);

            depthShaderProgram = getProgramGenerator()->compileGeneratedShader(name, theFlags, ShaderFeatureSetList());
        } else if (theCache->isShaderCachePersistenceEnabled()) {
            // we load from shader cache set default shader stages
            getProgramGenerator()->beginProgram(
                    QSSGShaderGeneratorStageFlags(QSSGShaderGeneratorStage::Vertex | QSSGShaderGeneratorStage::TessControl
                                               | QSSGShaderGeneratorStage::TessEval | QSSGShaderGeneratorStage::Fragment));
            QSSGShaderCacheProgramFlags theFlags(ShaderCacheProgramFlagValues::TessellationEnabled);

            depthShaderProgram = getProgramGenerator()->compileGeneratedShader(name, theFlags, ShaderFeatureSetList());
        }

        if (depthShaderProgram) {
            m_depthTessPhongPrepassShader = QSSGRef<QSSGRenderableDepthPrepassShader>(
                    new QSSGRenderableDepthPrepassShader(depthShaderProgram, context()));
        } else {
            m_depthTessPhongPrepassShader = QSSGRef<QSSGRenderableDepthPrepassShader>();
        }
    }
    return m_depthTessPhongPrepassShader;
}

const QSSGRef<QSSGRenderableDepthPrepassShader> &QSSGRendererImpl::getDepthTessNPatchPrepassShader()
{
    if (m_depthTessNPatchPrepassShader.isNull()) {
        QSSGRef<QSSGShaderCache> theCache = m_contextInterface->shaderCache();
        QByteArray name = "depth tess npatch prepass shader";
        QSSGRef<QSSGRenderShaderProgram> depthShaderProgram = theCache->getProgram(name, ShaderFeatureSetList());
        if (!depthShaderProgram) {
            getProgramGenerator()->beginProgram(
                    QSSGShaderGeneratorStageFlags(QSSGShaderGeneratorStage::Vertex | QSSGShaderGeneratorStage::TessControl
                                               | QSSGShaderGeneratorStage::TessEval | QSSGShaderGeneratorStage::Fragment));
            QSSGShaderStageGeneratorInterface &vertexShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Vertex));
            QSSGShaderStageGeneratorInterface &tessCtrlShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::TessControl));
            QSSGShaderStageGeneratorInterface &tessEvalShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::TessEval));
            QSSGShaderStageGeneratorInterface &fragmentShader(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Fragment));
            vertexShader.addIncoming("attr_pos", "vec3");
            vertexShader.addIncoming("attr_norm", "vec3");
            vertexShader.addOutgoing("outNormal", "vec3");
            vertexShader.addOutgoing("outWorldPos", "vec3");
            vertexShader.addUniform("modelViewProjection", "mat4");
            vertexShader.addUniform("modelMatrix", "mat4");
            vertexShader.append("void main() {");
            vertexShader.append("    gl_Position = vec4(attr_pos, 1.0);");
            vertexShader.append("    outWorldPos = (modelMatrix * vec4(attr_pos, 1.0)).xyz;");
            vertexShader.append("    outNormal = attr_norm;");
            vertexShader.append("}");
            fragmentShader.append("void main() {");
            fragmentShader.append("    fragOutput = vec4(0.0, 0.0, 0.0, 0.0);");
            fragmentShader.append("}");

            tessCtrlShader.addOutgoing("outNormalTC", "vec3");
            tessCtrlShader.addInclude("tessellationNPatch.glsllib");
            tessCtrlShader.addUniform("tessLevelInner", "float");
            tessCtrlShader.addUniform("tessLevelOuter", "float");
            tessCtrlShader.append("void main() {\n");
            tessCtrlShader.append("    ctWorldPos[0] = outWorldPos[0];");
            tessCtrlShader.append("    ctWorldPos[1] = outWorldPos[1];");
            tessCtrlShader.append("    ctWorldPos[2] = outWorldPos[2];");
            tessCtrlShader.append("    ctNorm[0] = outNormal[0];");
            tessCtrlShader.append("    ctNorm[1] = outNormal[1];");
            tessCtrlShader.append("    ctNorm[2] = outNormal[2];");
            tessCtrlShader.append("    ctTangent[0] = outNormal[0];"); // we don't care for the tangent
            tessCtrlShader.append("    ctTangent[1] = outNormal[1];");
            tessCtrlShader.append("    ctTangent[2] = outNormal[2];");
            tessCtrlShader.append("    gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;");
            tessCtrlShader.append("    tessShader( tessLevelOuter, tessLevelInner);\n");
            tessCtrlShader.append("    outNormalTC[gl_InvocationID] = outNormal[gl_InvocationID];\n");
            tessCtrlShader.append("}");

            tessEvalShader.addInclude("tessellationNPatch.glsllib");
            tessEvalShader.addUniform("modelViewProjection", "mat4");
            tessEvalShader.append("void main() {");
            tessEvalShader.append("    ctNorm[0] = outNormalTC[0];");
            tessEvalShader.append("    ctNorm[1] = outNormalTC[1];");
            tessEvalShader.append("    ctNorm[2] = outNormalTC[2];");
            tessEvalShader.append("    ctTangent[0] = outNormalTC[0];"); // we don't care for the tangent
            tessEvalShader.append("    ctTangent[1] = outNormalTC[1];");
            tessEvalShader.append("    ctTangent[2] = outNormalTC[2];");
            tessEvalShader.append("    vec4 pos = tessShader( );\n");
            tessEvalShader.append("    gl_Position = modelViewProjection * pos;\n");
            tessEvalShader.append("}");
            QSSGShaderCacheProgramFlags theFlags(ShaderCacheProgramFlagValues::TessellationEnabled);

            depthShaderProgram = getProgramGenerator()->compileGeneratedShader(name, theFlags, ShaderFeatureSetList());
        } else if (theCache->isShaderCachePersistenceEnabled()) {
            // we load from shader cache set default shader stages
            getProgramGenerator()->beginProgram(
                    QSSGShaderGeneratorStageFlags(QSSGShaderGeneratorStage::Vertex | QSSGShaderGeneratorStage::TessControl
                                               | QSSGShaderGeneratorStage::TessEval | QSSGShaderGeneratorStage::Fragment));
            QSSGShaderCacheProgramFlags theFlags(ShaderCacheProgramFlagValues::TessellationEnabled);

            depthShaderProgram = getProgramGenerator()->compileGeneratedShader(name, theFlags, ShaderFeatureSetList());
        }

        if (depthShaderProgram) {
            m_depthTessNPatchPrepassShader = QSSGRef<QSSGRenderableDepthPrepassShader>(
                    new QSSGRenderableDepthPrepassShader(depthShaderProgram, context()));
        } else {
            m_depthTessNPatchPrepassShader = QSSGRef<QSSGRenderableDepthPrepassShader>();
        }
    }
    return m_depthTessNPatchPrepassShader;
}

QSSGRef<QSSGSkyBoxShader> QSSGRendererImpl::getSkyBoxShader()
{
    if (!m_skyBoxShader) {
        QSSGRef<QSSGShaderCache> theCache = m_contextInterface->shaderCache();
        QByteArray name = "fullscreen skybox shader";
        QSSGRef<QSSGRenderShaderProgram> skyBoxShaderProgram = theCache->getProgram(name, ShaderFeatureSetList());
        if (!skyBoxShaderProgram) {
            QSSGRef<QSSGShaderProgramGeneratorInterface> theGenerator(getProgramGenerator());
            theGenerator->beginProgram();
            QSSGShaderStageGeneratorInterface &vertexGenerator(*theGenerator->getStage(QSSGShaderGeneratorStage::Vertex));
            QSSGShaderStageGeneratorInterface &fragmentGenerator(*theGenerator->getStage(QSSGShaderGeneratorStage::Fragment));

            vertexGenerator.addIncoming("attr_pos", "vec3");

            vertexGenerator.addOutgoing("eye_direction", "vec3");

            vertexGenerator.addUniform("viewMatrix", "mat4");
            vertexGenerator.addUniform("projection", "mat4");

            vertexGenerator.append("void main() {");
            vertexGenerator.append("    gl_Position = vec4(attr_pos, 1.0);");
            vertexGenerator.append("    mat4 inverseProjection = inverse(projection);");
            vertexGenerator.append("    vec3 unprojected = (inverseProjection * gl_Position).xyz;");
            vertexGenerator.append("    eye_direction = normalize(mat3(viewMatrix) * unprojected);");
            vertexGenerator.append("}");

            fragmentGenerator.addInclude("customMaterial.glsllib"); // Needed for PI, PI_TWO

            fragmentGenerator.addUniform("skybox_image", "sampler2D");
            fragmentGenerator.addUniform("output_color", "vec3");

            fragmentGenerator.append("void main() {");

            // Ideally, we would just reuse getProbeSampleUV like this, but that leads to issues
            // with incorrect texture gradients because we're drawing on a quad and not a sphere.
            // See explanation below.
            // fragmentGenerator.addInclude("sampleProbe.glsllib");
            // fragmentGenerator.append("    gl_FragColor = texture2D(skybox_image, getProbeSampleUV(eye, vec4(1.0, 0.0, 0.0, 1.0), vec2(0,0)));");

            // nlerp direction vector, not entirely correct, but simple/efficient
            fragmentGenerator.append("    vec3 eye = normalize(eye_direction);");

            // Equirectangular textures project longitude and latitude to the xy plane
            fragmentGenerator.append("    float longitude = atan(eye.x, eye.z) / PI_TWO + 0.5;");
            fragmentGenerator.append("    float latitude = asin(eye.y) / PI + 0.5;");
            fragmentGenerator.append("    vec2 uv = vec2(longitude, latitude);");

            // Because of the non-standard projection, the texture lookup for normal texture
            // filtering is messed up.
            // TODO: Alternatively, we could check if it's possible to disable some of the texture
            // filtering just for the skybox part.
            fragmentGenerator.append("    vec4 color = textureLod(skybox_image, uv, 0.0);");
            fragmentGenerator.append("    vec3 rgbeColor = color.rgb * pow(2.0, color.a * 255.0 - 128.0);");
            fragmentGenerator.append("    vec3 tonemappedColor = rgbeColor.rgb / (rgbeColor.rgb + vec3(1.0));");
            fragmentGenerator.append("    vec3 gammaCorrectedColor = pow( tonemappedColor, vec3( 1.0 / 2.2 ));");
            fragmentGenerator.append("    gl_FragColor = vec4(gammaCorrectedColor, 1.0);");
            fragmentGenerator.append("}");

            // No flags enabled
            skyBoxShaderProgram = theGenerator->compileGeneratedShader(name, QSSGShaderCacheProgramFlags(), ShaderFeatureSetList());
        }

        if (skyBoxShaderProgram) {
            m_skyBoxShader = QSSGRef<QSSGSkyBoxShader>(
                        new QSSGSkyBoxShader(skyBoxShaderProgram, context()));
        } else {
            m_skyBoxShader = QSSGRef<QSSGSkyBoxShader>();
        }
    }
    return m_skyBoxShader;
}

QSSGRef<QSSGDefaultAoPassShader> QSSGRendererImpl::getDefaultAoPassShader(const ShaderFeatureSetList &inFeatureSet)
{
    if (m_defaultAoPassShader.isNull()) {
        QSSGRef<QSSGShaderCache> theCache = m_contextInterface->shaderCache();
        QByteArray name = "fullscreen AO pass shader";
        QSSGRef<QSSGRenderShaderProgram> aoPassShaderProgram = theCache->getProgram(name, ShaderFeatureSetList());
        if (!aoPassShaderProgram) {
            getProgramGenerator()->beginProgram();
            QSSGShaderStageGeneratorInterface &theVertexGenerator(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Vertex));
            QSSGShaderStageGeneratorInterface &theFragmentGenerator(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Fragment));
            theVertexGenerator.addIncoming("attr_pos", "vec3");
            theVertexGenerator.addIncoming("attr_uv", "vec2");
            theVertexGenerator.addOutgoing("uv_coords", "vec2");
            theVertexGenerator.append("void main() {");
            theVertexGenerator.append("    gl_Position = vec4(attr_pos.xy, 0.5, 1.0 );");
            theVertexGenerator.append("    uv_coords = attr_uv;");
            theVertexGenerator.append("}");

            // fragmentGenerator.AddInclude( "SSAOCustomMaterial.glsllib" );
            theFragmentGenerator.addInclude("viewProperties.glsllib");
            theFragmentGenerator.addInclude("screenSpaceAO.glsllib");
            if (m_context->renderContextType() == QSSGRenderContextType::GLES2) {
                theFragmentGenerator << "    uniform vec4 aoProperties;"
                                     << "\n"
                                     << "    uniform vec4 aoProperties2;"
                                     << "\n"
                                     << "    uniform vec4 shadowProperties;"
                                     << "\n"
                                     << "    uniform vec4 aoScreenConst;"
                                     << "\n"
                                     << "    uniform vec4 uvToEyeConst;"
                                     << "\n";
            } else {
                theFragmentGenerator << "layout (std140) uniform aoShadow { "
                                     << "\n"
                                     << "    vec4 aoProperties;"
                                     << "\n"
                                     << "    vec4 aoProperties2;"
                                     << "\n"
                                     << "    vec4 shadowProperties;"
                                     << "\n"
                                     << "    vec4 aoScreenConst;"
                                     << "\n"
                                     << "    vec4 uvToEyeConst;"
                                     << "\n"
                                     << "};"
                                     << "\n";
            }
            theFragmentGenerator.addUniform("cameraDirection", "vec3");
            theFragmentGenerator.addUniform("depthTexture", "sampler2D");
            theFragmentGenerator.append("void main() {");
            theFragmentGenerator << "    float aoFactor;"
                                 << "\n";
            theFragmentGenerator << "    vec3 screenNorm;"
                                 << "\n";

            // We're taking multiple depth samples and getting the derivatives at each of them
            // to get a more
            // accurate view space normal vector.  When we do only one, we tend to get bizarre
            // values at the edges
            // surrounding objects, and this also ends up giving us weird AO values.
            // If we had a proper screen-space normal map, that would also do the trick.
            if (m_context->renderContextType() == QSSGRenderContextType::GLES2) {
                theFragmentGenerator.addUniform("depthTextureSize", "vec2");
                theFragmentGenerator.append("    ivec2 iCoords = ivec2( gl_FragCoord.xy );");
                theFragmentGenerator.append("    float depth = getDepthValue( "
                                            "texture2D(depthTexture, vec2(iCoords)"
                                            " / depthTextureSize), cameraProperties );");
                theFragmentGenerator.append("    depth = depthValueToLinearDistance( depth, cameraProperties );");
                theFragmentGenerator.append("    depth = (depth - cameraProperties.x) / "
                                            "(cameraProperties.y - cameraProperties.x);");
                theFragmentGenerator.append("    float depth2 = getDepthValue( "
                                            "texture2D(depthTexture, vec2(iCoords+ivec2(1))"
                                            " / depthTextureSize), cameraProperties );");
                theFragmentGenerator.append("    depth2 = depthValueToLinearDistance( depth, cameraProperties );");
                theFragmentGenerator.append("    float depth3 = getDepthValue( "
                                            "texture2D(depthTexture, vec2(iCoords-ivec2(1))"
                                            " / depthTextureSize), cameraProperties );");
            } else {
                theFragmentGenerator.append("    ivec2 iCoords = ivec2( gl_FragCoord.xy );");
                theFragmentGenerator.append("    float depth = getDepthValue( "
                                            "texelFetch(depthTexture, iCoords, 0), "
                                            "cameraProperties );");
                theFragmentGenerator.append("    depth = depthValueToLinearDistance( depth, cameraProperties );");
                theFragmentGenerator.append("    depth = (depth - cameraProperties.x) / "
                                            "(cameraProperties.y - cameraProperties.x);");
                theFragmentGenerator.append("    float depth2 = getDepthValue( "
                                            "texelFetch(depthTexture, iCoords+ivec2(1), 0), "
                                            "cameraProperties );");
                theFragmentGenerator.append("    depth2 = depthValueToLinearDistance( depth, cameraProperties );");
                theFragmentGenerator.append("    float depth3 = getDepthValue( "
                                            "texelFetch(depthTexture, iCoords-ivec2(1), 0), "
                                            "cameraProperties );");
            }
            theFragmentGenerator.append("    depth3 = depthValueToLinearDistance( depth, cameraProperties );");
            theFragmentGenerator.append("    vec3 tanU = vec3(10, 0, dFdx(depth));");
            theFragmentGenerator.append("    vec3 tanV = vec3(0, 10, dFdy(depth));");
            theFragmentGenerator.append("    screenNorm = normalize(cross(tanU, tanV));");
            theFragmentGenerator.append("    tanU = vec3(10, 0, dFdx(depth2));");
            theFragmentGenerator.append("    tanV = vec3(0, 10, dFdy(depth2));");
            theFragmentGenerator.append("    screenNorm += normalize(cross(tanU, tanV));");
            theFragmentGenerator.append("    tanU = vec3(10, 0, dFdx(depth3));");
            theFragmentGenerator.append("    tanV = vec3(0, 10, dFdy(depth3));");
            theFragmentGenerator.append("    screenNorm += normalize(cross(tanU, tanV));");
            theFragmentGenerator.append("    screenNorm = -normalize(screenNorm);");

            theFragmentGenerator.append("    aoFactor = \
                                        SSambientOcclusion( depthTexture, screenNorm, aoProperties, aoProperties2, \
                                                            cameraProperties, aoScreenConst, uvToEyeConst );");

            theFragmentGenerator.append("    gl_FragColor = vec4(aoFactor, aoFactor, aoFactor, 1.0);");

            theFragmentGenerator.append("}");
            aoPassShaderProgram = getProgramGenerator()->compileGeneratedShader(name, QSSGShaderCacheProgramFlags(), inFeatureSet);
        }

        if (aoPassShaderProgram) {
            m_defaultAoPassShader = QSSGRef<QSSGDefaultAoPassShader>(
                    new QSSGDefaultAoPassShader(aoPassShaderProgram, context()));
        } else {
            m_defaultAoPassShader = QSSGRef<QSSGDefaultAoPassShader>();
        }
    }
    return m_defaultAoPassShader;
}

#ifdef QT_QUICK3D_DEBUG_SHADOWS
QSSGRef<QSSGDefaultAoPassShader> QSSGRendererImpl::getDebugDepthShader(ShaderFeatureSetList inFeatureSet)
{
    if (m_debugDepthShader.isNull()) {
        QSSGRef<QSSGShaderCache> theCache = m_contextInterface->shaderCache();
        QByteArray name = "depth display shader";
        QSSGRef<QSSGRenderShaderProgram> depthShaderProgram = theCache->getProgram(name, ShaderFeatureSetList());
        if (!depthShaderProgram) {
            getProgramGenerator()->beginProgram();
            QSSGShaderStageGeneratorInterface &theVertexGenerator(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Vertex));
            QSSGShaderStageGeneratorInterface &theFragmentGenerator(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Fragment));
            theVertexGenerator.addIncoming("attr_pos", "vec3");
            theVertexGenerator.addIncoming("attr_uv", "vec2");
            theVertexGenerator.addOutgoing("uv_coords", "vec2");
            theVertexGenerator.append("void main() {");
            theVertexGenerator.append("\tgl_Position = vec4(attr_pos.xy, 0.5, 1.0);");
            theVertexGenerator.append("\tuv_coords = attr_uv;");
            theVertexGenerator.append("}");

            theFragmentGenerator.addUniform("depthTexture", "sampler2D");
            theFragmentGenerator.append("void main() {");
            theFragmentGenerator.append("\tivec2 iCoords = ivec2(gl_FragCoord.xy);");
            theFragmentGenerator.append("\tfloat depSample = texelFetch(depthTexture, iCoords, 0).x;");
            theFragmentGenerator.append("\tif (depSample <= 0) discard;");
            theFragmentGenerator.append("\tgl_FragColor = vec4(depSample, depSample, depSample, 1.0);");
            theFragmentGenerator.append("\treturn;");
            theFragmentGenerator.append("}");
        }

        depthShaderProgram = getProgramGenerator()->compileGeneratedShader(name, QSSGShaderCacheProgramFlags(), inFeatureSet);

        if (depthShaderProgram) {
            m_debugDepthShader = QSSGRef<QSSGDefaultAoPassShader>(new QSSGDefaultAoPassShader(depthShaderProgram, context()));
        } else {
            m_debugDepthShader = QSSGRef<QSSGDefaultAoPassShader>();
        }
    }
    return m_debugDepthShader;
}

QSSGRef<QSSGDefaultAoPassShader> QSSGRendererImpl::getDebugCubeDepthShader(ShaderFeatureSetList inFeatureSet)
{
    if (!m_debugCubemapDepthShader) {
        QSSGRef<QSSGShaderCache> theCache = m_contextInterface->shaderCache();
        QByteArray name = "cube depth display shader";
        QSSGRef<QSSGRenderShaderProgram> cubeShaderProgram = theCache->getProgram(name, ShaderFeatureSetList());
        if (!cubeShaderProgram) {
            getProgramGenerator()->beginProgram();
            QSSGShaderStageGeneratorInterface &theVertexGenerator(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Vertex));
            QSSGShaderStageGeneratorInterface &theFragmentGenerator(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Fragment));
            theVertexGenerator.addIncoming("attr_pos", "vec3");
            theVertexGenerator.addIncoming("attr_uv", "vec2");
            theVertexGenerator.addOutgoing("sample_dir", "vec3");
            theVertexGenerator.append("void main() {");
            theVertexGenerator.append("\tgl_Position = vec4(attr_pos.xy, 0.5, 1.0);");
            theVertexGenerator.append("\tsample_dir = vec3(4.0 * (attr_uv.x - 0.5), -1.0, 4.0 * (attr_uv.y - 0.5));");
            theVertexGenerator.append("}");
            theFragmentGenerator.addUniform("depthCube", "samplerCube");
            theFragmentGenerator.append("void main() {");
            theFragmentGenerator.append("\tfloat smpDepth = texture(depthCube, sample_dir).x;");
            theFragmentGenerator.append("\tif (smpDepth <= 0) discard;");
            theFragmentGenerator.append("\tgl_FragColor = vec4(smpDepth, smpDepth, smpDepth, 1.0);");
            theFragmentGenerator.append("}");
        }

        cubeShaderProgram = getProgramGenerator()->compileGeneratedShader(name, QSSGShaderCacheProgramFlags(), inFeatureSet);

        if (cubeShaderProgram) {
            m_debugCubemapDepthShader = QSSGRef<QSSGDefaultAoPassShader>(
                    new QSSGDefaultAoPassShader(cubeShaderProgram, context()));
        } else {
            m_debugCubemapDepthShader = QSSGRef<QSSGDefaultAoPassShader>();
        }
    }
    return m_debugCubemapDepthShader;
}
#endif

QSSGRef<QSSGRenderShaderProgram> QSSGRendererImpl::getTextAtlasEntryShader()
{
    getProgramGenerator()->beginProgram();

    QSSGShaderStageGeneratorInterface &vertexGenerator(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Vertex));
    QSSGShaderStageGeneratorInterface &fragmentGenerator(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Fragment));

    vertexGenerator.addIncoming("attr_pos", "vec3");
    vertexGenerator.addIncoming("attr_uv", "vec2");
    vertexGenerator.addUniform("modelViewProjection", "mat4");
    vertexGenerator.addOutgoing("uv_coords", "vec2");
    vertexGenerator.append("void main() {");

    vertexGenerator.append("    gl_Position = modelViewProjection * vec4(attr_pos, 1.0);");
    vertexGenerator.append("    uv_coords = attr_uv;");
    vertexGenerator.append("}");

    fragmentGenerator.addUniform("text_image", "sampler2D");
    fragmentGenerator.append("void main() {");
    fragmentGenerator.append("    float alpha = texture2D( text_image, uv_coords ).a;");
    fragmentGenerator.append("    fragOutput = vec4(alpha, alpha, alpha, alpha);");
    fragmentGenerator.append("}");

    return getProgramGenerator()->compileGeneratedShader("texture atlas entry shader", QSSGShaderCacheProgramFlags(), ShaderFeatureSetList());
}

QSSGRef<QSSGFlippedQuadShader> QSSGRendererImpl::getFlippedQuadShader()
{
    if (m_flippedQuadShader)
        return m_flippedQuadShader;

    QSSGRef<QSSGShaderCache> theCache = m_contextInterface->shaderCache();
    QByteArray name = "flipped quad shader";
    QSSGRef<QSSGRenderShaderProgram> shader = theCache->getProgram(name, ShaderFeatureSetList());
    if (!shader) {
        getProgramGenerator()->beginProgram();

        QSSGShaderStageGeneratorInterface &vertexGenerator(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Vertex));
        QSSGShaderStageGeneratorInterface &fragmentGenerator(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Fragment));

        vertexGenerator.addIncoming("attr_pos", "vec3");
        vertexGenerator.addIncoming("attr_uv", "vec2");
        // xy of text dimensions are scaling factors, zw are offset factors.
        vertexGenerator.addUniform("layer_dimensions", "vec2");
        vertexGenerator.addUniform("modelViewProjection", "mat4");
        vertexGenerator.addOutgoing("uv_coords", "vec2");
        vertexGenerator.append("void main() {");
        vertexGenerator << "    vec3 layerPos = vec3(attr_pos.x * layer_dimensions.x / 2.0"
                        << ", attr_pos.y * layer_dimensions.y / 2.0"
                        << ", attr_pos.z);"
                        << "\n";

        vertexGenerator.append("    gl_Position = modelViewProjection * vec4(layerPos, 1.0);");
        vertexGenerator.append("    uv_coords = vec2(attr_uv.x, 1.0 - attr_uv.y);");
        vertexGenerator.append("}");

        fragmentGenerator.addUniform("layer_image", "sampler2D");
        fragmentGenerator.addUniform("opacity", "float");
        fragmentGenerator.append("void main() {");
        fragmentGenerator.append("    vec2 theCoords = uv_coords;\n");
        fragmentGenerator.append("    vec4 theLayerTexture = texture2D( layer_image, theCoords );\n");
        fragmentGenerator.append("    fragOutput = theLayerTexture * opacity;\n");
        fragmentGenerator.append("}");
        shader = getProgramGenerator()->compileGeneratedShader(name, QSSGShaderCacheProgramFlags(),
                                                               ShaderFeatureSetList());
    }
    QSSGRef<QSSGFlippedQuadShader> retval;
    if (shader)
        retval = QSSGRef<QSSGFlippedQuadShader>(new QSSGFlippedQuadShader(shader));
    m_flippedQuadShader = retval;
    return m_flippedQuadShader;
}

QSSGRef<QSSGLayerProgAABlendShader> QSSGRendererImpl::getLayerProgAABlendShader()
{
    if (m_layerProgAAShader)
        return m_layerProgAAShader;

    QSSGRef<QSSGShaderCache> theCache = m_contextInterface->shaderCache();
    QByteArray name = "layer progressiveAA blend shader";
    QSSGRef<QSSGRenderShaderProgram> shader = theCache->getProgram(name, ShaderFeatureSetList());
    if (!shader) {
        getProgramGenerator()->beginProgram();

        QSSGShaderStageGeneratorInterface &vertexGenerator(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Vertex));
        QSSGShaderStageGeneratorInterface &fragmentGenerator(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Fragment));
        vertexGenerator.addIncoming("attr_pos", "vec3");
        vertexGenerator.addIncoming("attr_uv", "vec2");
        vertexGenerator.addOutgoing("uv_coords", "vec2");
        vertexGenerator.append("void main() {");
        vertexGenerator.append("    gl_Position = vec4(attr_pos, 1.0 );");
        vertexGenerator.append("    uv_coords = attr_uv;");
        vertexGenerator.append("}");
        fragmentGenerator.addUniform("accumulator", "sampler2D");
        fragmentGenerator.addUniform("last_frame", "sampler2D");
        fragmentGenerator.addUniform("blend_factors", "vec2");
        fragmentGenerator.append("void main() {");
        fragmentGenerator.append("    vec4 accum = texture2D( accumulator, uv_coords );");
        fragmentGenerator.append("    vec4 lastFrame = texture2D( last_frame, uv_coords );");
        fragmentGenerator.append("    gl_FragColor = accum*blend_factors.y + lastFrame*blend_factors.x;");
        fragmentGenerator.append("}");
        shader = getProgramGenerator()->compileGeneratedShader(name, QSSGShaderCacheProgramFlags(),
                                                               ShaderFeatureSetList());
    }
    QSSGRef<QSSGLayerProgAABlendShader> retval;
    if (shader)
        retval = QSSGRef<QSSGLayerProgAABlendShader>(new QSSGLayerProgAABlendShader(shader));
    m_layerProgAAShader = retval;
    return m_layerProgAAShader;
}

QSSGRef<QSSGLayerLastFrameBlendShader> QSSGRendererImpl::getLayerLastFrameBlendShader()
{
    if (m_layerLastFrameBlendShader)
        return m_layerLastFrameBlendShader;

    QSSGRef<QSSGShaderCache> theCache = m_contextInterface->shaderCache();
    QByteArray name = "layer last frame blend shader";
    QSSGRef<QSSGRenderShaderProgram> shader = theCache->getProgram(name, ShaderFeatureSetList());
    if (!shader) {
        getProgramGenerator()->beginProgram();

        QSSGShaderStageGeneratorInterface &vertexGenerator(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Vertex));
        QSSGShaderStageGeneratorInterface &fragmentGenerator(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Fragment));
        vertexGenerator.addIncoming("attr_pos", "vec3");
        vertexGenerator.addIncoming("attr_uv", "vec2");
        vertexGenerator.addOutgoing("uv_coords", "vec2");
        vertexGenerator.append("void main() {");
        vertexGenerator.append("    gl_Position = vec4(attr_pos, 1.0);");
        vertexGenerator.append("    uv_coords = attr_uv;");
        vertexGenerator.append("}");
        fragmentGenerator.addUniform("last_frame", "sampler2D");
        fragmentGenerator.addUniform("blend_factor", "float");
        fragmentGenerator.append("void main() {");
        fragmentGenerator.append("    vec4 lastFrame = texture2D(last_frame, uv_coords);");
        fragmentGenerator.append("    gl_FragColor = vec4(lastFrame.rgb*blend_factor, blend_factor);");
        fragmentGenerator.append("}");
        QSSGRef<QSSGRenderShaderProgram>
                theShader = getProgramGenerator()->compileGeneratedShader(name,
                                                                          QSSGShaderCacheProgramFlags(),
                                                                          ShaderFeatureSetList());
    }
    QSSGRef<QSSGLayerLastFrameBlendShader> retval;
    if (shader)
        retval = QSSGRef<QSSGLayerLastFrameBlendShader>(new QSSGLayerLastFrameBlendShader(shader));
    m_layerLastFrameBlendShader = retval;
    return m_layerLastFrameBlendShader;
}

QSSGRef<QSSGCompositShader> QSSGRendererImpl::getCompositShader()
{
    if (m_compositShader)
        return m_compositShader;

    QSSGRef<QSSGShaderCache> theCache = m_contextInterface->shaderCache();
    QByteArray name = "composit shader";
    QSSGRef<QSSGRenderShaderProgram> shader = theCache->getProgram(name, ShaderFeatureSetList());
    if (!shader) {
        getProgramGenerator()->beginProgram();

        QSSGShaderStageGeneratorInterface &vertexGenerator(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Vertex));
        QSSGShaderStageGeneratorInterface &fragmentGenerator(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Fragment));
        vertexGenerator.addIncoming("attr_pos", "vec3");
        vertexGenerator.addIncoming("attr_uv", "vec2");
        vertexGenerator.addOutgoing("uv_coords", "vec2");
        vertexGenerator.append("void main() {");
        vertexGenerator.append("\tgl_Position = vec4(attr_pos, 1.0);");
        vertexGenerator.append("\tuv_coords = attr_uv;");
        vertexGenerator.append("}");
        fragmentGenerator.addUniform("last_frame", "sampler2D");
        fragmentGenerator.append("void main() {");
        fragmentGenerator.append("\tgl_FragColor = texture2D(last_frame, uv_coords);");
        fragmentGenerator.append("}");
        shader = getProgramGenerator()->compileGeneratedShader(name, QSSGShaderCacheProgramFlags(),
                                                               ShaderFeatureSetList());
    }
    QSSGRef<QSSGCompositShader> retval;
    if (shader)
        retval = QSSGRef<QSSGCompositShader>(new QSSGCompositShader(shader));
    m_compositShader = retval;
    return m_compositShader;
}

QSSGRef<QSSGShadowmapPreblurShader> QSSGRendererImpl::getCubeShadowBlurXShader()
{
    if (m_cubeShadowBlurXShader)
        return m_cubeShadowBlurXShader;

    QSSGShaderPreprocessorFeature noFragOutputFeature("NO_FRAG_OUTPUT", true);
    ShaderFeatureSetList features;
    features.push_back(noFragOutputFeature);
    QSSGRef<QSSGShaderCache> theCache = m_contextInterface->shaderCache();
    QByteArray name = "cubemap shadow blur X shader";
    QSSGRef<QSSGRenderShaderProgram> shader = theCache->getProgram(name, features);
    if (!shader) {
        getProgramGenerator()->beginProgram();

        QSSGShaderStageGeneratorInterface &vertexGenerator(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Vertex));
        QSSGShaderStageGeneratorInterface &fragmentGenerator(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Fragment));
        vertexGenerator.addIncoming("attr_pos", "vec3");
        // vertexGenerator.AddIncoming("attr_uv", "vec2");
        vertexGenerator.addOutgoing("uv_coords", "vec2");
        vertexGenerator.append("void main() {");
        vertexGenerator.append("    gl_Position = vec4(attr_pos, 1.0 );");
        vertexGenerator.append("    uv_coords.xy = attr_pos.xy;");
        vertexGenerator.append("}");

        // This with the ShadowBlurYShader design for a 2-pass 5x5 (sigma=1.0)
        // Weights computed using -- http://dev.theomader.com/gaussian-kernel-calculator/
        fragmentGenerator.addUniform("cameraProperties", "vec2");
        fragmentGenerator.addUniform("depthCube", "samplerCube");
        // fragmentGenerator.AddUniform("depthSrc", "sampler2D");
        fragmentGenerator.append("layout(location = 0) out vec4 frag0;");
        fragmentGenerator.append("layout(location = 1) out vec4 frag1;");
        fragmentGenerator.append("layout(location = 2) out vec4 frag2;");
        fragmentGenerator.append("layout(location = 3) out vec4 frag3;");
        fragmentGenerator.append("layout(location = 4) out vec4 frag4;");
        fragmentGenerator.append("layout(location = 5) out vec4 frag5;");
        fragmentGenerator.append("void main() {");
        fragmentGenerator.append("    float ofsScale = cameraProperties.x / 2500.0;");
        fragmentGenerator.append("    vec3 dir0 = vec3(1.0, -uv_coords.y, -uv_coords.x);");
        fragmentGenerator.append("    vec3 dir1 = vec3(-1.0, -uv_coords.y, uv_coords.x);");
        fragmentGenerator.append("    vec3 dir2 = vec3(uv_coords.x, 1.0, uv_coords.y);");
        fragmentGenerator.append("    vec3 dir3 = vec3(uv_coords.x, -1.0, -uv_coords.y);");
        fragmentGenerator.append("    vec3 dir4 = vec3(uv_coords.x, -uv_coords.y, 1.0);");
        fragmentGenerator.append("    vec3 dir5 = vec3(-uv_coords.x, -uv_coords.y, -1.0);");
        fragmentGenerator.append("    float depth0;");
        fragmentGenerator.append("    float depth1;");
        fragmentGenerator.append("    float depth2;");
        fragmentGenerator.append("    float outDepth;");
        fragmentGenerator.append("    depth0 = texture(depthCube, dir0).x;");
        fragmentGenerator.append("    depth1 = texture(depthCube, dir0 + vec3(0.0, 0.0, -ofsScale)).x;");
        fragmentGenerator.append("    depth1 += texture(depthCube, dir0 + vec3(0.0, 0.0, ofsScale)).x;");
        fragmentGenerator.append("    depth2 = texture(depthCube, dir0 + vec3(0.0, 0.0, -2.0*ofsScale)).x;");
        fragmentGenerator.append("    depth2 += texture(depthCube, dir0 + vec3(0.0, 0.0, 2.0*ofsScale)).x;");
        fragmentGenerator.append("    outDepth = 0.38774 * depth0 + 0.24477 * depth1 + 0.06136 * depth2;");
        fragmentGenerator.append("    frag0 = vec4(outDepth);");

        fragmentGenerator.append("    depth0 = texture(depthCube, dir1).x;");
        fragmentGenerator.append("    depth1 = texture(depthCube, dir1 + vec3(0.0, 0.0, -ofsScale)).x;");
        fragmentGenerator.append("    depth1 += texture(depthCube, dir1 + vec3(0.0, 0.0, ofsScale)).x;");
        fragmentGenerator.append("    depth2 = texture(depthCube, dir1 + vec3(0.0, 0.0, -2.0*ofsScale)).x;");
        fragmentGenerator.append("    depth2 += texture(depthCube, dir1 + vec3(0.0, 0.0, 2.0*ofsScale)).x;");
        fragmentGenerator.append("    outDepth = 0.38774 * depth0 + 0.24477 * depth1 + 0.06136 * depth2;");
        fragmentGenerator.append("    frag1 = vec4(outDepth);");

        fragmentGenerator.append("    depth0 = texture(depthCube, dir2).x;");
        fragmentGenerator.append("    depth1 = texture(depthCube, dir2 + vec3(-ofsScale, 0.0, 0.0)).x;");
        fragmentGenerator.append("    depth1 += texture(depthCube, dir2 + vec3(ofsScale, 0.0, 0.0)).x;");
        fragmentGenerator.append("    depth2 = texture(depthCube, dir2 + vec3(-2.0*ofsScale, 0.0, 0.0)).x;");
        fragmentGenerator.append("    depth2 += texture(depthCube, dir2 + vec3(2.0*ofsScale, 0.0, 0.0)).x;");
        fragmentGenerator.append("    outDepth = 0.38774 * depth0 + 0.24477 * depth1 + 0.06136 * depth2;");
        fragmentGenerator.append("    frag2 = vec4(outDepth);");

        fragmentGenerator.append("    depth0 = texture(depthCube, dir3).x;");
        fragmentGenerator.append("    depth1 = texture(depthCube, dir3 + vec3(-ofsScale, 0.0, 0.0)).x;");
        fragmentGenerator.append("    depth1 += texture(depthCube, dir3 + vec3(ofsScale, 0.0, 0.0)).x;");
        fragmentGenerator.append("    depth2 = texture(depthCube, dir3 + vec3(-2.0*ofsScale, 0.0, 0.0)).x;");
        fragmentGenerator.append("    depth2 += texture(depthCube, dir3 + vec3(2.0*ofsScale, 0.0, 0.0)).x;");
        fragmentGenerator.append("    outDepth = 0.38774 * depth0 + 0.24477 * depth1 + 0.06136 * depth2;");
        fragmentGenerator.append("    frag3 = vec4(outDepth);");

        fragmentGenerator.append("    depth0 = texture(depthCube, dir4).x;");
        fragmentGenerator.append("    depth1 = texture(depthCube, dir4 + vec3(-ofsScale, 0.0, 0.0)).x;");
        fragmentGenerator.append("    depth1 += texture(depthCube, dir4 + vec3(ofsScale, 0.0, 0.0)).x;");
        fragmentGenerator.append("    depth2 = texture(depthCube, dir4 + vec3(-2.0*ofsScale, 0.0, 0.0)).x;");
        fragmentGenerator.append("    depth2 += texture(depthCube, dir4 + vec3(2.0*ofsScale, 0.0, 0.0)).x;");
        fragmentGenerator.append("    outDepth = 0.38774 * depth0 + 0.24477 * depth1 + 0.06136 * depth2;");
        fragmentGenerator.append("    frag4 = vec4(outDepth);");

        fragmentGenerator.append("    depth0 = texture(depthCube, dir5).x;");
        fragmentGenerator.append("    depth1 = texture(depthCube, dir5 + vec3(-ofsScale, 0.0, 0.0)).x;");
        fragmentGenerator.append("    depth1 += texture(depthCube, dir5 + vec3(ofsScale, 0.0, 0.0)).x;");
        fragmentGenerator.append("    depth2 = texture(depthCube, dir5 + vec3(-2.0*ofsScale, 0.0, 0.0)).x;");
        fragmentGenerator.append("    depth2 += texture(depthCube, dir5 + vec3(2.0*ofsScale, 0.0, 0.0)).x;");
        fragmentGenerator.append("    outDepth = 0.38774 * depth0 + 0.24477 * depth1 + 0.06136 * depth2;");
        fragmentGenerator.append("    frag5 = vec4(outDepth);");

        fragmentGenerator.append("}");

        shader = getProgramGenerator()->compileGeneratedShader(name, QSSGShaderCacheProgramFlags(),
                                                               features);
    }
    QSSGRef<QSSGShadowmapPreblurShader> retval;
    if (shader)
        retval = QSSGRef<QSSGShadowmapPreblurShader>(new QSSGShadowmapPreblurShader(shader));
    m_cubeShadowBlurXShader = retval;
    return m_cubeShadowBlurXShader;
}

QSSGRef<QSSGShadowmapPreblurShader> QSSGRendererImpl::getCubeShadowBlurYShader()
{
    if (m_cubeShadowBlurYShader)
        return m_cubeShadowBlurYShader;

    QSSGShaderPreprocessorFeature noFragOutputFeature("NO_FRAG_OUTPUT", true);
    ShaderFeatureSetList features;
    features.push_back(noFragOutputFeature);
    QSSGRef<QSSGShaderCache> theCache = m_contextInterface->shaderCache();
    QByteArray name = "cubemap shadow blur Y shader";
    QSSGRef<QSSGRenderShaderProgram> shader = theCache->getProgram(name, features);
    if (!shader) {
        getProgramGenerator()->beginProgram();

        QSSGShaderStageGeneratorInterface &vertexGenerator(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Vertex));
        QSSGShaderStageGeneratorInterface &fragmentGenerator(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Fragment));
        vertexGenerator.addIncoming("attr_pos", "vec3");
        // vertexGenerator.AddIncoming("attr_uv", "vec2");
        vertexGenerator.addOutgoing("uv_coords", "vec2");
        vertexGenerator.append("void main() {");
        vertexGenerator.append("    gl_Position = vec4(attr_pos, 1.0 );");
        vertexGenerator.append("    uv_coords.xy = attr_pos.xy;");
        vertexGenerator.append("}");

        // This with the ShadowBlurXShader design for a 2-pass 5x5 (sigma=1.0)
        // Weights computed using -- http://dev.theomader.com/gaussian-kernel-calculator/
        fragmentGenerator.addUniform("cameraProperties", "vec2");
        fragmentGenerator.addUniform("depthCube", "samplerCube");
        // fragmentGenerator.AddUniform("depthSrc", "sampler2D");
        fragmentGenerator.append("layout(location = 0) out vec4 frag0;");
        fragmentGenerator.append("layout(location = 1) out vec4 frag1;");
        fragmentGenerator.append("layout(location = 2) out vec4 frag2;");
        fragmentGenerator.append("layout(location = 3) out vec4 frag3;");
        fragmentGenerator.append("layout(location = 4) out vec4 frag4;");
        fragmentGenerator.append("layout(location = 5) out vec4 frag5;");
        fragmentGenerator.append("void main() {");
        fragmentGenerator.append("    float ofsScale = cameraProperties.x / 2500.0;");
        fragmentGenerator.append("    vec3 dir0 = vec3(1.0, -uv_coords.y, -uv_coords.x);");
        fragmentGenerator.append("    vec3 dir1 = vec3(-1.0, -uv_coords.y, uv_coords.x);");
        fragmentGenerator.append("    vec3 dir2 = vec3(uv_coords.x, 1.0, uv_coords.y);");
        fragmentGenerator.append("    vec3 dir3 = vec3(uv_coords.x, -1.0, -uv_coords.y);");
        fragmentGenerator.append("    vec3 dir4 = vec3(uv_coords.x, -uv_coords.y, 1.0);");
        fragmentGenerator.append("    vec3 dir5 = vec3(-uv_coords.x, -uv_coords.y, -1.0);");
        fragmentGenerator.append("    float depth0;");
        fragmentGenerator.append("    float depth1;");
        fragmentGenerator.append("    float depth2;");
        fragmentGenerator.append("    float outDepth;");
        fragmentGenerator.append("    depth0 = texture(depthCube, dir0).x;");
        fragmentGenerator.append("    depth1 = texture(depthCube, dir0 + vec3(0.0, -ofsScale, 0.0)).x;");
        fragmentGenerator.append("    depth1 += texture(depthCube, dir0 + vec3(0.0, ofsScale, 0.0)).x;");
        fragmentGenerator.append("    depth2 = texture(depthCube, dir0 + vec3(0.0, -2.0*ofsScale, 0.0)).x;");
        fragmentGenerator.append("    depth2 += texture(depthCube, dir0 + vec3(0.0, 2.0*ofsScale, 0.0)).x;");
        fragmentGenerator.append("    outDepth = 0.38774 * depth0 + 0.24477 * depth1 + 0.06136 * depth2;");
        fragmentGenerator.append("    frag0 = vec4(outDepth);");

        fragmentGenerator.append("    depth0 = texture(depthCube, dir1).x;");
        fragmentGenerator.append("    depth1 = texture(depthCube, dir1 + vec3(0.0, -ofsScale, 0.0)).x;");
        fragmentGenerator.append("    depth1 += texture(depthCube, dir1 + vec3(0.0, ofsScale, 0.0)).x;");
        fragmentGenerator.append("    depth2 = texture(depthCube, dir1 + vec3(0.0, -2.0*ofsScale, 0.0)).x;");
        fragmentGenerator.append("    depth2 += texture(depthCube, dir1 + vec3(0.0, 2.0*ofsScale, 0.0)).x;");
        fragmentGenerator.append("    outDepth = 0.38774 * depth0 + 0.24477 * depth1 + 0.06136 * depth2;");
        fragmentGenerator.append("    frag1 = vec4(outDepth);");

        fragmentGenerator.append("    depth0 = texture(depthCube, dir2).x;");
        fragmentGenerator.append("    depth1 = texture(depthCube, dir2 + vec3(0.0, 0.0, -ofsScale)).x;");
        fragmentGenerator.append("    depth1 += texture(depthCube, dir2 + vec3(0.0, 0.0, ofsScale)).x;");
        fragmentGenerator.append("    depth2 = texture(depthCube, dir2 + vec3(0.0, 0.0, -2.0*ofsScale)).x;");
        fragmentGenerator.append("    depth2 += texture(depthCube, dir2 + vec3(0.0, 0.0, 2.0*ofsScale)).x;");
        fragmentGenerator.append("    outDepth = 0.38774 * depth0 + 0.24477 * depth1 + 0.06136 * depth2;");
        fragmentGenerator.append("    frag2 = vec4(outDepth);");

        fragmentGenerator.append("    depth0 = texture(depthCube, dir3).x;");
        fragmentGenerator.append("    depth1 = texture(depthCube, dir3 + vec3(0.0, 0.0, -ofsScale)).x;");
        fragmentGenerator.append("    depth1 += texture(depthCube, dir3 + vec3(0.0, 0.0, ofsScale)).x;");
        fragmentGenerator.append("    depth2 = texture(depthCube, dir3 + vec3(0.0, 0.0, -2.0*ofsScale)).x;");
        fragmentGenerator.append("    depth2 += texture(depthCube, dir3 + vec3(0.0, 0.0, 2.0*ofsScale)).x;");
        fragmentGenerator.append("    outDepth = 0.38774 * depth0 + 0.24477 * depth1 + 0.06136 * depth2;");
        fragmentGenerator.append("    frag3 = vec4(outDepth);");

        fragmentGenerator.append("    depth0 = texture(depthCube, dir4).x;");
        fragmentGenerator.append("    depth1 = texture(depthCube, dir4 + vec3(0.0, -ofsScale, 0.0)).x;");
        fragmentGenerator.append("    depth1 += texture(depthCube, dir4 + vec3(0.0, ofsScale, 0.0)).x;");
        fragmentGenerator.append("    depth2 = texture(depthCube, dir4 + vec3(0.0, -2.0*ofsScale, 0.0)).x;");
        fragmentGenerator.append("    depth2 += texture(depthCube, dir4 + vec3(0.0, 2.0*ofsScale, 0.0)).x;");
        fragmentGenerator.append("    outDepth = 0.38774 * depth0 + 0.24477 * depth1 + 0.06136 * depth2;");
        fragmentGenerator.append("    frag4 = vec4(outDepth);");

        fragmentGenerator.append("    depth0 = texture(depthCube, dir5).x;");
        fragmentGenerator.append("    depth1 = texture(depthCube, dir5 + vec3(0.0, -ofsScale, 0.0)).x;");
        fragmentGenerator.append("    depth1 += texture(depthCube, dir5 + vec3(0.0, ofsScale, 0.0)).x;");
        fragmentGenerator.append("    depth2 = texture(depthCube, dir5 + vec3(0.0, -2.0*ofsScale, 0.0)).x;");
        fragmentGenerator.append("    depth2 += texture(depthCube, dir5 + vec3(0.0, 2.0*ofsScale, 0.0)).x;");
        fragmentGenerator.append("    outDepth = 0.38774 * depth0 + 0.24477 * depth1 + 0.06136 * depth2;");
        fragmentGenerator.append("    frag5 = vec4(outDepth);");

        fragmentGenerator.append("}");

        shader = getProgramGenerator()->compileGeneratedShader(name, QSSGShaderCacheProgramFlags(),
                                                               features);
    }

    QSSGRef<QSSGShadowmapPreblurShader> retval;
    if (shader)
        retval = QSSGRef<QSSGShadowmapPreblurShader>(new QSSGShadowmapPreblurShader(shader));
    m_cubeShadowBlurYShader = retval;
    return m_cubeShadowBlurYShader;
}

QSSGRef<QSSGShadowmapPreblurShader> QSSGRendererImpl::getOrthoShadowBlurXShader()
{
    if (m_orthoShadowBlurXShader)
        return m_orthoShadowBlurXShader;

    QSSGRef<QSSGShaderCache> theCache = m_contextInterface->shaderCache();
    QByteArray name = "shadow map blur X shader";
    QSSGRef<QSSGRenderShaderProgram> shader = theCache->getProgram(name, ShaderFeatureSetList());
    if (!shader) {
        getProgramGenerator()->beginProgram();

        QSSGShaderStageGeneratorInterface &vertexGenerator(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Vertex));
        QSSGShaderStageGeneratorInterface &fragmentGenerator(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Fragment));
        vertexGenerator.addIncoming("attr_pos", "vec3");
        vertexGenerator.addIncoming("attr_uv", "vec2");
        vertexGenerator.addOutgoing("uv_coords", "vec2");
        vertexGenerator.append("void main() {");
        vertexGenerator.append("    gl_Position = vec4(attr_pos, 1.0 );");
        vertexGenerator.append("    uv_coords.xy = attr_uv.xy;");
        vertexGenerator.append("}");

        fragmentGenerator.addUniform("cameraProperties", "vec2");
        fragmentGenerator.addUniform("depthSrc", "sampler2D");
        fragmentGenerator.append("void main() {");
        fragmentGenerator.append("    vec2 ofsScale = vec2( cameraProperties.x / 7680.0, 0.0 );");
        fragmentGenerator.append("    float depth0 = texture(depthSrc, uv_coords).x;");
        fragmentGenerator.append("    float depth1 = texture(depthSrc, uv_coords + ofsScale).x;");
        fragmentGenerator.append("    depth1 += texture(depthSrc, uv_coords - ofsScale).x;");
        fragmentGenerator.append("    float depth2 = texture(depthSrc, uv_coords + 2.0 * ofsScale).x;");
        fragmentGenerator.append("    depth2 += texture(depthSrc, uv_coords - 2.0 * ofsScale).x;");
        fragmentGenerator.append("    float outDepth = 0.38774 * depth0 + 0.24477 * depth1 + 0.06136 * depth2;");
        fragmentGenerator.append("    fragOutput = vec4(outDepth);");
        fragmentGenerator.append("}");

        shader = getProgramGenerator()->compileGeneratedShader(name, QSSGShaderCacheProgramFlags(),
                                                               ShaderFeatureSetList());
    }
    QSSGRef<QSSGShadowmapPreblurShader> retval;
    if (shader)
        retval = QSSGRef<QSSGShadowmapPreblurShader>(new QSSGShadowmapPreblurShader(shader));
    m_orthoShadowBlurXShader = retval;
    return m_orthoShadowBlurXShader;
}

QSSGRef<QSSGShadowmapPreblurShader> QSSGRendererImpl::getOrthoShadowBlurYShader()
{
    if (m_orthoShadowBlurYShader)
        return m_orthoShadowBlurYShader;

    QSSGRef<QSSGShaderCache> theCache = m_contextInterface->shaderCache();
    QByteArray name = "shadow map blur Y shader";
    QSSGRef<QSSGRenderShaderProgram> shader = theCache->getProgram(name, ShaderFeatureSetList());
    if (!shader) {
        getProgramGenerator()->beginProgram();

        QSSGShaderStageGeneratorInterface &vertexGenerator(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Vertex));
        QSSGShaderStageGeneratorInterface &fragmentGenerator(*getProgramGenerator()->getStage(QSSGShaderGeneratorStage::Fragment));
        vertexGenerator.addIncoming("attr_pos", "vec3");
        vertexGenerator.addIncoming("attr_uv", "vec2");
        vertexGenerator.addOutgoing("uv_coords", "vec2");
        vertexGenerator.append("void main() {");
        vertexGenerator.append("    gl_Position = vec4(attr_pos, 1.0 );");
        vertexGenerator.append("    uv_coords.xy = attr_uv.xy;");
        vertexGenerator.append("}");

        fragmentGenerator.addUniform("cameraProperties", "vec2");
        fragmentGenerator.addUniform("depthSrc", "sampler2D");
        fragmentGenerator.append("void main() {");
        fragmentGenerator.append("    vec2 ofsScale = vec2( 0.0, cameraProperties.x / 7680.0 );");
        fragmentGenerator.append("    float depth0 = texture(depthSrc, uv_coords).x;");
        fragmentGenerator.append("    float depth1 = texture(depthSrc, uv_coords + ofsScale).x;");
        fragmentGenerator.append("    depth1 += texture(depthSrc, uv_coords - ofsScale).x;");
        fragmentGenerator.append("    float depth2 = texture(depthSrc, uv_coords + 2.0 * ofsScale).x;");
        fragmentGenerator.append("    depth2 += texture(depthSrc, uv_coords - 2.0 * ofsScale).x;");
        fragmentGenerator.append("    float outDepth = 0.38774 * depth0 + 0.24477 * depth1 + 0.06136 * depth2;");
        fragmentGenerator.append("    fragOutput = vec4(outDepth);");
        fragmentGenerator.append("}");

        shader = getProgramGenerator()->compileGeneratedShader(name, QSSGShaderCacheProgramFlags(),
                                                               ShaderFeatureSetList());
    }
    QSSGRef<QSSGShadowmapPreblurShader> retval;
    if (shader)
        retval = QSSGRef<QSSGShadowmapPreblurShader>(new QSSGShadowmapPreblurShader(shader));
    m_orthoShadowBlurYShader = retval;
    return m_orthoShadowBlurYShader;
}

QT_END_NAMESPACE
