#include "mythrender_opengl2.h"

#include <QPainter>

#include <cmath>

#define LOC QString("OpenGL2: ")

static inline int __glCheck__(const QString &loc, const char* fileName, int n)
{
    int error = glGetError();
    if (error)
    {
        LOG(VB_GENERAL, LOG_ERR, QString("%1: %2 @ %3, %4")
            .arg(loc).arg(error).arg(fileName).arg(n));
    }
    return error;
}
#define glCheck() __glCheck__(LOC, __FILE__, __LINE__)

#define VERTEX_INDEX  0
#define COLOR_INDEX   1
#define TEXTURE_INDEX 2
#define VERTEX_SIZE   2
#define TEXTURE_SIZE  2

static const GLuint kVertexOffset  = 0;
static const GLuint kTextureOffset = 8 * sizeof(GLfloat);
static const GLuint kVertexSize    = 16 * sizeof(GLfloat);

static const QString kDefaultVertexShader =
"GLSL_DEFINES"
"attribute vec2 a_position;\n"
"attribute vec4 a_color;\n"
"attribute vec2 a_texcoord0;\n"
"varying   vec4 v_color;\n"
"varying   vec2 v_texcoord0;\n"
"uniform   mat4 u_projection;\n"
"uniform   mat4 u_transform;\n"
"void main() {\n"
"    gl_Position = u_projection * u_transform * vec4(a_position, 0.0, 1.0);\n"
"    v_texcoord0 = a_texcoord0;\n"
"    v_color     = a_color;\n"
"}\n";

static const QString kDefaultFragmentShader =
"GLSL_DEFINES"
"uniform GLSL_SAMPLER s_texture0;\n"
"varying vec4 v_color;\n"
"varying vec2 v_texcoord0;\n"
"void main(void)\n"
"{\n"
"    gl_FragColor = GLSL_TEXTURE(s_texture0, v_texcoord0) * v_color;\n"
"}\n";

static const QString kSimpleVertexShader =
"GLSL_DEFINES"
"attribute vec2 a_position;\n"
"attribute vec4 a_color;\n"
"varying   vec4 v_color;\n"
"uniform   mat4 u_projection;\n"
"uniform   mat4 u_transform;\n"
"void main() {\n"
"    gl_Position = u_projection * u_transform * vec4(a_position, 0.0, 1.0);\n"
"    v_color     = a_color;\n"
"}\n";

static const QString kSimpleFragmentShader =
"GLSL_DEFINES"
"varying vec4 v_color;\n"
"void main(void)\n"
"{\n"
"    gl_FragColor = v_color;\n"
"}\n";

static const QString kDrawVertexShader =
"GLSL_DEFINES"
"attribute vec2 a_position;\n"
"attribute vec4 a_color;\n"
"varying   vec4 v_color;\n"
"varying   vec2 v_position;\n"
"uniform   mat4 u_projection;\n"
"uniform   mat4 u_transform;\n"
"void main() {\n"
"    gl_Position = u_projection * u_transform * vec4(a_position, 0.0, 1.0);\n"
"    v_color     = a_color;\n"
"    v_position  = a_position;\n"
"}\n";

static const QString kCircleFragmentShader =
"GLSL_DEFINES"
"varying vec4 v_color;\n"
"varying vec2 v_position;\n"
"uniform mat4 u_parameters;\n"
"void main(void)\n"
"{\n"
"    float dis = distance(v_position.xy, u_parameters[0].xy);\n"
"    float mult = smoothstep(u_parameters[0].z, u_parameters[0].w, dis);\n"
"    gl_FragColor = v_color * vec4(1.0, 1.0, 1.0, mult);\n"
"}\n";

static const QString kCircleEdgeFragmentShader =
"GLSL_DEFINES"
"varying vec4 v_color;\n"
"varying vec2 v_position;\n"
"uniform mat4 u_parameters;\n"
"void main(void)\n"
"{\n"
"    float dis = distance(v_position.xy, u_parameters[0].xy);\n"
"    float rad = u_parameters[0].z;\n"
"    float wid = u_parameters[0].w;\n"
"    float mult = smoothstep(rad + wid, rad + (wid - 1.0), dis) * smoothstep(rad - (wid + 1.0), rad - wid, dis);\n"
"    gl_FragColor = v_color * vec4(1.0, 1.0, 1.0, mult);\n"
"}\n";

static const QString kVertLineFragmentShader =
"GLSL_DEFINES"
"varying vec4 v_color;\n"
"varying vec2 v_position;\n"
"uniform mat4 u_parameters;\n"
"void main(void)\n"
"{\n"
"    float dis = abs(u_parameters[0].x - v_position.x);\n"
"    float y = u_parameters[0].y * 2.0;\n"
"    float mult = smoothstep(y, y - 0.1, dis) * smoothstep(-0.1, 0.0, dis);\n"
"    gl_FragColor = v_color * vec4(1.0, 1.0, 1.0, mult);\n"
"}\n";

