GPU通用计算——从Hello GPGPU开始
进几年来,GPU得到了飞速的发展,GPU已经可以
处理
3D
图形以外的计算应用,从而大大减轻了
CPU
的负担,这就是
GPU
的通用计算,即
GPGPU
(
General-Purpose computation on GPUs
)。本教程以一个
Hello GPGPU
为例,阐述其中的原理和开发步骤!
l
初始化OpenGL
在
main
函数中,首先需要调用的是初始化并创建窗口,在本例中,使用
GLUT
开发包,
GLUT(OpenGLUtility Toolkit)
开发包提供了一组窗口函数,可以用来处理窗口事件。
----------------------------------------------------------------------------------------------------------------------------
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
glutInitWindowSize(512, 512);
glutCreateWindow(
"Hello, GPGPU! (GLSL version)"
);
glutIdleFunc(idle);
glutDisplayFunc(display);
glutReshapeFunc(reshape);
----------------------------------------------------------------------------------------------------------------------
接着调用
Initialize
,在这里,首先初始化
glew
库,并查询当前显卡是否支持必要的
OpenGL
扩展。并构造出
HelloGPGPU
对象。
// Initialize the "OpenGL Extension Wrangler" library
glewInit();
// Ensure we have the necessary OpenGL Shading Language extensions.
if
(glewGetExtension(
"GL_ARB_fragment_shader"
) != GL_TRUE ||
glewGetExtension(
"GL_ARB_vertex_shader"
) != GL_TRUE ||
glewGetExtension(
"GL_ARB_shader_objects"
) != GL_TRUE ||
glewGetExtension(
"GL_ARB_shading_language_100"
) != GL_TRUE)
{
fprintf(stderr,
"Driver does not support OpenGL Shading Language\n"
);
exit(1);
}
// Create the example object
g_pHello =
new
HelloGPGPU(512, 512);
----------------------------------------------------------------------------------------------------------------------------
在
HelloGPGPU
构造函数中,完成一系列的初始化工作。首先是生成一个
2D
纹理,用来保存数据。
glGenTextures(1, &_iTexture);
glBindTexture(GL_TEXTURE_2D, _iTexture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, _iWidth, _iHeight, 0, GL_RGB, GL_FLOAT, 0);
----------------------------------------------------------------------------------------------------------------------------
接下来进行
GLSL
初始化工作。到此所有的初始化工作基本上完成
_programObject = glCreateProgramObjectARB();
//
创建程序对象
//
创建片段着色器对象,这里没有创建顶点着色器对象,采用固定流水线
_fragmentShader = glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB);
glShaderSourceARB(_fragmentShader, 1, &edgeFragSource, NULL);
//
指定着色器源代码,这里
edgeFragSource
指定的字符串即为
GLSL Shader
代码
glCompileShaderARB(_fragmentShader);
//
编译
glAttachObjectARB(_programObject, _fragmentShader);
//
将片段着色器对象附加到程序对象上
//
连接程序对象,从而创建一个可以在可编程处理器上运行的程序
glLinkProgramARB(_programObject);
GLint progLinkSuccess;
glGetObjectParameterivARB(_programObject, GL_OBJECT_LINK_STATUS_ARB,
&progLinkSuccess);
//
检查程序是否链接成功
if
(!progLinkSuccess)
{
fprintf(stderr,
"Filter shader could not be linked\n"
);
exit(1);
}
//
返回一致变量
texUnit
的位置,以便在适当的地方进行设置
_texUnit = glGetUniformLocationARB(_programObject,
"texUnit"
);
----------------------------------------------------------------------------------------------------------------------------
初始化完毕后,每一遍渲染之前需要更新数据,这在
update
函数中完成:
首先是旋转的角度以及当窗口大小发生变化时视口的及时更新:
_rAngle += 0.5f;
int
vp[4];
glGetIntegerv(GL_VIEWPORT, vp);
glViewport(0, 0, _iWidth, _iHeight);
接下来是绘制一个
teapot
和
3
个
tori
glClear(GL_COLOR_BUFFER_BIT);
//
清除颜色缓存
glMatrixMode(GL_MODELVIEW);
//
下面准备要修改模型视点矩阵了
……
glPushMatrix();
glRotatef(-_rAngle, 0, 1, 0.25);
glutSolidTeapot(0.5);
glPopMatrix();
glPushMatrix();
glRotatef(2.1 * _rAngle, 1, 0.5, 0);
glutSolidTorus(0.05, 0.9, 64, 64);
glPopMatrix();
glPushMatrix();
glRotatef(-1.5 * _rAngle, 0, 1, 0.5);
glutSolidTorus(0.05, 0.9, 64, 64);
glPopMatrix();
glPushMatrix();
glRotatef(1.78 * _rAngle, 0.5, 0, 1);
glutSolidTorus(0.05, 0.9, 64, 64);
glPopMatrix();
将上述的绘制结果拷贝到纹理
iTexture
中(在
Initialize
中已经初始化,这个纹理将作为参数传递给片段着色器进行处理),这时候纹理中存储的图像是填充效果的(如下图)。
glBindTexture(GL_TEXTURE_2D, _iTexture);
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, _iWidth, _iHeight);
接下来要调用片段着色器进行
filter
处理,只获取边界进行绘制。当调用
glBegin…glEnd
之间的语句进行渲染矩形时,
GPU
中的片段处理器截获这些数据进行过滤操作,最后渲染到帧缓冲器。并卸载
GLSL
着色器。
glUseProgramObjectARB(_programObject);
glUniform1iARB(_texUnit, 0);
//
使用纹理单元
0
来存放
2D
纹理,这里进行加载
glBegin(GL_QUADS);
{
glTexCoord2f(0, 0); glVertex3f(-1, -1, -0.5f);
glTexCoord2f(1, 0); glVertex3f( 1, -1, -0.5f);
glTexCoord2f(1, 1); glVertex3f( 1, 1, -0.5f);
glTexCoord2f(0, 1); glVertex3f(-1, 1, -0.5f);
}
glEnd();
glUseProgramObjectARB(0);
随后再次拷贝帧缓冲器中的结果到纹理
iTexture
中,此时已经提取了图像的轮廓边。
glBindTexture(GL_TEXTURE_2D, _iTexture);
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, _iWidth, _iHeight);
恢复视口
glViewport(vp[0], vp[1], vp[2], vp[3]);
----------------------------------------------------------------------------------------------------------------------------
上面的整个流程其实就是:
现在我们有必要分析一下
GLSL
片段着色器代码是如何进行,这个
Shader
使用
Laplace
方法进行轮廓边检测,将当前象素的
RGBA
值减去相邻的
8
个象素的
RGBA
值的平均值,那么在
RGBA
值相近的区域内,这么做的结果使得该象素的
RGBA
值接近于
(0,0,0,0)
;而在边界附近,
RGBA
值有明显的突变,这么做的结果使得该象素的
RGBA
值很大,这个象素正位于轮廓边上。关于轮廓边检测的文档以及代码可以参考:
www.pages.drexel.edu/~weg22/edge.html
以及
http://www-scf.usc.edu/~flv/ipbook/chap07.htm
uniform sampler2D texUnit;
void main(void)
{
const float offset = 1.0 / 512.0;
vec2 texCoord = gl_TexCoord[0].xy;
//
在纹理单元
0
中获得当前纹理坐标
vec4 c = texture2D(texUnit, texCoord);
//
根据纹理坐标进行采样
vec4 bl = texture2D(texUnit, texCoord + vec2(-offset, -offset));
vec4 l = texture2D(texUnit, texCoord + vec2(-offset, 0.0));
vec4 tl = texture2D(texUnit, texCoord + vec2(-offset, offset));
vec4 t = texture2D(texUnit, texCoord + vec2( 0.0, offset));
vec4 ur = texture2D(texUnit, texCoord + vec2( offset, offset));
vec4 r = texture2D(texUnit, texCoord + vec2( offset, 0.0));
vec4 br = texture2D(texUnit, texCoord + vec2( offset, -offset));
vec4 b = texture2D(texUnit, texCoord + vec2( 0.0, -offset));
gl_FragColor = 8.0 * (c + -0.125 * (bl + l + tl + t + ur + r + br + b));
//8.0
用来增强轮廓边亮度
}
----------------------------------------------------------------------------------------------------------------------------
最后在
display
函数中简单的将纹理中的图像绘制出来就可以了。
idle
函数中有
glutPostRedisplay();
会激发
display
函数,并调用
HelloGPGPU
中的
update
函数和
display
函数,从而将保存在纹理中的数据绘制出来。
HelloGPGPU
中的
display
函数内容如下:
glBindTexture(GL_TEXTURE_2D, _iTexture);
glEnable(GL_TEXTURE_2D);
glBegin(GL_QUADS);
{
glTexCoord2f(0, 0); glVertex3f(-1, -1, -0.5f);
glTexCoord2f(1, 0); glVertex3f( 1, -1, -0.5f);
glTexCoord2f(1, 1); glVertex3f( 1, 1, -0.5f);
glTexCoord2f(0, 1); glVertex3f(-1, 1, -0.5f);
}
glEnd();
glDisable(GL_TEXTURE_2D);
----------------------------------------------------------------------------------------------------------------------------
from : http://blog.csdn.net/chenrongqin/archive/2007/04/12/1562312.aspx