/**
 * 最简单的OpenGL播放视频的例子（OpenGL播放RGB/YUV）
 * Simplest Video Play OpenGL (OpenGL play RGB/YUV) 
 *
 * 雷霄骅 Lei Xiaohua
 * leixiaohua1020@126.com
 * 中国传媒大学/数字电视技术
 * Communication University of China / Digital TV Technology
 * http://blog.csdn.net/leixiaohua1020
 *
 * 本程序使用OpenGL播放RGB/YUV视频像素数据。
 * 是最简单的OpenGL播放视频的教程。
 *
 * This software plays RGB/YUV raw video data using OpenGL.
 * It's the simplest OpenGL tutorial (About video playback).
 */

#include <stdio.h>

#include "glew.h"
#include "glut.h"

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <string.h>

int screen_w=320,screen_h=240;
const int pixel_w = 320, pixel_h = 240;
//YUV file
FILE *infile = NULL;
unsigned char buf[pixel_w*pixel_h*3/2];
unsigned char *plane[3];


GLuint p;                // program id,着色器相关
GLuint id_y, id_u, id_v; // 纹理id
GLuint textureUniformY, textureUniformU,textureUniformV;


#define ATTRIB_VERTEX 3
#define ATTRIB_TEXTURE 4

void display(void){
    if (fread(buf, 1, pixel_w*pixel_h*3/2, infile) != pixel_w*pixel_h*3/2){
        // Loop
        fseek(infile, 0, SEEK_SET);
        fread(buf, 1, pixel_w*pixel_h*3/2, infile);
    }
    //选择可以由纹理函数进行修改的当前纹理单位
    glActiveTexture(GL_TEXTURE0);
	/*
	建立一个绑定到目标纹理的有名称的纹理。
	target:	纹理被绑定的目标，可选的值有GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D等。
	texture:纹理的名称，并且，该纹理的名称在当前的应用中不能被再次使用。
	*/
    glBindTexture(GL_TEXTURE_2D, id_y);
	/*
	根据指定的参数，生成一个2D纹理（Texture）。相似的函数还有glTexImage1D、glTexImage3D。
	target     指定目标纹理，这个值必须是GL_TEXTURE_2D。
	level       执行细节级别。0是最基本的图像级别，你表示第N级贴图细化级别。
	internalformat     指定纹理中的颜色组件，这个取值和后面的format取值必须相同。可选的值有
		GL_ALPHA,GL_RGB,GL_RGBA,GL_LUMINANCE, GL_LUMINANCE_ALPHA 等几种。
	width     指定纹理图像的宽度。
	height    指定纹理图像的高度。
	border    指定边框的宽度。必须为0。
	format    像素数据的颜色格式，必须和internalformat取值必须相同。
	type        指定像素数据的数据类型。可以使用的值有                    
		GL_UNSIGNED_BYTE,GL_UNSIGNED_SHORT_5_6_5,GL_UNSIGNED_SHORT_4_4_4_4, GL_UNSIGNED_SHORT_5_5_5_1
	pixels      指定内存中指向图像数据的指针
	*/
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, pixel_w, pixel_h, 0, GL_RED, GL_UNSIGNED_BYTE, plane[0]); 
    
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, id_u);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, pixel_w/2, pixel_h/2, 0, GL_RED, GL_UNSIGNED_BYTE, plane[1]);       
    
    glActiveTexture(GL_TEXTURE2);
    glBindTexture(GL_TEXTURE_2D, id_v);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, pixel_w/2, pixel_h/2, 0, GL_RED, GL_UNSIGNED_BYTE, plane[2]);    
        
    //glClearColor(0.0, 0.6, 0.0, 1.0);
    //glClear(GL_COLOR_BUFFER_BIT); 

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, id_y);
	/*
	glUniform()为当前程序对象指定Uniform变量的值。（注意，由于OpenGL由C语言编写，但是C语言不支持函数的重载，
	所以会有很多名字相同后缀不同的函数版本存在。其中函数名中包含数字（1、2、3、4）表示接受该数字个用于更
	改uniform变量的值，i表示32位整形，f表示32位浮点型，ub表示8位无符号byte，ui表示32位无符号整形，v表示接受
	相应的指针类型。 ）
	*/
    glUniform1i(textureUniformY, 0);    
    
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, id_u);
    glUniform1i(textureUniformU, 1);
    
    glActiveTexture(GL_TEXTURE2);
    glBindTexture(GL_TEXTURE_2D, id_v);
    glUniform1i(textureUniformV, 2);
    
    // Draw
	/*
	绘制图形。
	mode:有3种参数。
		GL_TRIANGLES：每三个顶之间绘制三角形，之间不连接
		GL_TRIANGLE_FAN：以V0V1V2,V0V2V3,V0V3V4，……的形式绘制三角形
		GL_TRIANGLE_STRIP：顺序在每三个顶点之间均绘制三角形。这个方法可以保证从相同的
		方向上所有三角形均被绘制。以V0V1V2,V1V2V3,V2V3V4……的形式绘制三角形
	first:从数组缓存中的哪一位开始绘制，一般都定义为0
	count:顶点的数量
	*/
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
	// Show
	/*
	显示图像。交换两个缓冲区指针。
	当我们进行复杂的绘图操作时，画面便可能有明显的闪烁。解决这个问题的关键在于使绘制的
	东西同时出现在屏幕上。所谓双缓冲技术， 是指使用两个缓冲区： 前台缓冲和后台缓冲。前
	台缓冲即我们看到的屏幕，后台缓冲则在内存当中，对我们来说是不可见的。每次的所有绘图
	操作都在后台缓冲中进行， 当绘制完成时， 把绘制的最终结果复制到屏幕上。
	*/
    glutSwapBuffers();
}

