#include "glyuvwidget.h"
#include <QOpenGLShaderProgram>
#include <QOpenGLTexture>
#include <QDebug>
#include "globaldata.h"

#define ATTRIB_VERTEX 0
#define ATTRIB_TEXTURE 1
// 顶点矩阵
static const GLfloat vertexVertices[] = {
    -1.0f, -1.0f,
     1.0f, -1.0f,
     -1.0f, 1.0f,
     1.0f, 1.0f,
};
//纹理矩阵
static const GLfloat textureVertices[] = {
    0.0f,  1.0f,
    1.0f,  1.0f,
    0.0f,  0.0f,
    1.0f,  0.0f,
};

glyuvwidget::glyuvwidget(QWidget *parent):
    QOpenGLWidget(parent)
{
}

glyuvwidget::~glyuvwidget()
{
    makeCurrent();
    //vbo.destroy();
    globalData::getInstance()->g_GLList.append(id_y);//回收纹理
    globalData::getInstance()->g_GLList.append(id_u);
    globalData::getInstance()->g_GLList.append(id_v);

    m_pShaderProgram->disableAttributeArray(ATTRIB_VERTEX);
    m_pShaderProgram->disableAttributeArray(ATTRIB_TEXTURE);
    m_pShaderProgram->release();
    m_pShaderProgram->removeAllShaders();
    delete m_pShaderProgram;
    m_pShaderProgram=NULL;
    delete [] m_pBufYuv420p;
    m_pBufYuv420p=NULL;
    doneCurrent();
}

void glyuvwidget::slotShowYuv(uchar *ptr, uint width, uint height)
{
    if(NULL == ptr){
        //qDebug() << "-------PlayOneFrame inBuf==NULL";
        return;
    }
    if(NULL == m_pBufYuv420p){//如果正在运行中改变摄像头的分辨率有可能导致内存不足而奔溃
       // qDebug() << "-------new buf";
        int bufSize = 1920*1080*1.5;
        m_pBufYuv420p = new unsigned char[bufSize];
    }

    m_nVideoW = width;
    m_nVideoH = height;
    int bufSize = m_nVideoW * m_nVideoH * 1.5;
    memcpy(m_pBufYuv420p,ptr, bufSize);
    //m_pBufYuv420p = ptr;
    update();
}