static const QString kHorizLineFragmentShader =
"GLSL_DEFINES"
"varying vec4 v_color;\n"
"varying vec2 v_position;\n"
"uniform mat4 u_parameters;\n"
"void main(void)\n"
"{\n"
"    float dis = abs(u_parameters[0].x - v_position.y);\n"
"    float x = u_parameters[0].y * 2.0;\n"
"    float mult = smoothstep(x, x - 0.1, dis) * smoothstep(-0.1, 0.0, dis);\n"
"    gl_FragColor = v_color * vec4(1.0, 1.0, 1.0, mult);\n"
"}\n";

class MythGLShaderObject
{
  public:
    MythGLShaderObject(uint vert, uint frag)
      : m_vertex_shader(vert), m_fragment_shader(frag) { }
    MythGLShaderObject() = default;

    GLuint m_vertex_shader {0};
    GLuint m_fragment_shader {0};
};

MythRenderOpenGL2::MythRenderOpenGL2(const MythRenderFormat& format,
                                     QPaintDevice* device,
                                     RenderType type)
  : MythRenderOpenGL(format, device, type)
{
    ResetVars();
    ResetProcs();
}

MythRenderOpenGL2::MythRenderOpenGL2(const MythRenderFormat& format, RenderType type)
  : MythRenderOpenGL(format, type)
{
    ResetVars();
    ResetProcs();
}

MythRenderOpenGL2::~MythRenderOpenGL2()
{
    if (!isValid())
        return;
    makeCurrent();
    DeleteOpenGLResources();
    doneCurrent();
}

void MythRenderOpenGL2::Init2DState(void)
{
    MythRenderOpenGL::Init2DState();
}

void MythRenderOpenGL2::ResetVars(void)
{
    MythRenderOpenGL::ResetVars();
    m_projection.fill(0);
    m_parameters.fill(0);
    memset(m_shaders, 0, sizeof(m_shaders));
    m_active_obj = 0;
    m_transforms.clear();
    m_transforms.push(QMatrix4x4());
    m_map.clear();
}

void MythRenderOpenGL2::ResetProcs(void)
{
    MythRenderOpenGL::ResetProcs();

    m_glCreateShader = nullptr;
    m_glShaderSource = nullptr;
    m_glCompileShader = nullptr;
    m_glGetShaderiv = nullptr;
    m_glGetShaderInfoLog = nullptr;
    m_glCreateProgram = nullptr;
    m_glAttachShader = nullptr;
    m_glLinkProgram = nullptr;
    m_glUseProgram = nullptr;
    m_glDeleteProgram = nullptr;
    m_glGetProgramInfoLog = nullptr;
    m_glGetProgramiv = nullptr;
    m_glDetachShader = nullptr;
    m_glDeleteShader = nullptr;
    m_glGetUniformLocation = nullptr;
    m_glUniform1i = nullptr;
    m_glUniformMatrix4fv = nullptr;
    m_glVertexAttribPointer = nullptr;
    m_glEnableVertexAttribArray = nullptr;
    m_glDisableVertexAttribArray = nullptr;
    m_glBindAttribLocation = nullptr;
    m_glVertexAttrib4f = nullptr;
}

bool MythRenderOpenGL2::InitFeatures(void)
{
    m_exts_supported = kGLFeatNone;

    static bool glslshaders = true;
    static bool check       = true;
    if (check)
    {
        check = false;
        glslshaders = !getenv("OPENGL_NOGLSL");
        if (!glslshaders)
            LOG(VB_GENERAL, LOG_INFO, LOC + "Disabling GLSL.");
    }

    // These should all be present for a valid OpenGL2.0/ES installation
    if (m_glShaderSource  && m_glCreateShader &&
        m_glCompileShader && m_glGetShaderiv &&
        m_glGetShaderInfoLog &&
        m_glCreateProgram &&
        m_glAttachShader  && m_glLinkProgram &&
        m_glUseProgram    && m_glGetProgramInfoLog &&
        m_glDetachShader  && m_glGetProgramiv &&
        m_glDeleteShader  && m_glGetUniformLocation &&
        m_glUniform1i     && m_glUniformMatrix4fv &&
        m_glVertexAttribPointer &&
        m_glEnableVertexAttribArray &&
        m_glDisableVertexAttribArray &&
        m_glBindAttribLocation &&
        m_glVertexAttrib4f && glslshaders)
    {
        LOG(VB_GENERAL, LOG_INFO, LOC + "GLSL supported");
        m_exts_supported += kGLSL;
    }

    MythRenderOpenGL::InitFeatures();

    // After rect texture support
    if (m_exts_supported & kGLSL)
    {
        DeleteDefaultShaders();
        CreateDefaultShaders();
    }

    return true;
}

