/*
    Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). <qt-info@nokia.com>
    Copyright (C) 2011-2012 Collabora Ltd. <info@collabora.com>

    This library is free software; you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License version 2.1
    as published by the Free Software Foundation.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "openglsurfacepainter.h"
#include <QtCore/qmath.h>
#include <QOpenGLContext>
#include <QPainter>

#ifndef GL_TEXTURE0
#  define GL_TEXTURE0    0x84C0
#  define GL_TEXTURE1    0x84C1
#  define GL_TEXTURE2    0x84C2
#endif

#ifndef GL_PROGRAM_ERROR_STRING_ARB
#  define GL_PROGRAM_ERROR_STRING_ARB       0x8874
#endif

#ifndef GL_UNSIGNED_SHORT_5_6_5
#  define GL_UNSIGNED_SHORT_5_6_5 33635
#endif

#ifndef GL_CLAMP_TO_EDGE
#  define GL_CLAMP_TO_EDGE 0x812F
#endif

#define QRECT_TO_GLMATRIX(rect) \
    { \
        GLfloat(rect.left())     , GLfloat(rect.bottom() + 1), \
        GLfloat(rect.right() + 1), GLfloat(rect.bottom() + 1), \
        GLfloat(rect.left())     , GLfloat(rect.top()), \
        GLfloat(rect.right() + 1), GLfloat(rect.top()) \
    }

OpenGLSurfacePainter::OpenGLSurfacePainter()
    : m_textureFormat(0)
    , m_textureInternalFormat(0)
    , m_textureType(0)
    , m_textureCount(0)
    , m_videoColorMatrix(GST_VIDEO_COLOR_MATRIX_UNKNOWN)
{
#ifndef QT_OPENGL_ES
    glActiveTexture = (_glActiveTexture) QOpenGLContext::currentContext()->getProcAddress("glActiveTexture");
#endif
}

//static
QSet<GstVideoFormat> OpenGLSurfacePainter::supportedPixelFormats()
{
    return QSet<GstVideoFormat>()
        //also handled by the generic painter on LE
        << GST_VIDEO_FORMAT_BGRA
        << GST_VIDEO_FORMAT_BGRx

        //also handled by the generic painter on BE
        << GST_VIDEO_FORMAT_ARGB
        << GST_VIDEO_FORMAT_xRGB

        //also handled by the generic painter everywhere
        << GST_VIDEO_FORMAT_RGB
        << GST_VIDEO_FORMAT_RGB16

        //not handled by the generic painter
        << GST_VIDEO_FORMAT_BGR
        << GST_VIDEO_FORMAT_v308
        << GST_VIDEO_FORMAT_AYUV
        << GST_VIDEO_FORMAT_YV12
        << GST_VIDEO_FORMAT_I420
        ;
}

void OpenGLSurfacePainter::updateColors(int brightness, int contrast, int hue, int saturation)
{
    const qreal b = brightness / 200.0;
    const qreal c = contrast / 100.0 + 1.0;
    const qreal h = hue / 100.0;
    const qreal s = saturation / 100.0 + 1.0;

    const qreal cosH = qCos(M_PI * h);
    const qreal sinH = qSin(M_PI * h);

    const qreal h11 =  0.787 * cosH - 0.213 * sinH + 0.213;
    const qreal h21 = -0.213 * cosH + 0.143 * sinH + 0.213;
    const qreal h31 = -0.213 * cosH - 0.787 * sinH + 0.213;

    const qreal h12 = -0.715 * cosH - 0.715 * sinH + 0.715;
    const qreal h22 =  0.285 * cosH + 0.140 * sinH + 0.715;
    const qreal h32 = -0.715 * cosH + 0.715 * sinH + 0.715;

    const qreal h13 = -0.072 * cosH + 0.928 * sinH + 0.072;
    const qreal h23 = -0.072 * cosH - 0.283 * sinH + 0.072;
    const qreal h33 =  0.928 * cosH + 0.072 * sinH + 0.072;

    const qreal sr = (1.0 - s) * 0.3086;
    const qreal sg = (1.0 - s) * 0.6094;
    const qreal sb = (1.0 - s) * 0.0820;

    const qreal sr_s = sr + s;
    const qreal sg_s = sg + s;
    const qreal sb_s = sr + s;

    const float m4 = (s + sr + sg + sb) * (0.5 - 0.5 * c + b);

    m_colorMatrix(0, 0) = c * (sr_s * h11 + sg * h21 + sb * h31);
    m_colorMatrix(0, 1) = c * (sr_s * h12 + sg * h22 + sb * h32);
    m_colorMatrix(0, 2) = c * (sr_s * h13 + sg * h23 + sb * h33);
    m_colorMatrix(0, 3) = m4;

    m_colorMatrix(1, 0) = c * (sr * h11 + sg_s * h21 + sb * h31);
    m_colorMatrix(1, 1) = c * (sr * h12 + sg_s * h22 + sb * h32);
    m_colorMatrix(1, 2) = c * (sr * h13 + sg_s * h23 + sb * h33);
    m_colorMatrix(1, 3) = m4;

    m_colorMatrix(2, 0) = c * (sr * h11 + sg * h21 + sb_s * h31);
    m_colorMatrix(2, 1) = c * (sr * h12 + sg * h22 + sb_s * h32);
    m_colorMatrix(2, 2) = c * (sr * h13 + sg * h23 + sb_s * h33);
    m_colorMatrix(2, 3) = m4;

    m_colorMatrix(3, 0) = 0.0;
    m_colorMatrix(3, 1) = 0.0;
    m_colorMatrix(3, 2) = 0.0;
    m_colorMatrix(3, 3) = 1.0;

    switch (m_videoColorMatrix) {
#if 0
    //I have no idea what this is - it's not needed currently in this code
    case BufferFormat::YCbCr_JPEG:
        m_colorMatrix *= QMatrix4x4(
                    1.0,  0.000,  1.402, -0.701,
                    1.0, -0.344, -0.714,  0.529,
                    1.0,  1.772,  0.000, -0.886,
                    0.0,  0.000,  0.000,  1.0000);
        break;
#endif
    case GST_VIDEO_COLOR_MATRIX_BT709:
        m_colorMatrix *= QMatrix4x4(
                    1.164,  0.000,  1.793, -0.5727,
                    1.164, -0.534, -0.213,  0.3007,
                    1.164,  2.115,  0.000, -1.1302,
                    0.0,    0.000,  0.000,  1.0000);
        break;
    case GST_VIDEO_COLOR_MATRIX_BT601:
        m_colorMatrix *= QMatrix4x4(
                    1.164,  0.000,  1.596, -0.8708,
                    1.164, -0.392, -0.813,  0.5296,
                    1.164,  2.017,  0.000, -1.081,
                    0.0,    0.000,  0.000,  1.0000);
        break;
    default:
        break;
    }
}

void OpenGLSurfacePainter::paint(quint8 *data,
        const BufferFormat & /*frameFormat*/,
        QPainter *painter,
        const PaintAreas & areas)
{
    // if these are enabled, we need to reenable them after beginNativePainting()
    // has been called, as they may get disabled
    bool stencilTestEnabled = glIsEnabled(GL_STENCIL_TEST);
    bool scissorTestEnabled = glIsEnabled(GL_SCISSOR_TEST);

    painter->beginNativePainting();

    if (stencilTestEnabled)
        glEnable(GL_STENCIL_TEST);
    if (scissorTestEnabled)
        glEnable(GL_SCISSOR_TEST);

    const GLfloat vertexCoordArray[] = QRECT_TO_GLMATRIX(areas.videoArea);

    const GLfloat txLeft = areas.sourceRect.left();
    const GLfloat txRight = areas.sourceRect.right();
    const GLfloat txTop = areas.sourceRect.top();
    const GLfloat txBottom = areas.sourceRect.bottom();

    const GLfloat textureCoordArray[] =
    {
        txLeft , txBottom,
        txRight, txBottom,
        txLeft , txTop,
        txRight, txTop
    };

    for (int i = 0; i < m_textureCount; ++i) {
        glBindTexture(GL_TEXTURE_2D, m_textureIds[i]);
        glTexImage2D(
                GL_TEXTURE_2D,
                0,
                m_textureInternalFormat,
                m_textureWidths[i],
                m_textureHeights[i],
                0,
                m_textureFormat,
                m_textureType,
                data + m_textureOffsets[i]);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    }

    paintImpl(painter, vertexCoordArray, textureCoordArray);

    painter->endNativePainting();
    painter->fillRect(areas.blackArea1, Qt::black);
    painter->fillRect(areas.blackArea2, Qt::black);
}