void glyuvwidget::initializeGL()
{
    //初始化OpenGL函数
    initializeOpenGLFunctions();
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_TEXTURE_2D);
    //创建着色器程序容器
    m_pShaderProgram = new QOpenGLShaderProgram();

    //将顶点着色器源码添加到程序容器
    m_pShaderProgram->addShaderFromSourceCode(QOpenGLShader::Vertex,
                                              "attribute  vec4 vertexIn;"
                                              "attribute  vec2 textureIn;"
                                              "varying   vec2 textureOut;"
                                              "void main(void) {"
                                              "gl_Position = vertexIn;"
                                              "textureOut = textureIn;}");
    //将片段着色器添加到程序容器
    m_pShaderProgram->addShaderFromSourceCode(QOpenGLShader::Fragment,
                                              "varying lowp vec2 textureOut;"
                                              "uniform  sampler2D tex_y;"
                                              "uniform  sampler2D tex_u;"
                                              "uniform  sampler2D tex_v;"
                                              "void main(void){"
                                              "mediump vec3 yuv;"
                                              "lowp  vec3 rgb;"
                                              "yuv.x = texture2D(tex_y, textureOut).r;"
                                              "yuv.y = texture2D(tex_u, textureOut).r - 0.5;"
                                              "yuv.z = texture2D(tex_v, textureOut).r - 0.5;"
                                              "rgb = mat3( 1,       1,         1,"
                                              "0,       -0.39465,  2.03211,"
                                              "1.13983, -0.58060,  0) * yuv;"
                                              "gl_FragColor = vec4(rgb, 1);}" );

    //绑定属性vertexIn到指定位置ATTRIB_VERTEX,该属性在顶点着色源码其中有声明
    m_pShaderProgram->bindAttributeLocation("vertexIn", ATTRIB_VERTEX);
    //绑定属性textureIn到指定位置ATTRIB_TEXTURE,该属性在顶点着色源码其中有声明
    m_pShaderProgram->bindAttributeLocation("textureIn", ATTRIB_TEXTURE);
    //链接所有所有添入到的着色器程序
    m_pShaderProgram->link();
    //激活所有链接
    m_pShaderProgram->bind();
    //读取着色器中的数据变量tex_y, tex_u, tex_v的位置,这些变量的声明可以在
    //片段着色器源码中可以看到
    textureUniformY =  m_pShaderProgram->uniformLocation("tex_y");
    textureUniformU =  m_pShaderProgram->uniformLocation("tex_u");
    textureUniformV =  m_pShaderProgram->uniformLocation("tex_v");

    //设置属性ATTRIB_VERTEX的顶点矩阵值以及格式
    glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, GL_FALSE, 0, vertexVertices);
    glEnableVertexAttribArray(ATTRIB_VERTEX);
    //设置属性ATTRIB_TEXTURE的纹理矩阵值以及格式
    glVertexAttribPointer(ATTRIB_TEXTURE, 2, GL_FLOAT, GL_FALSE, 0, textureVertices);
    glEnableVertexAttribArray(ATTRIB_TEXTURE);

    if(globalData::getInstance()->g_GLList.size()==0)
    {
        glGenTextures(1,&id_y); //生成纹理
        //qDebug()<< "生成纹理 "<<id_y;
    }
    else
    {
       id_y = globalData::getInstance()->g_GLList.first();
       globalData::getInstance()->g_GLList.removeFirst();
//       glDeleteTextures(1,&id_y);

    }
    glBindTexture(GL_TEXTURE_2D,id_y);//绑定纹理
    glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,(GLfloat)GL_LINEAR);//设置纹理参数
    glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,(GLfloat)GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE);

    if(globalData::getInstance()->g_GLList.size()==0)
    {
        glGenTextures(1,&id_u); //生成纹理
    }
    else
    {
       id_u = globalData::getInstance()->g_GLList.first();
       globalData::getInstance()->g_GLList.removeFirst();
//       glDeleteTextures(1,&id_u);
    }
    glBindTexture(GL_TEXTURE_2D, id_u);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, (GLfloat)GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, (GLfloat)GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    if(globalData::getInstance()->g_GLList.size()==0)
    {
        glGenTextures(1,&id_v); //生成纹理
    }
    else
    {
       id_v = globalData::getInstance()->g_GLList.first();
       globalData::getInstance()->g_GLList.removeFirst();
//       glDeleteTextures(1,&id_v);
    }
    glBindTexture(GL_TEXTURE_2D, id_v);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, (GLfloat)GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, (GLfloat)GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    glClearColor(0.3f,0.3f,0.3f,0.0);//设置背景色
}

//void glyuvwidget::initializeGL()
//{
//    initializeOpenGLFunctions();
//    glEnable(GL_DEPTH_TEST);

//    static const GLfloat vertices[]{
//        //顶点坐标
//        -1.0f,-1.0f,
//        -1.0f,+1.0f,
//        +1.0f,+1.0f,
//        +1.0f,-1.0f,
//        //纹理坐标
//        0.0f,1.0f,
//        0.0f,0.0f,
//        1.0f,0.0f,
//        1.0f,1.0f,
//    };