void MythRenderOpenGL2::InitProcs(void)
{
    MythRenderOpenGL::InitProcs();

    // GLSL version
    m_GLSLVersion = "#version 110\n";
    m_qualifiers = QString();

    m_glCreateShader = (MYTH_GLCREATESHADERPROC)
        GetProcAddress("glCreateShader");
    m_glShaderSource = (MYTH_GLSHADERSOURCEPROC)
        GetProcAddress("glShaderSource");
    m_glCompileShader = (MYTH_GLCOMPILESHADERPROC)
        GetProcAddress("glCompileShader");
    m_glGetShaderiv = (MYTH_GLGETSHADERIVPROC)
        GetProcAddress("glGetShaderiv");
    m_glGetShaderInfoLog = (MYTH_GLGETSHADERINFOLOGPROC)
        GetProcAddress("glGetShaderInfoLog");
    m_glDeleteProgram = (MYTH_GLDELETEPROGRAMPROC)
        GetProcAddress("glDeleteProgram");
    m_glCreateProgram = (MYTH_GLCREATEPROGRAMPROC)
        GetProcAddress("glCreateProgram");
    m_glAttachShader = (MYTH_GLATTACHSHADERPROC)
        GetProcAddress("glAttachShader");
    m_glLinkProgram = (MYTH_GLLINKPROGRAMPROC)
        GetProcAddress("glLinkProgram");
    m_glUseProgram = (MYTH_GLUSEPROGRAMPROC)
        GetProcAddress("glUseProgram");
    m_glGetProgramInfoLog = (MYTH_GLGETPROGRAMINFOLOGPROC)
        GetProcAddress("glGetProgramInfoLog");
    m_glGetProgramiv = (MYTH_GLGETPROGRAMIVPROC)
        GetProcAddress("glGetProgramiv");
    m_glDetachShader = (MYTH_GLDETACHSHADERPROC)
        GetProcAddress("glDetachShader");
    m_glDeleteShader = (MYTH_GLDELETESHADERPROC)
        GetProcAddress("glDeleteShader");
    m_glGetUniformLocation = (MYTH_GLGETUNIFORMLOCATIONPROC)
        GetProcAddress("glGetUniformLocation");
    m_glUniform1i = (MYTH_GLUNIFORM1IPROC)
        GetProcAddress("glUniform1i");
    m_glUniformMatrix4fv = (MYTH_GLUNIFORMMATRIX4FVPROC)
        GetProcAddress("glUniformMatrix4fv");
    m_glVertexAttribPointer = (MYTH_GLVERTEXATTRIBPOINTERPROC)
        GetProcAddress("glVertexAttribPointer");
    m_glEnableVertexAttribArray = (MYTH_GLENABLEVERTEXATTRIBARRAYPROC)
        GetProcAddress("glEnableVertexAttribArray");
    m_glDisableVertexAttribArray = (MYTH_GLDISABLEVERTEXATTRIBARRAYPROC)
        GetProcAddress("glDisableVertexAttribArray");
    m_glBindAttribLocation = (MYTH_GLBINDATTRIBLOCATIONPROC)
        GetProcAddress("glBindAttribLocation");
    m_glVertexAttrib4f = (MYTH_GLVERTEXATTRIB4FPROC)
        GetProcAddress("glVertexAttrib4f");
}

inline void MythRenderOpenGL2::m_glVertexAttribPointerI(
    GLuint index, GLint size, GLenum type, GLboolean normalize,
    GLsizei stride, const GLuint value)
{
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wint-to-pointer-cast"
    m_glVertexAttribPointer(index, size, type, normalize,
                            stride, (const char *)value);
#pragma GCC diagnostic pop
}

uint MythRenderOpenGL2::CreateShaderObject(const QString &vertex,
                                          const QString &fragment)
{
    if (!(m_exts_supported & kGLSL))
        return 0;

    OpenGLLocker locker(this);

    uint result = 0;
    QString vert_shader = vertex.isEmpty() ? kDefaultVertexShader : vertex;
    QString frag_shader = fragment.isEmpty() ? kDefaultFragmentShader: fragment;

    OptimiseShaderSource(vert_shader);
    OptimiseShaderSource(frag_shader);

    result = m_glCreateProgram();
    if (!result)
        return 0;

    MythGLShaderObject object(CreateShader(GL_VERTEX_SHADER, vert_shader),
                              CreateShader(GL_FRAGMENT_SHADER, frag_shader));
    m_shader_objects.insert(result, object);

    if (!ValidateShaderObject(result))
    {
        DeleteShaderObject(result);
        return 0;
    }

    return result;
}