void OpenGLSurfacePainter::initRgbTextureInfo(
        GLenum internalFormat, GLuint format, GLenum type, const QSize &size)
{
#ifndef QT_OPENGL_ES
    //make sure we get 8 bits per component, at least on the desktop GL where we can
    switch(internalFormat) {
    case GL_RGBA:
        internalFormat = GL_RGBA8;
        break;
    case GL_RGB:
        internalFormat = GL_RGB8;
        break;
    default:
        break;
    }
#endif

    m_textureInternalFormat = internalFormat;
    m_textureFormat = format;
    m_textureType = type;
    m_textureCount = 1;
    m_textureWidths[0] = size.width();
    m_textureHeights[0] = size.height();
    m_textureOffsets[0] = 0;
}

void OpenGLSurfacePainter::initYuv420PTextureInfo(const QSize &size)
{
    int bytesPerLine = (size.width() + 3) & ~3;
    int bytesPerLine2 = (size.width() / 2 + 3) & ~3;

    m_textureInternalFormat = GL_LUMINANCE;
    m_textureFormat = GL_LUMINANCE;
    m_textureType = GL_UNSIGNED_BYTE;
    m_textureCount = 3;
    m_textureWidths[0] = bytesPerLine;
    m_textureHeights[0] = size.height();
    m_textureOffsets[0] = 0;
    m_textureWidths[1] = bytesPerLine2;
    m_textureHeights[1] = size.height() / 2;
    m_textureOffsets[1] = bytesPerLine * size.height();
    m_textureWidths[2] = bytesPerLine2;
    m_textureHeights[2] = size.height() / 2;
    m_textureOffsets[2] = bytesPerLine * size.height() + bytesPerLine2 * size.height()/2;
}