void timeFunc(int value){
    display();
    // 每40ms播放一帧
    glutTimerFunc(40, timeFunc, 0);
}
/*
获取OpenGL Shader Language的代码。
OpenGL Shader Language,简称GLSL，它是一种类似于C语言的专门为GPU设计的语言，它可以放在
GPU里面被并行运行。
opengl es的着色器有.fsh和.vsh两个文件。这两个文件在被编译和链接后就可以产生可执行程序与GPU交互。
	.vsh 是Vertex Shader，用于顶点计算，可以理解控制顶点的位置，在这个文件中我们通常会传入当前顶
		点的位置，和纹理的坐标。
	.fsh 是Fragment Shader。在这里面我可以对于每一个像素点进行重新计算。
总结：
	vsh 负责搞定像素位置 ,填写  gl_Posizion。
	sh 负责搞定像素外观，填写 gl_FragColor。
	他们都是一个像素运行一次的。
*/
char *textFileRead(char * filename)
{
    char *s = (char *)malloc(8000);
    memset(s, 0, 8000);
    FILE *infile = fopen(filename, "rb");
    int len = fread(s, 1, 8000, infile);
    fclose(infile);
    s[len] = 0;
    return s;
}
void setShaders()
{
    GLint vertCompiled, fragCompiled, linked;
    
    GLint v, f;
    const char *vs,*fs;
	//编译一个shader对象的Step1: 创建一个容纳shader的容器。
    v = glCreateShader(GL_VERTEX_SHADER);
    f = glCreateShader(GL_FRAGMENT_SHADER);
    vs = textFileRead("Shader.vsh");
    fs = textFileRead("Shader.fsh");
	//编译一个shader对象的Step2: shader容器中添加shader的源代码。
    glShaderSource(v, 1, &vs,NULL);
    glShaderSource(f, 1, &fs,NULL);
	//编译一个shader对象的Step3: 对shader容器中的源代码进行编译。
    glCompileShader(v);
	//获取shader对象参数，参数包括：GL_SHADER_TYPE, GL_DELETE_STATUS, 
	//   GL_COMPILE_STATUS, GL_INFO_LOG_LENGTH, GL_SHADER_SOURCE_LENGTH.
    glGetShaderiv(v, GL_COMPILE_STATUS, &vertCompiled);
    glCompileShader(f);
    glGetShaderiv(f, GL_COMPILE_STATUS, &fragCompiled);

	//program对象提供了把需要做的事连接在一起的机制。在一个program
	//中，shader对象需要连接在一起。
	//连接成一个程序的Step1: 首先创建一个容纳程序（Program）的容器，我们称之为程序容器。
    p = glCreateProgram(); 
	//连接成一个程序的Step2: 将shader容器添加到程序中。
    glAttachShader(p,v);
    glAttachShader(p,f); 
	//把program的顶点属性索引与顶点shader中的变量名进行绑定。
    glBindAttribLocation(p, ATTRIB_VERTEX, "position");
    glBindAttribLocation(p, ATTRIB_TEXTURE, "TexCoordIn");
	//连接成一个程序的Step3: 连接程序对象。如果任何类型为GL_VERTEX_SHADER的shader对象连接到program,
	//它将产生在“顶点着色器”（Vertex Shader）上可执行的程序；如果任何类型为GL_FRAGMENT_SHADER的shader
	//对象连接到program,它将产生在“像素着色器”（Pixel Shader）上可执行的程序。
    glLinkProgram(p);
	//获取program对象的参数值，参数有：GL_DELETE_STATUS, GL_LINK_STATUS, GL_VALIDATE_STATUS,
	//GL_INFO_LOG_LENGTH, GL_ATTACHED_SHADERS, GL_ACTIVE_ATTRIBUTES, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, 
	//GL_ACTIVE_UNIFORMS, GL_ACTIVE_UNIFORM_MAX_LENGTH.
    glGetProgramiv(p, GL_LINK_STATUS, &linked);  
	//安装一个program object，并把它作为当前rendering state的一部分。
    glUseProgram(p);

    static const GLfloat squareVertices[] = {
        -1.0f, -1.0f,
         1.0f, -1.0f,
        -1.0f,  1.0f,
         1.0f,  1.0f,
    };    
    static const GLfloat coordVertices[] = {
         0.0f,  1.0f,
         1.0f,  1.0f,
         0.0f,  0.0f,
         1.0f,  0.0f,
    }; 
	//获取一个一致变量(Uniform Variables)的存储位置
	//一致变量适合描述在一个图元中、一帧中甚至一个场景中都不变的值。
	//一致变量在顶点shader和片断shader中都是只读的。
    textureUniformY = glGetUniformLocation(p, "tex_y");
    textureUniformU = glGetUniformLocation(p, "tex_u");
    textureUniformV = glGetUniformLocation(p, "tex_v"); 
	//定义一个通用顶点属性数组。当渲染时，它指定了通用顶点属性数组从索引index处开始的位置和数据格式。
	/*
	index: 指示将被修改的通用顶点属性的索引  
	size:  指点每个顶点元素个数(1~4)  
	type:  数组中每个元素的数据类型  
	normalized: 指示定点数据值是否被归一化(归一化<[-1,1]或[0,1]>：GL_TRUE,直接使用:GL_FALSE)  
	stride: 连续顶点属性间的偏移量，如果为0，相邻顶点属性间紧紧相邻  
	pointer:顶点数组  
	*/
    glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, 0, 0, squareVertices);
	//Enable由索引index指定的通用顶点属性数组。
    glEnableVertexAttribArray(ATTRIB_VERTEX);    
    glVertexAttribPointer(ATTRIB_TEXTURE, 2, GL_FLOAT, 0, 0, coordVertices);
    glEnableVertexAttribArray(ATTRIB_TEXTURE);
	//根据纹理参数返回n个纹理索引。
    glGenTextures(1, &id_y); 
    glBindTexture(GL_TEXTURE_2D, id_y);    
	/*
	图象从纹理图象空间映射到帧缓冲图象空间(映射需要重新构造纹理图像,这样就会造成应用到多边形
	上的图像失真),这时就可用glTexParmeteri()函数来确定如何把纹理象素映射成像素。
	GL_TEXTURE_MAG_FILTER: 放大过滤
	GL_TEXTURE_MIN_FILTER: 缩小过滤
	GL_LINEAR: 线性差值,。使用距离当前渲染像素中心最近的4个纹素加权平均值。
	GL_NEAREST：临近像素插值。质量非常不好。
	*/
    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);
    
    
    glGenTextures(1, &id_u);
    glBindTexture(GL_TEXTURE_2D, id_u);   
    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);
    
    glGenTextures(1, &id_v); 
    glBindTexture(GL_TEXTURE_2D, id_v);    
    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);
}

int main(int argc, char* argv[])
{
	if((infile=fopen("../test_320x240.yuv", "rb"))==NULL){
		printf("cannot open this file\n");
		return -1;
	}

	//YUV数据
    plane[0] = buf;
    plane[1] = plane[0] + pixel_w*pixel_h;
    plane[2] = plane[1] + pixel_w*pixel_h/4;

    // GLUT初始化
    glutInit(&argc, argv);  
	/*
	GLUT_DOUBLE: 使用双缓冲
	*/
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA /*| GLUT_STENCIL | GLUT_DEPTH*/);
    glutInitWindowPosition(100, 100);
    glutInitWindowSize(screen_w, screen_h);
    glutCreateWindow("Simplest Video Play OpenGL");

    printf("%s\n", glGetString(GL_VERSION));
    GLenum l = glewInit();
    if (glewIsSupported("GL_VERSION_2_0"))
        printf("Ready for OpenGL 2.0\n");
    else{
        printf("OpenGL 2.0 not supported\n");
        return 0;
    }

    glutDisplayFunc(&display);
    glutTimerFunc(40, timeFunc, 0); 
    setShaders();

    // 开始显示
    glutMainLoop();

    return 0;
}