void MythRenderOpenGL2::DeleteShaderObject(uint obj)
{
    OpenGLLocker locker(this);

    if (!m_shader_objects.contains(obj))
        return;

    GLuint vertex   = m_shader_objects[obj].m_vertex_shader;
    GLuint fragment = m_shader_objects[obj].m_fragment_shader;
    m_glDetachShader(obj, vertex);
    m_glDetachShader(obj, fragment);
    m_glDeleteShader(vertex);
    m_glDeleteShader(fragment);
    m_glDeleteProgram(obj);
    m_shader_objects.remove(obj);
    m_map.clear();

    Flush(true);
}

void MythRenderOpenGL2::EnableShaderObject(uint obj)
{
    OpenGLLocker locker(this);

    if (obj == m_active_obj)
        return;

    if (!obj && m_active_obj)
    {
        m_glUseProgram(0);
        m_active_obj = 0;
        return;
    }

    if (!m_shader_objects.contains(obj))
        return;

    m_glUseProgram(obj);
    m_active_obj = obj;
}

void MythRenderOpenGL2::SetShaderParams(uint obj, const QMatrix4x4 &m,
                                        const char* uniform)
{
    if (!(m_exts_supported & kGLSL))
        return;

    OpenGLLocker locker(this);

    EnableShaderObject(obj);

    QString tag = QString("%1-%2").arg(obj).arg(uniform);
    map_t::iterator it = m_map.find(tag);
    if (it == m_map.end())
        m_map.insert(tag, m);
    else if (!qFuzzyCompare(m, it.value()))
        it.value() = m;
    else
        return;
    GLint loc = m_glGetUniformLocation(obj, uniform);
    if (loc != -1)
        m_glUniformMatrix4fv(loc, 1, GL_FALSE, GLMatrix4x4(m));
}

void MythRenderOpenGL2::DrawBitmapPriv(uint tex, const QRect *src,
                                       const QRect *dst, uint prog, int alpha,
                                       int red, int green, int blue)
{
    if (prog && !m_shader_objects.contains(prog))
        prog = 0;
    if (prog == 0)
        prog = m_shaders[kShaderDefault];

    SetShaderParams(prog, m_projection, "u_projection");
    SetShaderParams(prog, m_transforms.top(), "u_transform");
    SetBlend(true);

    EnableTextures(tex);

    GLint loc = m_glGetUniformLocation(prog, "s_texture0");
    if (loc != -1)
        m_glUniform1i(loc, 0);

    ActiveTexture(GL_TEXTURE0);
    glBindTexture(m_textures[tex].m_type, tex);

    m_glBindBuffer(GL_ARRAY_BUFFER, m_textures[tex].m_vbo);
    UpdateTextureVertices(tex, src, dst);
    if (m_exts_used & kGLExtPBufObj)
    {
        m_glBufferData(GL_ARRAY_BUFFER, kVertexSize, nullptr, GL_STREAM_DRAW);
        void* target = m_glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
        if (target)
            memcpy(target, m_textures[tex].m_vertex_data, kVertexSize);
        m_glUnmapBuffer(GL_ARRAY_BUFFER);
    }
    else
    {
        m_glBufferData(GL_ARRAY_BUFFER, kVertexSize, m_textures[tex].m_vertex_data, GL_STREAM_DRAW);
    }

    m_glEnableVertexAttribArray(VERTEX_INDEX);
    m_glEnableVertexAttribArray(TEXTURE_INDEX);

    m_glVertexAttribPointerI(VERTEX_INDEX, VERTEX_SIZE, GL_FLOAT, GL_FALSE,
                            VERTEX_SIZE * sizeof(GLfloat),
                            kVertexOffset);
    m_glVertexAttrib4f(COLOR_INDEX, red / 255.0, green / 255.0, blue / 255.0, alpha / 255.0);
    m_glVertexAttribPointerI(TEXTURE_INDEX, TEXTURE_SIZE, GL_FLOAT, GL_FALSE,
                            TEXTURE_SIZE * sizeof(GLfloat),
                            kTextureOffset);

    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    glCheck();

    m_glDisableVertexAttribArray(TEXTURE_INDEX);
    m_glDisableVertexAttribArray(VERTEX_INDEX);
    m_glBindBuffer(GL_ARRAY_BUFFER, 0);
}

