本程序共分为7个文件:
1个主程序文件,2个CTimer文件,2个CReader文件,2个着色器文件
两个CReader文件和2个着色器文件分别在前边的(二)和(四)中给出,这里就不详细解释了
首先我们来讲一下比较简单的文件——CTimer文件:
主要是为了获取时间,以便比较程序性能
这是CTimer.h文件
#ifndef TIMER_H_
#define TIMER_H_
#include"windows.h"
#include<stdlib.h>
class CTimer{
public:
CTimer(void){init();};
long getTime(void);
void reset(void);
private:
SYSTEMTIME time;
long _lStart;
long _lStop;
void init(void);
};
#endif /*TIMER_H_*/
这是CTimer.cpp文件:
#include "CTimer.h"
void CTimer::init(void)
{
_lStart = 0;
_lStop = 0;
GetSystemTime(&time);
_lStart=(time.wSecond*1000)+time.wMilliseconds;
}
long CTimer::getTime(void)
{
GetSystemTime(&time);
_lStop=(time.wSecond*1000)+time.wMilliseconds -_lStart;
return _lStop;
}
void CTimer::reset(void)
{
init();
}
接下来是主程序文件:
共分为6大部分:初始化,准备纹理缓存,配置GLSL,绘制矩形,计时,读回结果
1、初始化:包含对glut、GLEW、FBO的初始化
2、准备纹理缓存:定义两块纹理,一块保存输入一块保存输出,简单生成数据传入输入数据的纹理缓存中
3、配置GLSL:建立一个程序对象,一个着色器对象,贾周着色器程序文件,编译着色器对象以及添加、链接、启用程序对象,设置uniform变量
4、绘制矩形:输出缓存yTexID与FBO关联(计算的结果需要写入保存输出数据的纹理缓存),激活已经保存有输入数据的纹理单元,设置显然对象,使矩形中的每一个像素都会被覆盖到,保证矩形与纹理图同一尺寸
5、计时:glFinish()可以起到线程同步的作用,该函数是控制进入等待状态,直到所有调用的OpenGL命令都执行完成,它才返回。在计时器开始和结束的时候都要调用该函数以保证线程同步
6、读回结果:将结果传回pfOutput缓存,最后完成清理工作
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <gl/glew.h>
#include <gl/glut.h>
#include "CReader.h"
#include "CTimer.h"
#pragma comment(lib, "glew32.lib")
#define WIDTH 1024 //数据块的宽度
#define HEIGHT 1024 //数据块的长度
#define MASK_RADIUS 2 //掩膜半径
using namespace std;
void initGLSL(void);
void initFBO(unsigned unWidth,unsigned unHeight);
void initGLUT(int argc,char** argv);
void createTextures(void);
void setupTexture(const GLuint texID);
void performComputation(void);
void transferFromTexture(float* data);
void transferToTexture(float* data,GLuint texID);
//纹理标识符
GLuint yTexID;
GLuint xTexID;
//GLSL 变量 配置GLSL(一)
GLuint glslProgram;
GLuint fragmentShader;
GLuint outParam,inParam,radiusParam;
//FBO标识符
GLuint fb;
//“屏幕外窗口”的标识符,创建一个有效的OpenGL环境
GLuint glutWindowHandle;
//为OpenGL纹理准备的结构体,包含了纹理格式、内部格式等
struct structTextureParameters{
GLenum texTarget; //纹理类型
GLenum texInternalFormat; //内部格式
GLenum texFormat; //纹理格式
char* shader_source; //着色器源文件
}textureParameters;
//全局变量
float* pfInput; //输入数据
float fRadius = (float)MASK_RADIUS;
unsigned unWidth = (unsigned)WIDTH;
unsigned unHeight = (unsigned)HEIGHT;
unsigned unSize = unWidth * unHeight;
int main(int argc, char **argv)
{
//生成输入数据
unsigned unNoData = 4 * unSize; //数据总数
pfInput = new float[unNoData];
float* pfOutput = new float[unNoData];
for(unsigned i = 0;i < unNoData;i++) pfInput[i] = i;
//确定纹理参数
textureParameters.texTarget = GL_TEXTURE_RECTANGLE_ARB;
textureParameters.texInternalFormat = GL_RGBA32F_ARB;
textureParameters.texFormat = GL_RGBA;
//初始化GLUT和GLEW
initGLUT(argc,argv);
glewInit();
//初始化FBO
initFBO(unWidth, unHeight);
//为输入、输出数据创建纹理缓存
createTextures();
//初始化CReader
CReader reader;
//安全起见,先清除输入纹理缓存 配置GLSL(二)
textureParameters.shader_source = reader.textFileRead("clean.frag");
initGLSL();
performComputation();//计时
//计算二维离散卷积
textureParameters.shader_source = reader.textFileRead("convolution.frag");
initGLSL();
performComputation();
//读回计算结果
transferFromTexture (pfOutput);
//清理工作
glDetachShader(glslProgram, fragmentShader);
glDeleteShader(fragmentShader);
glDeleteProgram(glslProgram);
glDeleteFramebuffersEXT(1,&fb);
glDeleteTextures(1,&yTexID);
glDeleteTextures(1,&xTexID);
glutDestroyWindow(glutWindowHandle);
//退出
delete pfInput;
delete pfOutput;
return EXIT_SUCCESS;
}
//初始化GLUT,创建的窗口是为有一个有效的OpenGL环境
void initGLUT(int argc,char **argv)
{
glutInit(&argc,argv);
glutWindowHandle = glutCreateWindow("GPGPU Tutorial");
}
//屏幕外渲染
void initFBO(unsigned unWidth,unsigned unHeight)
{
//创建FBO,准备屏幕外帧缓存
glGenFramebuffersEXT(1,&fb);
//绑定屏幕外帧缓存,即避开了窗口系统默认的渲染目标
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb);
//设置一个1:1等大的纹理元——像素映射
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0.0,unWidth,0.0,unHeight);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glViewport(0,0,unWidth,unHeight);
}
//初始化GLSL运行时组件,并创建着色器 配置GLSL(三)
void initGLSL(void)
{
//建立程序对象
glslProgram = glCreateProgram();
//建立片段着色器对象
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER_ARB);
//为着色器设置着色器程序文件
const GLchar* source = textureParameters.shader_source;
glShaderSource(fragmentShader, 1, &source, NULL);
//编译着色器
glCompileShader(fragmentShader);
//把着色器与程序关联
glAttachShader(glslProgram,fragmentShader);
//链接到完整的程序,这里使用了默认功能的顶点着色器,用户也可以使用自定义的流经顶点着色器
glLinkProgram(glslProgram);
//获得uniform变量的位置
radiusParam = glGetUniformLocation(glslProgram, "fRadius");
}
void createTextures(void){
//创建纹理,y保存输出数据,x保存输入数据
glGenTextures(1, &yTexID);
glGenTextures(1, &xTexID);
//配置纹理
setupTexture (yTexID);
setupTexture (xTexID);
transferToTexture(pfInput, xTexID);
//设定映射参数 将纹理映射参数GL_MODULATE更改为GL_REPLACE
//GL_MODULATE代表:把纹理元素的颜色乘以几何图元(进行光照计算之后)的颜色。
//GL_REPLACE 代表:简单地覆盖掉纹理下面的结合图形的颜色。这样片段的颜色值将直接采用纹理的颜色。
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE,GL_REPLACE);
}
void setupTexture(const GLuint texID)
{
//激活并绑定将要设置的纹理
glBindTexture(textureParameters.texTarget,texID);
//关闭滤波算法和边界以外的重复算法
//使用GL_NEAREST的原因:将差值得到的纹理坐标和像素对齐,避免了因差值误差引起的误访问
//使用GL_CLAMP 的原因:矩阵的边界元素在绘制的矩形尺寸大于纹理图时的处理方式
//降低错误的发生并尽可能为发现错误提供方便
glTexParameteri(textureParameters.texTarget,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
glTexParameteri(textureParameters.texTarget,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
glTexParameteri(textureParameters.texTarget,GL_TEXTURE_WRAP_S,GL_CLAMP);
glTexParameteri(textureParameters.texTarget,GL_TEXTURE_WRAP_T,GL_CLAMP);
//定义纹理的数据类型为float
glTexImage2D(textureParameters.texTarget,0,textureParameters.texInternalFormat,
unWidth,unHeight,0,textureParameters.texFormat,GL_FLOAT,0);
}
void performComputation(void)
{
//关联输出缓存yTexID与FBO
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,GL_COLOR_ATTACHMENT0_EXT,
textureParameters.texTarget,yTexID,0);
//将glslProgram设为当前程序对象
glUseProgram(glslProgram);
//将GL_TEXTURE0设为当前纹理单元
glActiveTexture(GL_TEXTURE0);
//更新uniform变量,将应用程序的fRadius的值传递至着色器内部
glUniform1f(radiusParam,fRadius);
//同步线程,以便计时
glFinish();
//计时开始
CTimer timer;
long lTime = 0;
timer.reset();
//将设置写入纹理缓存的类型
glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);
//将计算范围定义为同样包括矩形的内部
glPolygonMode(GL_FRONT,GL_FILL);
//用未归一化的纹理坐标设定计算范围
glBegin(GL_QUADS);
glTexCoord2f(0.0,0.0);
glVertex2f(0.0,0.0);
glTexCoord2f(unWidth,0.0);
glVertex2f(unWidth,0.0);
glTexCoord2f(unWidth,unHeight);
glVertex2f(unWidth,unHeight);
glTexCoord2f(0.0,unHeight);
glVertex2f(0.0,unHeight);
glEnd();
//同步线程,终止计时
glFinish();
lTime = timer.getTime();
cout<<"Time elapsed: "<<lTime<<" ms."<<endl;
}
//将数据从当前的纹理缓存传至主存储器
void transferFromTexture(float* data)
{
glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);
glReadPixels(0,0,unWidth,unHeight,textureParameters.texFormat,GL_FLOAT,data);
}
//将数据传送至纹理缓存,请注意在使用硬件加速时,ATI和NVIDIA显卡的区别
void transferToTexture(float* data, GLuint texID)
{
glBindTexture(textureParameters.texTarget,texID);
glTexSubImage2D(textureParameters.texTarget,0,0,0,unWidth,unHeight,
textureParameters.texFormat,GL_FLOAT,data);
}
参考文献:
仇德元.《GPGPU编程技术——从GLSL、CDPU到OpenGL》[M].河北省三河市:机械工业出版社,2012年:323.