//--------------------------------------------------------------------------------------------------
/// Draw a background rectangle using OGL 1.1 compatibility
//--------------------------------------------------------------------------------------------------
void InternalLegendRenderTools::renderBackgroundImmediateMode(OpenGLContext* oglContext,
                                                      const Vec2f& size,
                                                      const Color4f& backgroundColor,
                                                      const Color4f& backgroundFrameColor)
{
    RenderStateDepth depth(false);
    depth.applyOpenGL(oglContext);

    RenderStateLighting_FF lighting(false);
    lighting.applyOpenGL(oglContext);

    RenderStateBlending blend;
    blend.configureTransparencyBlending();
    blend.applyOpenGL(oglContext);

    // Frame vertices

    std::array<Vec3f, 4> vertexArray = {
        Vec3f(1       ,        1, 0.0f),
        Vec3f(size.x(),        1, 0.0f),
        Vec3f(size.x(), size.y(), 0.0f),
        Vec3f(1       , size.y(), 0.0f),
    };


    glColor4fv(backgroundColor.ptr());
    glBegin(GL_TRIANGLE_FAN);
    glVertex3fv(vertexArray[0].ptr());
    glVertex3fv(vertexArray[1].ptr());
    glVertex3fv(vertexArray[2].ptr());
    glVertex3fv(vertexArray[3].ptr());
    glEnd();

    // Render Line around

    {
        glColor4fv(backgroundFrameColor.ptr());
        glBegin(GL_LINES);
        glVertex3fv(vertexArray[0].ptr());
        glVertex3fv(vertexArray[1].ptr());
        glVertex3fv(vertexArray[1].ptr());
        glVertex3fv(vertexArray[2].ptr());
        glVertex3fv(vertexArray[2].ptr());
        glVertex3fv(vertexArray[3].ptr());
        glVertex3fv(vertexArray[3].ptr());
        glVertex3fv(vertexArray[0].ptr());
        glEnd();
    }

    // Reset render states

    RenderStateLighting_FF resetLighting;
    resetLighting.applyOpenGL(oglContext);
    RenderStateDepth resetDepth;
    resetDepth.applyOpenGL(oglContext);
    RenderStateBlending resetblend;
    resetblend.applyOpenGL(oglContext);
    CVF_CHECK_OGL(oglContext);
}
//--------------------------------------------------------------------------------------------------
/// 
//--------------------------------------------------------------------------------------------------
void OverlayImage::render(OpenGLContext* oglContext, const Vec2i& position, const Vec2ui& size, bool software)
{
    CVF_CALLSITE_OPENGL(oglContext);

    Camera projCam;
    projCam.setViewport(position.x(), position.y(), size.x(), size.y());
    projCam.setProjectionAsPixelExact2D();
    projCam.setViewMatrix(Mat4d::IDENTITY);

    // Turn off depth test
    RenderStateDepth depth(false, RenderStateDepth::LESS, false);
    depth.applyOpenGL(oglContext);

    float vertexArray[12];
    float textureCoords[] = {0.0f, 0.0f,
                            1.0f, 0.0f,
                            1.0f, 1.0f,
                            0.0f, 1.0f};
    
    projCam.viewport()->applyOpenGL(oglContext, Viewport::DO_NOT_CLEAR);

    if (software)
    {
        // Create a POW2 texture for software rendering if needed
        if (m_image.notNull() && m_pow2Image.isNull() && (!Math::isPow2(m_image->width()) || !Math::isPow2(m_image->height())))
        {
            m_pow2Image = new TextureImage;
            m_pow2Image->allocate(Math::roundUpPow2(m_image->width()), Math::roundUpPow2(m_image->height()));
            m_pow2Image->fill(Color4ub(Color3::BLACK));

            for (uint y = 0; y < m_image->height(); ++y)
            {
                for (uint x = 0; x < m_image->width(); ++x)
                {
                    m_pow2Image->setPixel(x, y, m_image->pixel(x, y));
                }
            }
        }


        if (ShaderProgram::supportedOpenGL(oglContext))
        {
            ShaderProgram::useNoProgram(oglContext);
        }

#ifndef CVF_OPENGL_ES
        RenderStateMaterial_FF mat;
        mat.enableColorMaterial(true);
        mat.applyOpenGL(oglContext);

        RenderStateLighting_FF light(false);
        light.applyOpenGL(oglContext);

        if (m_textureBindings.isNull())
        {
            // Use fixed function texture setup
            ref<Texture2D_FF> texture = new Texture2D_FF(m_pow2Image.notNull() ? m_pow2Image.p() : m_image.p());
            texture->setWrapMode(Texture2D_FF::CLAMP);
            texture->setMinFilter(Texture2D_FF::NEAREST);
            texture->setMagFilter(Texture2D_FF::NEAREST);
            texture->setupTexture(oglContext);
            texture->setupTextureParams(oglContext);

            ref<RenderStateTextureMapping_FF> textureMapping = new RenderStateTextureMapping_FF(texture.p());
            textureMapping->setTextureFunction(m_blendMode == TEXTURE_ALPHA ? RenderStateTextureMapping_FF::MODULATE : RenderStateTextureMapping_FF::DECAL);

            m_textureBindings = textureMapping;
        }
#endif
        // Adjust texture coordinates
        if (m_pow2Image.notNull())
        {
            float xMax = static_cast<float>(m_image->width())/static_cast<float>(m_pow2Image->width());
            float yMax = static_cast<float>(m_image->height())/static_cast<float>(m_pow2Image->height());
            textureCoords[2] = xMax;
            textureCoords[4] = xMax;
            textureCoords[5] = yMax;
            textureCoords[7] = yMax;
        }

        projCam.applyOpenGL();
    }
    else
    {
        glBindBuffer(GL_ARRAY_BUFFER, 0);
        glEnableVertexAttribArray(ShaderProgram::VERTEX);
        glEnableVertexAttribArray(ShaderProgram::TEX_COORD_2F_0);
        glVertexAttribPointer(ShaderProgram::VERTEX, 3, GL_FLOAT, GL_FALSE, 0, vertexArray);
        glVertexAttribPointer(ShaderProgram::TEX_COORD_2F_0, 2, GL_FLOAT, GL_FALSE, 0, textureCoords);

        if (m_shaderProgram.isNull())
        {
            ShaderProgramGenerator gen("OverlayImage_Shader", ShaderSourceProvider::instance());
            gen.addVertexCode(ShaderSourceRepository::vs_MinimalTexture);
            
            if (m_blendMode == GLOBAL_ALPHA)
            {
                gen.addFragmentCode(ShaderSourceRepository::src_TextureGlobalAlpha);
            }
            else
            {
                gen.addFragmentCode(ShaderSourceRepository::src_Texture);
            }

            gen.addFragmentCode(ShaderSourceRepository::fs_Unlit);
            m_shaderProgram = gen.generate();
            m_shaderProgram->linkProgram(oglContext);
        }

        if (m_shaderProgram->useProgram(oglContext))
        {
            MatrixState projMatrixState(projCam);
            m_shaderProgram->clearUniformApplyTracking();
            m_shaderProgram->applyFixedUniforms(oglContext, projMatrixState);
        }

        if (m_texture->textureOglId() == 0)
        {
            m_texture->setupTexture(oglContext);
        }

        if (m_textureBindings.isNull())
        {
            cvf::RenderStateTextureBindings* textureBindings = new cvf::RenderStateTextureBindings;
            textureBindings->addBinding(m_texture.p(), m_sampler.p(), "u_texture2D");
            m_textureBindings = textureBindings;
        }
    }

    float offset = 0.0f;
    Vec3f min(offset, offset, 0.0f);
    Vec3f max(static_cast<float>(size.x()) + offset, static_cast<float>(size.y()) + offset, 0.0f);

    // Setup the vertex array
    float* v1 = &vertexArray[0]; 
    float* v2 = &vertexArray[3];
    float* v3 = &vertexArray[6];
    float* v4 = &vertexArray[9];
    v1[0] = min.x(); v1[1] = min.y(); v1[2] = 0.0f;
    v2[0] = max.x(); v2[1] = min.y(); v2[2] = 0.0f;
    v3[0] = max.x(); v3[1] = max.y(); v3[2] = 0.0f;
    v4[0] = min.x(); v4[1] = max.y(); v4[2] = 0.0f;

    if (m_blendMode != NO_BLENDING)
    {
        RenderStateBlending blend;
        blend.configureTransparencyBlending();
        blend.applyOpenGL(oglContext);
    }

    m_textureBindings->applyOpenGL(oglContext);

    if (software)
    {
#ifndef CVF_OPENGL_ES
        glColor4f(1.0f, 1.0f, 1.0f, m_blendMode == GLOBAL_ALPHA ? m_alpha : 1.0f);
        glBegin(GL_TRIANGLE_FAN);
        glTexCoord2f(textureCoords[0], textureCoords[1]);
        glVertex3fv(v1);
        glTexCoord2f(textureCoords[2], textureCoords[3]);
        glVertex3fv(v2);
        glTexCoord2f(textureCoords[4], textureCoords[5]);
        glVertex3fv(v3);
        glTexCoord2f(textureCoords[6], textureCoords[7]);
        glVertex3fv(v4);
        glEnd();
#endif
    }
    else
    {
        if (m_blendMode == GLOBAL_ALPHA)
        {
            UniformFloat alphaUniform("u_alpha", m_alpha);
            m_shaderProgram->applyUniform(oglContext, alphaUniform);
        }

        glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
    }

    if (m_blendMode != NO_BLENDING)
    {
        RenderStateBlending blend;
        blend.applyOpenGL(oglContext);
    }

    RenderStateDepth resetDepth;
    resetDepth.applyOpenGL(oglContext);

    if (software)
    {
#ifndef CVF_OPENGL_ES
        RenderStateTextureMapping_FF resetTextureMapping;
        resetTextureMapping.applyOpenGL(oglContext);
#endif
    }

    if (!software)
    {
        glDisableVertexAttribArray(ShaderProgram::VERTEX);
        glDisableVertexAttribArray(ShaderProgram::TEX_COORD_2F_0);
    }
}
//--------------------------------------------------------------------------------------------------
/// Render a semi transparent background frame
//--------------------------------------------------------------------------------------------------
void InternalLegendRenderTools::renderBackgroundUsingShaders(OpenGLContext* oglContext,
                                                     const MatrixState& matrixState,
                                                     const Vec2f& size,
                                                     const Color4f& backgroundColor,
                                                     const Color4f& backgroundFrameColor)
{
    CVF_CALLSITE_OPENGL(oglContext);

    RenderStateDepth depth(false);
    depth.applyOpenGL(oglContext);

    RenderStateLine line(1.0f);
    line.applyOpenGL(oglContext);

    RenderStateBlending blend;
    blend.configureTransparencyBlending();
    blend.applyOpenGL(oglContext);

    // Shader program

    ref<ShaderProgram> shaderProgram = oglContext->resourceManager()->getLinkedUnlitColorShaderProgram(oglContext);
    CVF_TIGHT_ASSERT(shaderProgram.notNull());

    if (shaderProgram->useProgram(oglContext))
    {
        shaderProgram->clearUniformApplyTracking();
        shaderProgram->applyFixedUniforms(oglContext, matrixState);
    }

    std::array<Vec3f, 4> vertexArray ={
        Vec3f(1       ,        1, 0.0f),
        Vec3f(size.x(),        1, 0.0f),
        Vec3f(size.x(), size.y(), 0.0f),
        Vec3f(1       , size.y(), 0.0f),
    };


    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glEnableVertexAttribArray(ShaderProgram::VERTEX);
    glVertexAttribPointer(ShaderProgram::VERTEX, 3, GL_FLOAT, GL_FALSE, 0, vertexArray.data());

    // Draw frame background

    UniformFloat backgroundColorUniform("u_color", backgroundColor);
    shaderProgram->applyUniform(oglContext, backgroundColorUniform);

    // Triangle indices for the frame background

    static const ushort backgroundTriangleIndices[] = { 0, 1, 2,  2, 3, 0};

    glDrawRangeElements(GL_TRIANGLES, 0, 3, 6, GL_UNSIGNED_SHORT, backgroundTriangleIndices);


    // Draw frame border lines

    UniformFloat uniformColor("u_color", backgroundFrameColor);
    shaderProgram->applyUniform(oglContext, uniformColor);

    static const ushort frameLineIndices[] = { 0, 1,
                                               1, 2,
                                               2, 3,
                                               3, 0 };

    glDrawRangeElements(GL_LINES, 0, 3, 8, GL_UNSIGNED_SHORT, frameLineIndices);

    glDisableVertexAttribArray(ShaderProgram::VERTEX);

    CVF_TIGHT_ASSERT(shaderProgram.notNull());
    shaderProgram->useNoProgram(oglContext);

    // Reset render states
    RenderStateDepth resetDepth;
    resetDepth.applyOpenGL(oglContext);

    RenderStateLine resetLine;
    resetLine.applyOpenGL(oglContext);

    RenderStateBlending resetblend;
    resetblend.applyOpenGL(oglContext);

    CVF_CHECK_OGL(oglContext);
}
//--------------------------------------------------------------------------------------------------
/// Draw the legend using immediate mode OpenGL
//--------------------------------------------------------------------------------------------------
void OverlayScalarMapperLegend::renderLegendImmediateMode(OpenGLContext* oglContext, OverlayColorLegendLayoutInfo* layout)
{
#ifdef CVF_OPENGL_ES
    CVF_UNUSED(layout);
    CVF_FAIL_MSG("Not supported on OpenGL ES");
#else
    CVF_TIGHT_ASSERT(layout);
    CVF_TIGHT_ASSERT(layout->size.x() > 0);
    CVF_TIGHT_ASSERT(layout->size.y() > 0);

    RenderStateDepth depth(false);
    depth.applyOpenGL(oglContext);

    RenderStateLighting_FF lighting(false);
    lighting.applyOpenGL(oglContext);

    // All vertices. Initialized here to set Z to zero once and for all.
    static float vertexArray[] = 
    {
        0.0f, 0.0f, 0.0f,
        0.0f, 0.0f, 0.0f,
        0.0f, 0.0f, 0.0f,
        0.0f, 0.0f, 0.0f,
        0.0f, 0.0f, 0.0f,
    };

    // Per vector convenience pointers
    float* v0 = &vertexArray[0];    
    float* v1 = &vertexArray[3];    
    float* v2 = &vertexArray[6];    
    float* v3 = &vertexArray[9];    
    float* v4 = &vertexArray[12];   

    // Constant coordinates
    v0[0] = v3[0] = layout->x0;
    v1[0] = v4[0] = layout->x1;

    // Render color bar as one colored quad per pixel

    int legendHeightPixelCount = static_cast<int>(layout->tickPixelPos->get(m_tickValues.size() - 1) - layout->tickPixelPos->get(0) + 0.01);
    if (m_scalarMapper.notNull())
    {
        int iPx;
        for (iPx = 0; iPx < legendHeightPixelCount; iPx++)
        {
            const Color3ub& clr = m_scalarMapper->mapToColor(m_scalarMapper->domainValue((iPx+0.5)/legendHeightPixelCount));
            float y0 = static_cast<float>(layout->legendRect.min().y() + iPx);
            float y1 = static_cast<float>(layout->legendRect.min().y() + iPx + 1);

            // Dynamic coordinates for rectangle
            v0[1] = v1[1] = y0;
            v3[1] = v4[1] = y1;

            // Draw filled rectangle elements
            glColor3ubv(clr.ptr());
            glBegin(GL_TRIANGLE_FAN);
            glVertex3fv(v0);
            glVertex3fv(v1);
            glVertex3fv(v4);
            glVertex3fv(v3);
            glEnd();
        }
    }

    // Render frame

    // Dynamic coordinates for  tickmarks-lines
    bool isRenderingFrame = true;
    if (isRenderingFrame)
    {
        v0[0] = v2[0] = layout->legendRect.min().x()-0.5f;
        v1[0] = v3[0] = layout->legendRect.max().x()-0.5f;
        v0[1] = v1[1] = layout->legendRect.min().y()-0.5f;
        v2[1] = v3[1] = layout->legendRect.max().y()-0.5f;

        glColor3fv(m_color.ptr());
        glBegin(GL_LINES);
        glVertex3fv(v0);
        glVertex3fv(v1);
        glVertex3fv(v1);
        glVertex3fv(v3);
        glVertex3fv(v3);
        glVertex3fv(v2);
        glVertex3fv(v2);
        glVertex3fv(v0);
        glEnd();

    }

    // Render tickmarks
    bool isRenderingTicks = true;

    if (isRenderingTicks)
    {
        // Constant coordinates
        v0[0] = layout->x0;
        v1[0] = layout->x1 - 0.5f*(layout->tickX - layout->x1) - 0.5f;
        v2[0] = layout->x1;
        v3[0] = layout->tickX - 0.5f*(layout->tickX - layout->x1) - 0.5f;
        v4[0] = layout->tickX;

        size_t ic;
        for (ic = 0; ic < m_tickValues.size(); ic++)
        {
            float y0 = static_cast<float>(layout->legendRect.min().y() + layout->tickPixelPos->get(ic) - 0.5f);

            // Dynamic coordinates for  tickmarks-lines
            v0[1] = v1[1] = v2[1] = v3[1] = v4[1] = y0;

            glColor3fv(m_color.ptr());
            glBegin(GL_LINES);
            if ( m_visibleTickLabels[ic])
            {
                glVertex3fv(v0);
                glVertex3fv(v4); 
            }
            else
            {
                glVertex3fv(v2);
                glVertex3fv(v3);
            }
            glEnd();
        }
    }

    // Reset render states
    RenderStateLighting_FF resetLighting;
    resetLighting.applyOpenGL(oglContext);
    RenderStateDepth resetDepth;
    resetDepth.applyOpenGL(oglContext);

    CVF_CHECK_OGL(oglContext);
#endif // CVF_OPENGL_ES
}
//--------------------------------------------------------------------------------------------------
/// Draw the legend using shader programs
//--------------------------------------------------------------------------------------------------
void OverlayScalarMapperLegend::renderLegend(OpenGLContext* oglContext, OverlayColorLegendLayoutInfo* layout, const MatrixState& matrixState)
{
    CVF_CALLSITE_OPENGL(oglContext);

    CVF_TIGHT_ASSERT(layout);
    CVF_TIGHT_ASSERT(layout->size.x() > 0);
    CVF_TIGHT_ASSERT(layout->size.y() > 0);

    RenderStateDepth depth(false);
    depth.applyOpenGL(oglContext);
    RenderStateLine line(static_cast<float>(m_lineWidth));
    line.applyOpenGL(oglContext);

    // All vertices. Initialized here to set Z to zero once and for all.
    static float vertexArray[] = 
    {
        0.0f, 0.0f, 0.0f,
        0.0f, 0.0f, 0.0f,
        0.0f, 0.0f, 0.0f,
        0.0f, 0.0f, 0.0f,
        0.0f, 0.0f, 0.0f
    };

    // Per vector convenience pointers
    float* v0 = &vertexArray[0]; 
    float* v1 = &vertexArray[3]; 
    float* v2 = &vertexArray[6]; 
    float* v3 = &vertexArray[9]; 
    float* v4 = &vertexArray[12];

    // Constant coordinates
    v0[0] = v3[0] = layout->x0;
    v1[0] = v4[0] = layout->x1;

    // Connects
    static const ushort trianglesConnects[] = { 0, 1, 4, 0, 4, 3 };

    ref<ShaderProgram> shaderProgram = oglContext->resourceManager()->getLinkedUnlitColorShaderProgram(oglContext);
    CVF_TIGHT_ASSERT(shaderProgram.notNull());

    if (shaderProgram->useProgram(oglContext))
    {
        shaderProgram->clearUniformApplyTracking();
        shaderProgram->applyFixedUniforms(oglContext, matrixState);
    }

    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glEnableVertexAttribArray(ShaderProgram::VERTEX);
    glVertexAttribPointer(ShaderProgram::VERTEX, 3, GL_FLOAT, GL_FALSE, 0, vertexArray);

    // Render color bar as one colored quad per pixel

    int legendHeightPixelCount = static_cast<int>(layout->tickPixelPos->get(m_tickValues.size()-1) - layout->tickPixelPos->get(0) + 0.01);
    if (m_scalarMapper.notNull())
    {
        int iPx;
        for (iPx = 0; iPx < legendHeightPixelCount; iPx++)
        {
            const Color3ub& clr = m_scalarMapper->mapToColor(m_scalarMapper->domainValue((iPx+0.5)/legendHeightPixelCount));
            float y0 = static_cast<float>(layout->legendRect.min().y() + iPx);
            float y1 = static_cast<float>(layout->legendRect.min().y() + iPx + 1);

            // Dynamic coordinates for rectangle
            v0[1] = v1[1] = y0;
            v3[1] = v4[1] = y1;

            // Draw filled rectangle elements
            {
                UniformFloat uniformColor("u_color", Color4f(Color3f(clr)));
                shaderProgram->applyUniform(oglContext, uniformColor);

#ifdef CVF_OPENGL_ES
                glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, trianglesConnects);
#else
                glDrawRangeElements(GL_TRIANGLES, 0, 4, 6, GL_UNSIGNED_SHORT, trianglesConnects);
#endif
            }
        }
    }

    // Render frame

    // Dynamic coordinates for  tickmarks-lines
    bool isRenderingFrame = true;
    if (isRenderingFrame)
    {
        v0[0] = v2[0] = layout->legendRect.min().x()-0.5f;
        v1[0] = v3[0] = layout->legendRect.max().x()-0.5f;
        v0[1] = v1[1] = layout->legendRect.min().y()-0.5f;
        v2[1] = v3[1] = layout->legendRect.max().y()-0.5f;
        static const ushort frameConnects[] = { 0, 1, 1, 3, 3, 2, 2, 0};

        UniformFloat uniformColor("u_color", Color4f(m_lineColor));
        shaderProgram->applyUniform(oglContext, uniformColor);

#ifdef CVF_OPENGL_ES
        glDrawElements(GL_LINES, 8, GL_UNSIGNED_SHORT, frameConnects);
#else
        glDrawRangeElements(GL_LINES, 0, 3, 8, GL_UNSIGNED_SHORT, frameConnects);
#endif
    }

    // Render tickmarks
    bool isRenderingTicks = true;

    if (isRenderingTicks)
    {
        // Constant coordinates
        v0[0] = layout->x0;
        v1[0] = layout->x1 - 0.5f*(layout->tickX - layout->x1) - 0.5f;
        v2[0] = layout->x1;
        v3[0] = layout->tickX - 0.5f*(layout->tickX - layout->x1) - 0.5f;
        v4[0] = layout->tickX;

        static const ushort tickLinesWithLabel[] = { 0, 4 };
        static const ushort tickLinesWoLabel[] = { 2, 3 };

        size_t ic;
        for (ic = 0; ic < m_tickValues.size(); ic++)
        {
                float y0 = static_cast<float>(layout->legendRect.min().y() + layout->tickPixelPos->get(ic) - 0.5f);

                // Dynamic coordinates for  tickmarks-lines
                v0[1] = v1[1] = v2[1] = v3[1] = v4[1] = y0;

                UniformFloat uniformColor("u_color", Color4f(m_lineColor));
                shaderProgram->applyUniform(oglContext, uniformColor);
                const ushort * linesConnects;

                if ( m_visibleTickLabels[ic])
                {
                    linesConnects = tickLinesWithLabel;
                }
                else
                {
                    linesConnects = tickLinesWoLabel;
                }

#ifdef CVF_OPENGL_ES
                glDrawElements(GL_LINES, 2, GL_UNSIGNED_SHORT, linesConnects);
#else
                glDrawRangeElements(GL_LINES, 0, 4, 2, GL_UNSIGNED_SHORT, linesConnects);
#endif
        }
    }

    glDisableVertexAttribArray(ShaderProgram::VERTEX);

    CVF_TIGHT_ASSERT(shaderProgram.notNull());
    shaderProgram->useNoProgram(oglContext);

    // Reset render states
    RenderStateDepth resetDepth;
    resetDepth.applyOpenGL(oglContext);

    RenderStateLine resetLine;
    resetLine.applyOpenGL(oglContext);

    CVF_CHECK_OGL(oglContext);
}