void MythRenderOpenGL2::DrawBitmapPriv(uint *textures, uint texture_count,
                                       const QRectF *src, const QRectF *dst,
                                       uint prog)
{
    if (prog && !m_shader_objects.contains(prog))
        prog = 0;
    if (prog == 0)
        prog = m_shaders[kShaderDefault];

    uint first = textures[0];

    SetShaderParams(prog, m_projection, "u_projection");
    SetShaderParams(prog, m_transforms.top(), "u_transform");
    SetBlend(false);

    EnableTextures(first);
    uint active_tex = 0;
    for (uint i = 0; i < texture_count; i++)
    {
        if (m_textures.contains(textures[i]))
        {
            QString uniform = QString("s_texture%1").arg(active_tex);
            GLint loc = m_glGetUniformLocation(prog, qPrintable(uniform));
            if (loc != -1)
                m_glUniform1i(loc, active_tex);

            ActiveTexture(GL_TEXTURE0 + active_tex++);
            glBindTexture(m_textures[textures[i]].m_type, textures[i]);
        }
    }

    m_glBindBuffer(GL_ARRAY_BUFFER, m_textures[first].m_vbo);
    UpdateTextureVertices(first, src, dst);
    if (m_exts_used & kGLExtPBufObj)
    {
        m_glBufferData(GL_ARRAY_BUFFER, kVertexSize, nullptr, GL_STREAM_DRAW);
        void* target = m_glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
        if (target)
            memcpy(target, m_textures[first].m_vertex_data, kVertexSize);
        m_glUnmapBuffer(GL_ARRAY_BUFFER);
    }
    else
    {
        m_glBufferData(GL_ARRAY_BUFFER, kVertexSize, m_textures[first].m_vertex_data, GL_STREAM_DRAW);
    }

    m_glEnableVertexAttribArray(VERTEX_INDEX);
    m_glEnableVertexAttribArray(TEXTURE_INDEX);

    m_glVertexAttribPointerI(VERTEX_INDEX, VERTEX_SIZE, GL_FLOAT, GL_FALSE,
                            VERTEX_SIZE * sizeof(GLfloat),
                            kVertexOffset);
    m_glVertexAttrib4f(COLOR_INDEX, 1.0, 1.0, 1.0, 1.0);
    m_glVertexAttribPointerI(TEXTURE_INDEX, TEXTURE_SIZE, GL_FLOAT, GL_FALSE,
                            TEXTURE_SIZE * sizeof(GLfloat),
                            kTextureOffset);

    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

    m_glDisableVertexAttribArray(TEXTURE_INDEX);
    m_glDisableVertexAttribArray(VERTEX_INDEX);
    m_glBindBuffer(GL_ARRAY_BUFFER, 0);
}

void MythRenderOpenGL2::DrawRectPriv(const QRect &area, const QBrush &fillBrush,
                                     const QPen &linePen, int alpha)
{
    DrawRoundRectPriv(area, 1, fillBrush, linePen, alpha);
}