void OpenGLSurfacePainter::initYv12TextureInfo(const QSize &size)
{
    int bytesPerLine = (size.width() + 3) & ~3;
    int bytesPerLine2 = (size.width() / 2 + 3) & ~3;

    m_textureInternalFormat = GL_LUMINANCE;
    m_textureFormat = GL_LUMINANCE;
    m_textureType = GL_UNSIGNED_BYTE;
    m_textureCount = 3;
    m_textureWidths[0] = bytesPerLine;
    m_textureHeights[0] = size.height();
    m_textureOffsets[0] = 0;
    m_textureWidths[1] = bytesPerLine2;
    m_textureHeights[1] = size.height() / 2;
    m_textureOffsets[1] = bytesPerLine * size.height() + bytesPerLine2 * size.height()/2;
    m_textureWidths[2] = bytesPerLine2;
    m_textureHeights[2] = size.height() / 2;
    m_textureOffsets[2] = bytesPerLine * size.height();
}

#ifndef QT_OPENGL_ES

# ifndef GL_FRAGMENT_PROGRAM_ARB
#  define GL_FRAGMENT_PROGRAM_ARB           0x8804
#  define GL_PROGRAM_FORMAT_ASCII_ARB       0x8875
# endif

// Interprets the RGBA texture as in fact being BGRx and paints it.
static const char *qt_arbfp_bgrxShaderProgram =
    "!!ARBfp1.0\n"
    "PARAM matrix[4] = { program.local[0..2],"
    "{ 0.0, 0.0, 0.0, 1.0 } };\n"
    "TEMP bgrx;\n"
    "TEX bgrx.xyz, fragment.texcoord[0], texture[0], 2D;\n"
    "MOV bgrx.w, matrix[3].w;\n"
    "DP4 result.color.x, bgrx.zyxw, matrix[0];\n"
    "DP4 result.color.y, bgrx.zyxw, matrix[1];\n"
    "DP4 result.color.z, bgrx.zyxw, matrix[2];\n"
    "END";

