测试了《GPU编程之GLSL(五)——二维离散卷积》一文的程序,发现先运行哪个“clean.frag”,程序总运行时间更短,就以该文为模板来运行层卷积。
先把《glsl着色器实现多重纹理与帧缓冲对象(fbo)》一文的着色器出错提示部分复制过来(不然,将不能调试着色器)。
接口和前面相同,为了区别加一个G:
void conv_initG(int argc, char* argv[],int h,int w)
//核数据,核宽,输入维度,输出维度,偏置数据,输入数据,输出数据,是否激活
//核大小:kw * kw * 输入维度 * 输出维度。
//输入数据大小:wh * 输入维度。 输出数据大小:wh * 输出维度。
//加法结果在fbo中,中间不用读出,relu也在GLSL中完成
void conv_GLSL_层_add7G(float* kernel,int kw,int inNum,int outNum,float *bias,float *inData, float * &outData,bool active=true)
可以用前面的单个3x3卷积测试下:
conv_initG(argc, argv, h, w);
conv_GLSL_层_add7G(kernel,kw,1,1,NULL,inData, outData, false);
或1x1:
conv_initG(argc, argv, h, w);
conv_GLSL_层_add7G(kernel,1,1,1,NULL,inData, outData, false);
层卷积着色器和前面相同,全cpp:
//GPU编程之GLSL(五)——二维离散卷积
//本程序共分为7个文件:
//
//1个主程序文件,2个CTimer文件,2个CReader文件,2个着色器文件
//
//
//
//两个CReader文件和2个着色器文件分别在前边的(二)和(四)中给出,这里就不详细解释了
//
//
//
//首先我们来讲一下比较简单的文件——CTimer文件:
//
//主要是为了获取时间,以便比较程序性能
//接下来是主程序文件:
//
//共分为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")
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;
static GLuint textureN[64]; //多通道数据成为多纹理
static GLuint texture; //结果加回纹理
//GLSL 变量 配置GLSL(一)
GLuint glslProgram;
GLuint fragmentShader;
GLuint outParam,inParam,radiusParam;
GLuint kernelParam;
//FBO标识符
GLuint fb;
//“屏幕外窗口”的标识符,创建一个有效的OpenGL环境
GLuint glutWindowHandle;
//为OpenGL纹理准备的结构体,包含了纹理格式、内部格式等
struct structTextureParameters{
GLenum texTarget; //纹理类型
GLenum texInternalFormat; //内部格式
GLenum texFormat; //纹理格式
char* shader_source; //着色器源文件
}textureParameters;
//全局变量
float* pfInput; //输入数据
unsigned unWidth ;//= (unsigned)WIDTH;
unsigned unHeight;// = (unsigned)HEIGHT;
unsigned unSize;// = unWidth * unHeight;
void printShaderInfoLog(GLuint obj)
{
int infologLength = 0;
int charsWritten = 0;
char *infoLog;
glGetShaderiv(obj, GL_INFO_LOG_LENGTH,&infologLength);
if (infologLength > 0)
{
infoLog = (char *)malloc(infologLength);
glGetShaderInfoLog(obj, infologLength, &charsWritten, infoLog);
printf("%s\n",infoLog);
free(infoLog);
}
}
#define printOpenGLError() printOglError(__FILE__, __LINE__)
//输出错误相关信息
int printOglError(char *file, int line)
{
GLenum glErr;
int retCode = 0;
glErr = glGetError();
while (glErr != GL_NO_ERROR)
{
printf("glError in file %s @ line %d: %s\n", file, line, gluErrorString(glErr));
retCode = 1;
glErr = glGetError();
}
return retCode;
}
//输出opengl错误
void printInfoLog(GLhandleARB obj)
{
int infologLength = 0;
int charsWritten = 0;
GLcharARB *infoLog;
printOpenGLError();
glGetObjectParameterivARB(obj, GL_OBJECT_INFO_LOG_LENGTH_ARB, &infologLength);
printOpenGLError();
if(infologLength > 0)
{
infoLog = (GLcharARB*)malloc(infologLength);
if(infoLog == NULL)
{
printf("ERROR: Could not allocate InfoLog buffer\n");
exit(1);
}
glGetInfoLogARB(obj,infologLength,&charsWritten,infoLog);
printf("InfoLog:\n%s\n\n",infoLog);
free(infoLog);
}
printOpenGLError();
}
//初始化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);
{
GLint vertfragCompiled;
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &vertfragCompiled);
if (!vertfragCompiled )
{
printf("编译出错了!!\n");
printShaderInfoLog(fragmentShader);
}
}
//把着色器与程序关联
glAttachShader(glslProgram,fragmentShader);
//链接到完整的程序,这里使用了默认功能的顶点着色器,用户也可以使用自定义的流经顶点着色器
glLinkProgram(glslProgram);
{
GLint linked;
//检查opengl错误
glGetObjectParameterivARB(glslProgram, GL_OBJECT_LINK_STATUS_ARB, &linked);
if(!linked)
{
printf("链接出错了!!\n");
printInfoLog(glslProgram);
}
}
}
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);
//同步线程,以便计时
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<<"经过时间: "<<lTime<<" ms."<<endl;
}
void performComputationG(void)
{
//将设置写入纹理缓存的类型
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();
}
//将数据从当前的纹理缓存传至主存储器
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);
}
float * _0Data;
float* pfOutput;
void conv_initG(int argc, char* argv[],int h,int w)
{
printf("\n为 GLSL 运行作准备...\n");
unWidth =w;
unHeight=h;
unSize = unWidth * unHeight;//数据总数
float * _0Data= new float[unWidth * unHeight];
memset(_0Data, 0,unWidth * unHeight*sizeof(float));//清零
pfInput=_0Data;
pfOutput = new float[unSize];
//确定纹理参数
textureParameters.texTarget = GL_TEXTURE_RECTANGLE_ARB;
textureParameters.texInternalFormat = GL_R32F;
textureParameters.texFormat = GL_RED;
//初始化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.h");
//textureParameters.shader_source = reader.textFileRead("convolution - 1.h");
initGLSL();
//关联输出缓存yTexID与FBO
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,GL_COLOR_ATTACHMENT0_EXT,
textureParameters.texTarget,yTexID,0);
//将glslProgram设为当前程序对象
glUseProgram(glslProgram);
}
void LoadinToTextures(float *pTexData,int num)
{
glGenTextures( num, textureN );
for(int i=0;i<num;++i)
{
setupTexture(textureN[i]);
transferToTexture(pTexData+i* unHeight * unWidth,textureN[i]);
}
}
//加法纹理
void initaddG()
{
if(texture == 0)
{
//设置输出纹理的参数
glGenTextures( 1, &texture );
setupTexture( texture );
transferToTexture( _0Data, texture );
}
else
{
transferToTexture( _0Data, texture );
}
}
#include <time.h>
void relu(float *res,int len);
//核数据,核宽,输入维度,输出维度,偏置数据,输入数据,输出数据,是否激活
//核大小:kw * kw * 输入维度 * 输出维度。
//输入数据大小:wh * 输入维度。 输出数据大小:wh * 输出维度。
//加法结果在fbo中,中间不用读出,relu也在GLSL中完成
void conv_GLSL_层_add7G(float* kernel,int kw,int inNum,int outNum,float *bias,float *inData, float * &outData,bool active=true)
{
//可以不是7个
#define CONV_NUM 7
//7个纹理用于卷积,1个纹理用于加法
//printf("核宽:%d,输入维度:%d,输出维度:%d\n",kw,inNum,outNum);
//加载全部输入通道到多纹理
LoadinToTextures(inData,inNum);
initaddG();
//最大 7个9x9 核
//float kernel648[648]={0.0f};
clock_t start_t, end_t;//计算时间
double total_t;
start_t = clock();
int wh=unHeight * unWidth;
outData=new float[wh*outNum];
memset(outData, 0, wh*outNum*sizeof(float));//清零
float *out=outData;
float *in=inData;
float * kernel_s=kernel;//当前核
for(int k=0;k<outNum;++k)
{
//一次实现7个卷积
int kNum=(inNum<CONV_NUM)?inNum:CONV_NUM;
for(int i=0;i<inNum;i+=CONV_NUM)
{
//最多7核
if((kNum==CONV_NUM) && (i+CONV_NUM>inNum))//余数
kNum= inNum % CONV_NUM;
if ( glslProgram <= 0 )
printf("Failed to run shader.\n");
else{
char txt[255];
//传送纹理 Texture0--- Texture7
for(int j=0;j<kNum;++j)
{
sprintf_s(txt,255,"Texture%d\0",j);
glActiveTexture( GL_TEXTURE0 +j);
glBindTexture(textureParameters.texTarget,textureN[i+j]);
glUniform1i( glGetUniformLocation( glslProgram, txt ), j);
//printf("%s.\n",txt);
}
//激活 纹理
sprintf_s(txt,255,"Texture%d\0",kNum);
glActiveTexture( GL_TEXTURE0 +kNum);
glBindTexture(textureParameters.texTarget,texture);
glUniform1i( glGetUniformLocation( glslProgram, txt ), kNum);
//printf("%s.\n",txt);
glUniform1i( glGetUniformLocation( glslProgram, "kw" ), kw ); //核宽
glUniform1i( glGetUniformLocation( glslProgram, "kNum" ), kNum ); //核1--8个
glUniform1fv( glGetUniformLocation( glslProgram, "kernel"),kw*kw*kNum,kernel_s);//核数据
kernel_s+=kw*kw*kNum;
//加偏置
if(i+CONV_NUM>=inNum && bias!=NULL)
{
glUniform1f( glGetUniformLocation( glslProgram, "bias"),bias[k]);//偏置数据
if(active)
glUniform1i( glGetUniformLocation( glslProgram, "active"),1);//激活
else
glUniform1i( glGetUniformLocation( glslProgram, "active"),0);//激活
}
else
{
glUniform1f( glGetUniformLocation( glslProgram, "bias"),0.0f);//无偏置
glUniform1i( glGetUniformLocation( glslProgram, "active"),0);//激活
}
}
performComputationG();
//取出数据
if(i+kNum==inNum)
{
//p++;
transferFromTexture( pfOutput );
memcpy(out,pfOutput,wh*sizeof(float));
//清0
glBindTexture( GL_TEXTURE_RECTANGLE_ARB, texture );
glTexImage2D( GL_TEXTURE_RECTANGLE_ARB, 0, GL_R32F, unWidth, unHeight, 0,GL_RED,GL_FLOAT,_0Data );
}
else
{
//结果转到 texture 中
glBindTexture( GL_TEXTURE_RECTANGLE_ARB, texture );
glCopyTexSubImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, 0, 0, 0, 0, unWidth, unHeight);
}
end_t = clock();
total_t = (double)(end_t - start_t) / CLOCKS_PER_SEC;
if(total_t>1.0){
printf("%d/%d\n",k,outNum);
start_t = clock();
}
}
out+=wh;
}
//清理
for(int i=0;i<64;++i)
{
if( textureN[i] !=0)
{
//删除纹理
glDeleteTextures(1,textureN+i);
textureN[i]=0;
}
}
}
结束。