//    vbo.create();
//    vbo.bind();
//    vbo.allocate(vertices,sizeof(vertices));
//    //顶点着色器
//    QOpenGLShader *vshader = new QOpenGLShader(QOpenGLShader::Vertex,this);
//    const char *vsrc =
//   "attribute vec4 vertexIn; \
//    attribute vec2 textureIn; \
//    varying vec2 textureOut;  \
//    void main(void)           \
//    {                         \
//        gl_Position = vertexIn; \
//        textureOut = textureIn; \
//    }";
//    vshader->compileSourceCode(vsrc);
//    //片元着色器
//    QOpenGLShader *fshader = new QOpenGLShader(QOpenGLShader::Fragment,this);
//    const char *fsrc = "varying vec2 textureOut; \
//    uniform sampler2D tex_y; \
//    uniform sampler2D tex_u; \
//    uniform sampler2D tex_v; \
//    void main(void) \
//    { \
//        vec3 yuv; \
//        vec3 rgb; \
//        yuv.x = texture2D(tex_y, textureOut).r; \
//        yuv.y = texture2D(tex_u, textureOut).r - 0.5; \
//        yuv.z = texture2D(tex_v, textureOut).r - 0.5; \
//        rgb = mat3( 1,       1,         1, \
//                    0,       -0.39465,  2.03211, \
//                    1.13983, -0.58060,  0) * yuv; \
//        gl_FragColor = vec4(rgb, 1); \
//    }";
//    fshader->compileSourceCode(fsrc);

//    program = new QOpenGLShaderProgram(this);
//    program->addShader(vshader);
//    program->addShader(fshader);
//    program->bindAttributeLocation("vertexIn",VERTEXIN);
//    program->bindAttributeLocation("textureIn",TEXTUREIN);
//    program->link();
//    program->bind();
//    program->enableAttributeArray(VERTEXIN);
//    program->enableAttributeArray(TEXTUREIN);
//    program->setAttributeBuffer(VERTEXIN,GL_FLOAT,0,2,2*sizeof(GLfloat));
//    program->setAttributeBuffer(TEXTUREIN,GL_FLOAT,8*sizeof(GLfloat),2,2*sizeof(GLfloat));
//    textureUniformY = program->uniformLocation("tex_y");
//    textureUniformU = program->uniformLocation("tex_u");
//    textureUniformV = program->uniformLocation("tex_v");
//    textureY = new QOpenGLTexture(QOpenGLTexture::Target2D);
//    textureU = new QOpenGLTexture(QOpenGLTexture::Target2D);
//    textureV = new QOpenGLTexture(QOpenGLTexture::Target2D);
//    textureY->create();
//    textureU->create();
//    textureV->create();
//    idY = textureY->textureId();
//    idU = textureU->textureId();
//    idV = textureV->textureId();
//   // glClearColor(0.0,0.0,0.0,0.0);
//    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
//    glClear(GL_COLOR_BUFFER_BIT);
//}

void glyuvwidget::resizeGL(int w, int h)
{
    // 计算窗口横纵比
    qreal aspect = qreal(w) / qreal(h ? h : 1);
    // 设置近平面值 3.0, 远平面值 7.0, 视场45度
    const qreal zNear = 3.0, zFar = 7.0, fov = 45.0;
    // 重设投影
    projection.setToIdentity();
    // 设置透视投影
    projection.perspective(fov, static_cast<float>(aspect), zNear, zFar);
}