void MythRenderOpenGL2::DrawRoundRectPriv(const QRect &area, int cornerRadius,
                                          const QBrush &fillBrush,
                                          const QPen &linePen, int alpha)
{
    int lineWidth = linePen.width();
    int halfline  = lineWidth / 2;
    int rad = cornerRadius - halfline;

    if ((area.width() / 2) < rad)
        rad = area.width() / 2;

    if ((area.height() / 2) < rad)
        rad = area.height() / 2;
    int dia = rad * 2;

    QRect r(area.left(), area.top(), area.width(), area.height());

    QRect tl(r.left(),  r.top(), rad, rad);
    QRect tr(r.left() + r.width() - rad, r.top(), rad, rad);
    QRect bl(r.left(),  r.top() + r.height() - rad, rad, rad);
    QRect br(r.left() + r.width() - rad, r.top() + r.height() - rad, rad, rad);

    SetBlend(true);
    DisableTextures();

    m_glEnableVertexAttribArray(VERTEX_INDEX);

    if (fillBrush.style() != Qt::NoBrush)
    {
        // Get the shaders
        int elip = m_shaders[kShaderCircle];
        int fill = m_shaders[kShaderSimple];

        // Set the fill color
        m_glVertexAttrib4f(COLOR_INDEX,
                           fillBrush.color().red() / 255.0,
                           fillBrush.color().green() / 255.0,
                           fillBrush.color().blue() / 255.0,
                          (fillBrush.color().alpha() / 255.0) * (alpha / 255.0));

        // Set the radius
        m_parameters(2,0) = rad;
        m_parameters(3,0) = rad - 1.0;

        // Enable the Circle shader
        SetShaderParams(elip, m_projection, "u_projection");
        SetShaderParams(elip, m_transforms.top(), "u_transform");

        // Draw the top left segment
        m_parameters(0,0) = tl.left() + rad;
        m_parameters(1,0) = tl.top() + rad;

        SetShaderParams(elip, m_parameters, "u_parameters");
        GetCachedVBO(GL_TRIANGLE_STRIP, tl);
        m_glVertexAttribPointerI(VERTEX_INDEX, VERTEX_SIZE, GL_FLOAT, GL_FALSE,
                                VERTEX_SIZE * sizeof(GLfloat),
                               kVertexOffset);
        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

        // Draw the top right segment
        m_parameters(0,0) = tr.left();
        m_parameters(1,0) = tr.top() + rad;
        SetShaderParams(elip, m_parameters, "u_parameters");
        GetCachedVBO(GL_TRIANGLE_STRIP, tr);
        m_glVertexAttribPointerI(VERTEX_INDEX, VERTEX_SIZE, GL_FLOAT, GL_FALSE,
                                VERTEX_SIZE * sizeof(GLfloat),
                               kVertexOffset);
        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

        // Draw the bottom left segment
        m_parameters(0,0) = bl.left() + rad;
        m_parameters(1,0) = bl.top();
        SetShaderParams(elip, m_parameters, "u_parameters");
        GetCachedVBO(GL_TRIANGLE_STRIP, bl);
        m_glVertexAttribPointerI(VERTEX_INDEX, VERTEX_SIZE, GL_FLOAT, GL_FALSE,
                                VERTEX_SIZE * sizeof(GLfloat),
                               kVertexOffset);
        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

        // Draw the bottom right segment
        m_parameters(0,0) = br.left();
        m_parameters(1,0) = br.top();
        SetShaderParams(elip, m_parameters, "u_parameters");
        GetCachedVBO(GL_TRIANGLE_STRIP, br);
        m_glVertexAttribPointerI(VERTEX_INDEX, VERTEX_SIZE, GL_FLOAT, GL_FALSE,
                                VERTEX_SIZE * sizeof(GLfloat),
                               kVertexOffset);
        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

        // Fill the remaining areas
        QRect main(r.left() + rad, r.top(), r.width() - dia, r.height());
        QRect left(r.left(), r.top() + rad, rad, r.height() - dia);
        QRect right(r.left() + r.width() - rad, r.top() + rad, rad, r.height() - dia);

        SetShaderParams(fill, m_projection, "u_projection");
        SetShaderParams(fill, m_transforms.top(), "u_transform");

        GetCachedVBO(GL_TRIANGLE_STRIP, main);
        m_glVertexAttribPointerI(VERTEX_INDEX, VERTEX_SIZE, GL_FLOAT, GL_FALSE,
                                VERTEX_SIZE * sizeof(GLfloat),
                               kVertexOffset);
        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
        GetCachedVBO(GL_TRIANGLE_STRIP, left);
        m_glVertexAttribPointerI(VERTEX_INDEX, VERTEX_SIZE, GL_FLOAT, GL_FALSE,
                                VERTEX_SIZE * sizeof(GLfloat),
                               kVertexOffset);
        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
        GetCachedVBO(GL_TRIANGLE_STRIP, right);
        m_glVertexAttribPointerI(VERTEX_INDEX, VERTEX_SIZE, GL_FLOAT, GL_FALSE,
                                VERTEX_SIZE * sizeof(GLfloat),
                               kVertexOffset);
        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
        m_glBindBuffer(GL_ARRAY_BUFFER, 0);
    }

    if (linePen.style() != Qt::NoPen)
    {
        // Get the shaders
        int edge = m_shaders[kShaderCircleEdge];
        int vline = m_shaders[kShaderVertLine];
        int hline = m_shaders[kShaderHorizLine];

        // Set the line color
        m_glVertexAttrib4f(COLOR_INDEX,
                           linePen.color().red() / 255.0,
                           linePen.color().green() / 255.0,
                           linePen.color().blue() / 255.0,
                          (linePen.color().alpha() / 255.0) * (alpha / 255.0));

        // Set the radius and width
        m_parameters(2,0) = rad - lineWidth / 2.0;
        m_parameters(3,0) = lineWidth / 2.0;

        // Enable the edge shader
        SetShaderParams(edge, m_projection, "u_projection");
        SetShaderParams(edge, m_transforms.top(), "u_transform");

        // Draw the top left edge segment
        m_parameters(0,0) = tl.left() + rad;
        m_parameters(1,0) = tl.top() + rad;
        SetShaderParams(edge, m_parameters, "u_parameters");
        GetCachedVBO(GL_TRIANGLE_STRIP, tl);
        m_glVertexAttribPointerI(VERTEX_INDEX, VERTEX_SIZE, GL_FLOAT, GL_FALSE,
                                VERTEX_SIZE * sizeof(GLfloat),
                               kVertexOffset);
        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

        // Draw the top right edge segment
        m_parameters(0,0) = tr.left();
        m_parameters(1,0) = tr.top() + rad;
        SetShaderParams(edge, m_parameters, "u_parameters");
        GetCachedVBO(GL_TRIANGLE_STRIP, tr);
        m_glVertexAttribPointerI(VERTEX_INDEX, VERTEX_SIZE, GL_FLOAT, GL_FALSE,
                                VERTEX_SIZE * sizeof(GLfloat),
                               kVertexOffset);
        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

        // Draw the bottom left edge segment
        m_parameters(0,0) = bl.left() + rad;
        m_parameters(1,0) = bl.top();
        SetShaderParams(edge, m_parameters, "u_parameters");
        GetCachedVBO(GL_TRIANGLE_STRIP, bl);
        m_glVertexAttribPointerI(VERTEX_INDEX, VERTEX_SIZE, GL_FLOAT, GL_FALSE,
                                VERTEX_SIZE * sizeof(GLfloat),
                               kVertexOffset);
        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

        // Draw the bottom right edge segment
        m_parameters(0,0) = br.left();
        m_parameters(1,0) = br.top();
        SetShaderParams(edge, m_parameters, "u_parameters");
        GetCachedVBO(GL_TRIANGLE_STRIP, br);
        m_glVertexAttribPointerI(VERTEX_INDEX, VERTEX_SIZE, GL_FLOAT, GL_FALSE,
                                VERTEX_SIZE * sizeof(GLfloat),
                               kVertexOffset);
        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

        // Vertical lines
        SetShaderParams(vline, m_projection, "u_projection");
        SetShaderParams(vline, m_transforms.top(), "u_transform");

        m_parameters(1,0) = lineWidth / 2.0;
        QRect vl(r.left(), r.top() + rad,
                 lineWidth, r.height() - dia);

        // Draw the left line segment
        m_parameters(0,0) = vl.left() + lineWidth;
        SetShaderParams(vline, m_parameters, "u_parameters");
        GetCachedVBO(GL_TRIANGLE_STRIP, vl);
        m_glVertexAttribPointerI(VERTEX_INDEX, VERTEX_SIZE, GL_FLOAT, GL_FALSE,
                                VERTEX_SIZE * sizeof(GLfloat),
                               kVertexOffset);
        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

        // Draw the right line segment
        vl.translate(r.width() - lineWidth, 0);
        m_parameters(0,0) = vl.left();
        SetShaderParams(vline, m_parameters, "u_parameters");
        GetCachedVBO(GL_TRIANGLE_STRIP, vl);
        m_glVertexAttribPointerI(VERTEX_INDEX, VERTEX_SIZE, GL_FLOAT, GL_FALSE,
                                VERTEX_SIZE * sizeof(GLfloat),
                               kVertexOffset);
        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

        // Horizontal lines
        SetShaderParams(hline, m_projection, "u_projection");
        SetShaderParams(hline, m_transforms.top(), "u_transform");
        QRect hl(r.left() + rad, r.top(),
                 r.width() - dia, lineWidth);

        // Draw the top line segment
        m_parameters(0,0) = hl.top() + lineWidth;
        SetShaderParams(hline, m_parameters, "u_parameters");
        GetCachedVBO(GL_TRIANGLE_STRIP, hl);
        m_glVertexAttribPointerI(VERTEX_INDEX, VERTEX_SIZE, GL_FLOAT, GL_FALSE,
                                VERTEX_SIZE * sizeof(GLfloat),
                               kVertexOffset);
        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

        // Draw the bottom line segment
        hl.translate(0, r.height() - lineWidth);
        m_parameters(0,0) = hl.top();
        SetShaderParams(hline, m_parameters, "u_parameters");
        GetCachedVBO(GL_TRIANGLE_STRIP, hl);
        m_glVertexAttribPointerI(VERTEX_INDEX, VERTEX_SIZE, GL_FLOAT, GL_FALSE,
                                VERTEX_SIZE * sizeof(GLfloat),
                               kVertexOffset);
        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

        m_glBindBuffer(GL_ARRAY_BUFFER, 0);
    }

    m_glDisableVertexAttribArray(VERTEX_INDEX);
}