// Interprets the RGBA texture as in fact being BGRA and paints it.
static const char *qt_arbfp_bgraShaderProgram =
    "!!ARBfp1.0\n"
    "PARAM matrix[4] = { program.local[0..2],"
    "{ 0.0, 0.0, 0.0, 1.0 } };\n"
    "TEMP bgra;\n"
    "TEX bgra, fragment.texcoord[0], texture[0], 2D;\n"
    "MOV bgra.w, matrix[3].w;\n"
    "DP4 result.color.x, bgra.zyxw, matrix[0];\n"
    "DP4 result.color.y, bgra.zyxw, matrix[1];\n"
    "DP4 result.color.z, bgra.zyxw, matrix[2];\n"
    "TEX result.color.w, fragment.texcoord[0], texture, 2D;\n"
    "END";

// Interprets the RGBA texture as in fact being xRGB and paints it.
static const char *qt_arbfp_xrgbShaderProgram =
    "!!ARBfp1.0\n"
    "PARAM matrix[4] = { program.local[0..2],"
    "{ 0.0, 0.0, 0.0, 1.0 } };\n"
    "TEMP xrgb;\n"
    "TEX xrgb, fragment.texcoord[0], texture[0], 2D;\n"
    "MOV xrgb.x, matrix[3].w;\n"
    "DP4 result.color.x, xrgb.yzwx, matrix[0];\n"
    "DP4 result.color.y, xrgb.yzwx, matrix[1];\n"
    "DP4 result.color.z, xrgb.yzwx, matrix[2];\n"
    "END";

// Interprets the RGBA texture as in fact being ARGB and paints it.
static const char *qt_arbfp_argbShaderProgram =
    "!!ARBfp1.0\n"
    "PARAM matrix[4] = { program.local[0..2],"
    "{ 0.0, 0.0, 0.0, 1.0 } };\n"
    "TEMP argb;\n"
    "TEX argb, fragment.texcoord[0], texture[0], 2D;\n"
    "MOV argb.x, matrix[3].w;\n"
    "DP4 result.color.x, argb.yzwx, matrix[0];\n"
    "DP4 result.color.y, argb.yzwx, matrix[1];\n"
    "DP4 result.color.z, argb.yzwx, matrix[2];\n"
    "TEX result.color.w, fragment.texcoord[0], texture, 2D;\n"
    "END";

// Paints RGB frames without doing any color channel flipping.
static const char *qt_arbfp_rgbxShaderProgram =
    "!!ARBfp1.0\n"
    "PARAM matrix[4] = { program.local[0..2],"
    "{ 0.0, 0.0, 0.0, 1.0 } };\n"
    "TEMP rgb;\n"
    "TEX rgb.xyz, fragment.texcoord[0], texture[0], 2D;\n"
    "MOV rgb.w, matrix[3].w;\n"
    "DP4 result.color.x, rgb, matrix[0];\n"
    "DP4 result.color.y, rgb, matrix[1];\n"
    "DP4 result.color.z, rgb, matrix[2];\n"
    "END";

// Paints a YUV420P or YV12 frame.
static const char *qt_arbfp_yuvPlanarShaderProgram =
    "!!ARBfp1.0\n"
    "PARAM matrix[4] = { program.local[0..2],"
    "{ 0.0, 0.0, 0.0, 1.0 } };\n"
    "TEMP yuv;\n"
    "TEX yuv.x, fragment.texcoord[0], texture[0], 2D;\n"
    "TEX yuv.y, fragment.texcoord[0], texture[1], 2D;\n"
    "TEX yuv.z, fragment.texcoord[0], texture[2], 2D;\n"
    "MOV yuv.w, matrix[3].w;\n"
    "DP4 result.color.x, yuv, matrix[0];\n"
    "DP4 result.color.y, yuv, matrix[1];\n"
    "DP4 result.color.z, yuv, matrix[2];\n"
    "END";