void glyuvwidget::paintGL()
{
    if(NULL != m_pBufYuv420p)
    {
        //清理屏幕
        glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
        //加载y数据纹理
        glActiveTexture(GL_TEXTURE0);//激活纹理单元GL_TEXTURE0
        glBindTexture(GL_TEXTURE_2D, id_y);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, m_nVideoW, m_nVideoH, 0, GL_RED, GL_UNSIGNED_BYTE, (char*)m_pBufYuv420p);//使用内存中m_pBufYuv420p数据创建真正的y数据纹理
        //只能用0,1,2等表示纹理单元的索引，这是opengl不人性化的地方
        //0对应纹理单元GL_TEXTURE0，1对应纹理单元GL_TEXTURE1，2对应纹理的单元GL_TEXTURE2
        glUniform1i(textureUniformY, 0); //指定y纹理要使用新值

        //加载u数据纹理
        glActiveTexture(GL_TEXTURE1);//激活纹理单元GL_TEXTURE1
        glBindTexture(GL_TEXTURE_2D, id_u);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, m_nVideoW/2, m_nVideoH/2, 0, GL_RED, GL_UNSIGNED_BYTE, (char*)m_pBufYuv420p+m_nVideoW*m_nVideoH);
        glUniform1i(textureUniformU, 1);//指定u纹理要使用新值

        //加载v数据纹理
        glActiveTexture(GL_TEXTURE2);//激活纹理单元GL_TEXTURE2
        glBindTexture(GL_TEXTURE_2D, id_v);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, m_nVideoW/2, m_nVideoH/2, 0, GL_RED, GL_UNSIGNED_BYTE, (char*)m_pBufYuv420p+m_nVideoW*m_nVideoH*5/4);
        glUniform1i(textureUniformV, 2);//指定v纹理要使用新值
        glDisable(GL_DEPTH_TEST);//should be put before glDrawArrays
        //使用顶点数组方式绘制图形
        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    }
}
//void glyuvwidget::paintGL()
//{
////    QMatrix4x4 m;
////    m.perspective(60.0f, 4.0f/3.0f, 0.1f, 100.0f );//透视矩阵随距离的变化，图形跟着变化。屏幕平面中心就是视点（摄像头）,需要将图形移向屏幕里面一定距离。
////    m.ortho(-2,+2,-2,+2,-10,10);//近裁剪平面是一个矩形,矩形左下角点三维空间坐标是（left,bottom,-near）,右上角点是（right,top,-near）所以此处为负，表示z轴最大为10；
//                                //远裁剪平面也是一个矩形,左下角点空间坐标是（left,bottom,-far）,右上角点是（right,top,-far）所以此处为正，表示z轴最小为-10；
//                                //此时坐标中心还是在屏幕水平面中间，只是前后左右的距离已限制。
//    //清理屏幕
//    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
//    glActiveTexture(GL_TEXTURE0);  //激活纹理单元GL_TEXTURE0,系统里面的
//    glBindTexture(GL_TEXTURE_2D,idY); //绑定y分量纹理对象id到激活的纹理单元
//    //使用内存中的数据创建真正的y分量纹理数据
//    glTexImage2D(GL_TEXTURE_2D,0,GL_RED,videoW,videoH,0,GL_RED,GL_UNSIGNED_BYTE,yuvPtr);
//    //https://blog.csdn.net/xipiaoyouzi/article/details/53584798 纹理参数解析
//    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
//    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
//    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
//    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

//    glActiveTexture(GL_TEXTURE1); //激活纹理单元GL_TEXTURE1
//    glBindTexture(GL_TEXTURE_2D,idU);
//    //使用内存中的数据创建真正的u分量纹理数据
//    glTexImage2D(GL_TEXTURE_2D,0,GL_RED,videoW >> 1, videoH >> 1,0,GL_RED,GL_UNSIGNED_BYTE,yuvPtr + videoW * videoH);
//    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
//    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
//    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
//    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

//    glActiveTexture(GL_TEXTURE2); //激活纹理单元GL_TEXTURE2
//    glBindTexture(GL_TEXTURE_2D,idV);
//    //使用内存中的数据创建真正的v分量纹理数据
//    glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, videoW >> 1, videoH >> 1, 0, GL_RED, GL_UNSIGNED_BYTE, yuvPtr+videoW*videoH*5/4);
//    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
//    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
//    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
//    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
//    //指定y纹理要使用新值
//    glUniform1i(textureUniformY, 0);
//    //指定u纹理要使用新值
//    glUniform1i(textureUniformU, 1);
//    //指定v纹理要使用新值
//    glUniform1i(textureUniformV, 2);
//    //使用顶点数组方式绘制图形
//    glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
//}