void MythRenderOpenGL2::CreateDefaultShaders(void)
{
    m_shaders[kShaderSimple]  = CreateShaderObject(kSimpleVertexShader,
                                                   kSimpleFragmentShader);
    m_shaders[kShaderDefault] = CreateShaderObject(kDefaultVertexShader,
                                                   kDefaultFragmentShader);
    m_shaders[kShaderCircle]  = CreateShaderObject(kDrawVertexShader,
                                                   kCircleFragmentShader);
    m_shaders[kShaderCircleEdge] = CreateShaderObject(kDrawVertexShader,
                                                   kCircleEdgeFragmentShader);
    m_shaders[kShaderVertLine]   = CreateShaderObject(kDrawVertexShader,
                                                   kVertLineFragmentShader);
    m_shaders[kShaderHorizLine]  = CreateShaderObject(kDrawVertexShader,
                                                   kHorizLineFragmentShader);
}

void MythRenderOpenGL2::DeleteDefaultShaders(void)
{
    for (int i = 0; i < kShaderCount; i++)
    {
        DeleteShaderObject(m_shaders[i]);
        m_shaders[i] = 0;
    }
}

uint MythRenderOpenGL2::CreateShader(int type, const QString &source)
{
    uint result = m_glCreateShader(type);
    QByteArray src = source.toLatin1();
    const char* tmp[1] = { src.constData() };
    m_glShaderSource(result, 1, tmp, nullptr);
    m_glCompileShader(result);
    GLint compiled;
    m_glGetShaderiv(result, GL_COMPILE_STATUS, &compiled);
    if (!compiled)
    {
        GLint length = 0;
        m_glGetShaderiv(result, GL_INFO_LOG_LENGTH, &length);
        if (length > 1)
        {
            char *log = (char*)malloc(sizeof(char) * length);
            m_glGetShaderInfoLog(result, length, nullptr, log);
            LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to compile shader.");
            LOG(VB_GENERAL, LOG_ERR, log);
            LOG(VB_GENERAL, LOG_ERR, source);
            free(log);
        }
        m_glDeleteShader(result);
        result = 0;
    }
    return result;
}