ArbFpSurfacePainter::ArbFpSurfacePainter()
    : OpenGLSurfacePainter()
    , m_programId(0)
{
    const QOpenGLContext *context = QOpenGLContext::currentContext();

    glProgramStringARB = (_glProgramStringARB) context->getProcAddress("glProgramStringARB");
    glBindProgramARB = (_glBindProgramARB) context->getProcAddress("glBindProgramARB");
    glDeleteProgramsARB = (_glDeleteProgramsARB) context->getProcAddress("glDeleteProgramsARB");
    glGenProgramsARB = (_glGenProgramsARB) context->getProcAddress("glGenProgramsARB");
    glProgramLocalParameter4fARB = (_glProgramLocalParameter4fARB) context->getProcAddress("glProgramLocalParameter4fARB");
}

void ArbFpSurfacePainter::init(const BufferFormat &format)
{
    Q_ASSERT(m_textureCount == 0);

    const char *program = 0;

    switch (format.videoFormat()) {
    case GST_VIDEO_FORMAT_BGRx:
        initRgbTextureInfo(GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, format.frameSize());
        program = qt_arbfp_bgrxShaderProgram;
        break;
    case GST_VIDEO_FORMAT_xRGB:
        initRgbTextureInfo(GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, format.frameSize());
        program = qt_arbfp_xrgbShaderProgram;
        break;
    case GST_VIDEO_FORMAT_BGRA:
        initRgbTextureInfo(GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, format.frameSize());
        program = qt_arbfp_bgraShaderProgram;
        break;
    case GST_VIDEO_FORMAT_ARGB:
        initRgbTextureInfo(GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, format.frameSize());
        program = qt_arbfp_argbShaderProgram;
        break;
    case GST_VIDEO_FORMAT_RGB:
        initRgbTextureInfo(GL_RGB, GL_RGB, GL_UNSIGNED_BYTE, format.frameSize());
        program = qt_arbfp_rgbxShaderProgram;
        break;
    case GST_VIDEO_FORMAT_BGR:
        initRgbTextureInfo(GL_RGB, GL_RGB, GL_UNSIGNED_BYTE, format.frameSize());
        program = qt_arbfp_bgrxShaderProgram;
        break;
    //NOTE: unlike the other formats, this is endianness-dependent,
    //but using GL_UNSIGNED_SHORT_5_6_5 ensures that it's handled correctly
    case GST_VIDEO_FORMAT_RGB16:
        initRgbTextureInfo(GL_RGB, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, format.frameSize());
        program = qt_arbfp_rgbxShaderProgram;
        break;
    case GST_VIDEO_FORMAT_v308:
        initRgbTextureInfo(GL_RGB, GL_RGB, GL_UNSIGNED_BYTE, format.frameSize());
        program = qt_arbfp_rgbxShaderProgram;
        break;
    case GST_VIDEO_FORMAT_AYUV:
        initRgbTextureInfo(GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, format.frameSize());
        program = qt_arbfp_argbShaderProgram;
        break;
    case GST_VIDEO_FORMAT_YV12:
        initYv12TextureInfo(format.frameSize());
        program = qt_arbfp_yuvPlanarShaderProgram;
        break;
    case GST_VIDEO_FORMAT_I420:
        initYuv420PTextureInfo(format.frameSize());
        program = qt_arbfp_yuvPlanarShaderProgram;
        break;
    default:
        Q_ASSERT(false);
        break;
    }

    m_videoColorMatrix = format.colorMatrix();

    glGenProgramsARB(1, &m_programId);

    GLenum glError = glGetError();
    if (glError != GL_NO_ERROR) {
        throw QString("ARBfb Shader allocation error ") +
            QString::number(static_cast<int>(glError), 16);
    } else {
        glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, m_programId);
        glProgramStringARB(
                GL_FRAGMENT_PROGRAM_ARB,
                GL_PROGRAM_FORMAT_ASCII_ARB,
                qstrlen(program),
                reinterpret_cast<const GLvoid *>(program));

        if ((glError = glGetError()) != GL_NO_ERROR) {
            const GLubyte* errorString = glGetString(GL_PROGRAM_ERROR_STRING_ARB);

            glDeleteProgramsARB(1, &m_programId);
            m_textureCount = 0;
            m_programId = 0;

            throw QString("ARBfp Shader compile error ") +
                QString::number(static_cast<int>(glError), 16) +
                reinterpret_cast<const char *>(errorString);
        } else {
            glGenTextures(m_textureCount, m_textureIds);
        }
    }
}