bool MythRenderOpenGL2::ValidateShaderObject(uint obj)
{
    if (!m_shader_objects.contains(obj))
        return false;
    if (!m_shader_objects[obj].m_fragment_shader ||
        !m_shader_objects[obj].m_vertex_shader)
        return false;

    m_glAttachShader(obj, m_shader_objects[obj].m_fragment_shader);
    m_glAttachShader(obj, m_shader_objects[obj].m_vertex_shader);
    m_glBindAttribLocation(obj, VERTEX_INDEX,  "a_position");
    m_glBindAttribLocation(obj, COLOR_INDEX,   "a_color");
    m_glBindAttribLocation(obj, TEXTURE_INDEX, "a_texcoord0");
    m_glLinkProgram(obj);
    return CheckObjectStatus(obj);
}

bool MythRenderOpenGL2::CheckObjectStatus(uint obj)
{
    int ok;
#ifdef GL_LINK_STATUS
    m_glGetProgramiv(obj, GL_LINK_STATUS, &ok);
#else
    m_glGetProgramiv(obj, GL_OBJECT_LINK_STATUS, &ok);
#endif
    if (ok > 0)
        return true;

    LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to link shader object.");
    int infologLength = 0;
    int charsWritten  = 0;
    char *infoLog;
#ifdef GL_INFO_LOG_LENGTH
    m_glGetProgramiv(obj, GL_INFO_LOG_LENGTH, &infologLength);
#else
    m_glGetProgramiv(obj, GL_OBJECT_INFO_LOG_LENGTH, &infologLength);
#endif
    if (infologLength > 0)
    {
        infoLog = (char *)malloc(infologLength);
        m_glGetProgramInfoLog(obj, infologLength, &charsWritten, infoLog);
        LOG(VB_GENERAL, LOG_ERR, QString("\n\n%1").arg(infoLog));
        free(infoLog);
    }
    return false;
}

void MythRenderOpenGL2::OptimiseShaderSource(QString &source)
{
    QString extensions = "";
    QString sampler = "sampler2D";
    QString texture = "texture2D";

    if ((m_exts_used & kGLExtRect) && source.contains("GLSL_SAMPLER"))
    {
        extensions += "#extension GL_ARB_texture_rectangle : enable\n";
        sampler += "Rect";
        texture += "Rect";
    }

    source.replace("GLSL_SAMPLER", sampler);
    source.replace("GLSL_TEXTURE", texture);
    source.replace("GLSL_DEFINES", m_GLSLVersion + extensions + m_qualifiers);

    LOG(VB_GENERAL, LOG_DEBUG, "\n" + source);
}

void MythRenderOpenGL2::DeleteOpenGLResources(void)
{
    LOG(VB_GENERAL, LOG_INFO, LOC + "Deleting OpenGL Resources");
    DeleteDefaultShaders();
    DeleteShaders();
    MythRenderOpenGL::DeleteOpenGLResources();
}

void MythRenderOpenGL2::SetMatrixView(void)
{
    m_projection.setToIdentity();
    m_projection.ortho(m_viewport);
}

void MythRenderOpenGL2::PushTransformation(const UIEffects &fx, QPointF &center)
{
    QMatrix4x4 newtop = m_transforms.top();
    if (fx.hzoom != 1.0f || fx.vzoom != 1.0f || fx.angle != 0.0f)
    {
        newtop.translate(center.x(), center.y());
        newtop.scale(fx.hzoom, fx.vzoom);
        newtop.rotate(fx.angle, 0, 0, 1);
        newtop.translate(-center.x(), -center.y());
    }
    m_transforms.push(newtop);
}

void MythRenderOpenGL2::PopTransformation(void)
{
    m_transforms.pop();
}

void MythRenderOpenGL2::DeleteShaders(void)
{
    QHash<GLuint, MythGLShaderObject>::iterator it;
    for (it = m_shader_objects.begin(); it != m_shader_objects.end(); ++it)
    {
        GLuint object   = it.key();
        GLuint vertex   = it.value().m_vertex_shader;
        GLuint fragment = it.value().m_fragment_shader;
        m_glDetachShader(object, vertex);
        m_glDetachShader(object, fragment);
        m_glDeleteShader(vertex);
        m_glDeleteShader(fragment);
        m_glDeleteProgram(object);
    }
    m_shader_objects.clear();
    m_map.clear();
    Flush(true);
}