void ArbFpSurfacePainter::cleanup()
{
    glDeleteTextures(m_textureCount, m_textureIds);
    glDeleteProgramsARB(1, &m_programId);

    m_textureCount = 0;
    m_programId = 0;
}

void ArbFpSurfacePainter::paintImpl(const QPainter *painter,
        const GLfloat *vertexCoordArray,
        const GLfloat *textureCoordArray)
{
    Q_UNUSED(painter);

    glEnable(GL_FRAGMENT_PROGRAM_ARB);
    glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, m_programId);

    glProgramLocalParameter4fARB(
            GL_FRAGMENT_PROGRAM_ARB,
            0,
            m_colorMatrix(0, 0),
            m_colorMatrix(0, 1),
            m_colorMatrix(0, 2),
            m_colorMatrix(0, 3));
    glProgramLocalParameter4fARB(
            GL_FRAGMENT_PROGRAM_ARB,
            1,
            m_colorMatrix(1, 0),
            m_colorMatrix(1, 1),
            m_colorMatrix(1, 2),
            m_colorMatrix(1, 3));
    glProgramLocalParameter4fARB(
            GL_FRAGMENT_PROGRAM_ARB,
            2,
            m_colorMatrix(2, 0),
            m_colorMatrix(2, 1),
            m_colorMatrix(2, 2),
            m_colorMatrix(2, 3));

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, m_textureIds[0]);

    if (m_textureCount == 3) {
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, m_textureIds[1]);
        glActiveTexture(GL_TEXTURE2);
        glBindTexture(GL_TEXTURE_2D, m_textureIds[2]);
        glActiveTexture(GL_TEXTURE0);
    }

    glVertexPointer(2, GL_FLOAT, 0, vertexCoordArray);
    glTexCoordPointer(2, GL_FLOAT, 0, textureCoordArray);

    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);

    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    glDisable(GL_FRAGMENT_PROGRAM_ARB);
}

#endif

static const char *qt_glsl_vertexShaderProgram =
        "attribute highp vec4 vertexCoordArray;\n"
        "attribute highp vec2 textureCoordArray;\n"
        "uniform highp mat4 positionMatrix;\n"
        "varying highp vec2 textureCoord;\n"
        "void main(void)\n"
        "{\n"
        "   gl_Position = positionMatrix * vertexCoordArray;\n"
        "   textureCoord = textureCoordArray;\n"
        "}\n";

// Interprets the RGBA texture as in fact being BGRx and paints it.
static const char *qt_glsl_bgrxShaderProgram =
        "uniform sampler2D texRgb;\n"
        "uniform mediump mat4 colorMatrix;\n"
        "varying highp vec2 textureCoord;\n"
        "void main(void)\n"
        "{\n"
        "    highp vec4 color = vec4(texture2D(texRgb, textureCoord.st).bgr, 1.0);\n"
        "    gl_FragColor = colorMatrix * color;\n"
        "}\n";

// Interprets the RGBA texture as in fact being BGRA and paints it.
static const char *qt_glsl_bgraShaderProgram =
        "uniform sampler2D texRgb;\n"
        "uniform mediump mat4 colorMatrix;\n"
        "varying highp vec2 textureCoord;\n"
        "void main(void)\n"
        "{\n"
        "    highp vec4 color = vec4(texture2D(texRgb, textureCoord.st).bgr, 1.0);\n"
        "    color = colorMatrix * color;\n"
        "    gl_FragColor = vec4(color.rgb, texture2D(texRgb, textureCoord.st).a);\n"
        "}\n";

// Interprets the RGBA texture as in fact being xRGB and paints it.
static const char *qt_glsl_xrgbShaderProgram =
        "uniform sampler2D texRgb;\n"
        "uniform mediump mat4 colorMatrix;\n"
        "varying highp vec2 textureCoord;\n"
        "void main(void)\n"
        "{\n"
        "    highp vec4 color = vec4(texture2D(texRgb, textureCoord.st).gba, 1.0);\n"
        "    gl_FragColor = colorMatrix * color;\n"
        "}\n";

// Interprets the RGBA texture as in fact being ARGB and paints it.
static const char *qt_glsl_argbShaderProgram =
        "uniform sampler2D texRgb;\n"
        "uniform mediump mat4 colorMatrix;\n"
        "varying highp vec2 textureCoord;\n"
        "void main(void)\n"
        "{\n"
        "    highp vec4 color = vec4(texture2D(texRgb, textureCoord.st).gba, 1.0);\n"
        "    color = colorMatrix * color;\n"
        "    gl_FragColor = vec4(color.rgb, texture2D(texRgb, textureCoord.st).r);\n"
        "}\n";

// Paints RGB frames without doing any color channel flipping.
static const char *qt_glsl_rgbxShaderProgram =
        "uniform sampler2D texRgb;\n"
        "uniform mediump mat4 colorMatrix;\n"
        "varying highp vec2 textureCoord;\n"
        "void main(void)\n"
        "{\n"
        "    highp vec4 color = vec4(texture2D(texRgb, textureCoord.st).rgb, 1.0);\n"
        "    gl_FragColor = colorMatrix * color;\n"
        "}\n";

// Paints planar yuv frames.
static const char *qt_glsl_yuvPlanarShaderProgram =
        "uniform sampler2D texY;\n"
        "uniform sampler2D texU;\n"
        "uniform sampler2D texV;\n"
        "uniform mediump mat4 colorMatrix;\n"
        "varying highp vec2 textureCoord;\n"
        "void main(void)\n"
        "{\n"
        "    highp vec4 color = vec4(\n"
        "           texture2D(texY, textureCoord.st).r,\n"
        "           texture2D(texU, textureCoord.st).r,\n"
        "           texture2D(texV, textureCoord.st).r,\n"
        "           1.0);\n"
        "    gl_FragColor = colorMatrix * color;\n"
        "}\n";


GlslSurfacePainter::GlslSurfacePainter()
    : OpenGLSurfacePainter()
{
}

void GlslSurfacePainter::init(const BufferFormat &format)
{
    Q_ASSERT(m_textureCount == 0);

    const char *fragmentProgram = 0;

    switch (format.videoFormat()) {
    case GST_VIDEO_FORMAT_BGRx:
        initRgbTextureInfo(GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, format.frameSize());
        fragmentProgram = qt_glsl_bgrxShaderProgram;
        break;
    case GST_VIDEO_FORMAT_xRGB:
        initRgbTextureInfo(GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, format.frameSize());
        fragmentProgram = qt_glsl_xrgbShaderProgram;
        break;
    case GST_VIDEO_FORMAT_BGRA:
        initRgbTextureInfo(GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, format.frameSize());
        fragmentProgram = qt_glsl_bgraShaderProgram;
        break;
    case GST_VIDEO_FORMAT_ARGB:
        initRgbTextureInfo(GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, format.frameSize());
        fragmentProgram = qt_glsl_argbShaderProgram;
        break;
    case GST_VIDEO_FORMAT_RGB:
        initRgbTextureInfo(GL_RGB, GL_RGB, GL_UNSIGNED_BYTE, format.frameSize());
        fragmentProgram = qt_glsl_rgbxShaderProgram;
        break;
    case GST_VIDEO_FORMAT_BGR:
        initRgbTextureInfo(GL_RGB, GL_RGB, GL_UNSIGNED_BYTE, format.frameSize());
        fragmentProgram = qt_glsl_bgrxShaderProgram;
        break;
    //NOTE: unlike the other formats, this is endianness-dependent,
    //but using GL_UNSIGNED_SHORT_5_6_5 ensures that it's handled correctly
    case GST_VIDEO_FORMAT_RGB16:
        initRgbTextureInfo(GL_RGB, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, format.frameSize());
        fragmentProgram = qt_glsl_rgbxShaderProgram;
        break;
    case GST_VIDEO_FORMAT_v308:
        initRgbTextureInfo(GL_RGB, GL_RGB, GL_UNSIGNED_BYTE, format.frameSize());
        fragmentProgram = qt_glsl_rgbxShaderProgram;
        break;
    case GST_VIDEO_FORMAT_AYUV:
        initRgbTextureInfo(GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, format.frameSize());
        fragmentProgram = qt_glsl_argbShaderProgram;
        break;
    case GST_VIDEO_FORMAT_YV12:
        initYv12TextureInfo(format.frameSize());
        fragmentProgram = qt_glsl_yuvPlanarShaderProgram;
        break;
    case GST_VIDEO_FORMAT_I420:
        initYuv420PTextureInfo(format.frameSize());
        fragmentProgram = qt_glsl_yuvPlanarShaderProgram;
        break;
    default:
        Q_ASSERT(false);
        break;
    }

    m_videoColorMatrix = format.colorMatrix();

    if (!m_program.addShaderFromSourceCode(QOpenGLShader::Vertex, qt_glsl_vertexShaderProgram)) {
        throw QString("Vertex shader compile error ") + m_program.log();
    }

    if (!m_program.addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentProgram)) {
        throw QString("Shader compile error ") + m_program.log();
    }

    if(!m_program.link()) {
        throw QString("Shader link error ") + m_program.log();
    }

    glGenTextures(m_textureCount, m_textureIds);
}

void GlslSurfacePainter::cleanup()
{
    glDeleteTextures(m_textureCount, m_textureIds);
    m_program.removeAllShaders();

    m_textureCount = 0;
}

void GlslSurfacePainter::paintImpl(const QPainter *painter,
        const GLfloat *vertexCoordArray,
        const GLfloat *textureCoordArray)
{
    const int deviceWidth = painter->device()->width();
    const int deviceHeight = painter->device()->height();

    const QTransform transform = painter->deviceTransform();

    const GLfloat wfactor = 2.0 / deviceWidth;
    const GLfloat hfactor = -2.0 / deviceHeight;

    const GLfloat positionMatrix[4][4] =
    {
        {
            /*(0,0)*/ GLfloat(wfactor * transform.m11() - transform.m13()),
            /*(0,1)*/ GLfloat(hfactor * transform.m12() + transform.m13()),
            /*(0,2)*/ 0.0,
            /*(0,3)*/ GLfloat(transform.m13())
        }, {
            /*(1,0)*/ GLfloat(wfactor * transform.m21() - transform.m23()),
            /*(1,1)*/ GLfloat(hfactor * transform.m22() + transform.m23()),
            /*(1,2)*/ 0.0,
            /*(1,3)*/ GLfloat(transform.m23())
        }, {
            /*(2,0)*/ 0.0,
            /*(2,1)*/ 0.0,
            /*(2,2)*/ -1.0,
            /*(2,3)*/ 0.0
        }, {
            /*(3,0)*/ GLfloat(wfactor * transform.dx() - transform.m33()),
            /*(3,1)*/ GLfloat(hfactor * transform.dy() + transform.m33()),
            /*(3,2)*/ 0.0,
            /*(3,3)*/ GLfloat(transform.m33())
        }
    };

    m_program.bind();

    m_program.enableAttributeArray("vertexCoordArray");
    m_program.enableAttributeArray("textureCoordArray");
    m_program.setAttributeArray("vertexCoordArray", vertexCoordArray, 2);
    m_program.setAttributeArray("textureCoordArray", textureCoordArray, 2);
    m_program.setUniformValue("positionMatrix", positionMatrix);

    if (m_textureCount == 3) {
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, m_textureIds[0]);
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, m_textureIds[1]);
        glActiveTexture(GL_TEXTURE2);
        glBindTexture(GL_TEXTURE_2D, m_textureIds[2]);
        glActiveTexture(GL_TEXTURE0);

        m_program.setUniformValue("texY", 0);
        m_program.setUniformValue("texU", 1);
        m_program.setUniformValue("texV", 2);
    } else {
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, m_textureIds[0]);

        m_program.setUniformValue("texRgb", 0);
    }
    m_program.setUniformValue("colorMatrix", m_colorMatrix);

    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

    m_program.release();
}
