GPU通用计算——从Hello GPGPU开始

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
mic与gpu对比,GPGPU与MIC定位相似,两者都是相对于CPU具有较高性价比的高性能解决方案,甚至连外形都是一样使用PCI-E插槽的板卡。但对于“核”这个概念来说,两者却有很大的不同。GPGPU中所说的核,以CUDA为例,是指一个SP(即流处理器),SP的功能只有计算,以NVIDIA的Fermi GPU为例,32个SP组成一个SM(流处理器群),一个SM 才有两个控制单元。也就是说每16个GPU的“核”,必须执行同一条指令。而MIC得设计思路与GPGPU完全不同。MIC的每个“核”,可以简单看作一个X86核心,也就是与现有PC机或小型服务器上的CPU核心相同的核。因此MIC编程可以最大限度地沿袭已有CPU上的并行程序,甚至可以一定程度上认为MIC上的每个“核”都是独立的节点,亦即将MIC作为一个超小型的集群。MIC的“核”虽然是x86架构,虽然单核的功能比GPGPU的核强大不少,但要指望单兵作战接近主流CPU,暂时还是不现实的。MIC依靠和GPGPU一样,靠人海战术。说起“人数”,GPGPU动辄上百核,MIC只有几十核,几十单核性能再强,在并行应用中也掀不起多少浪花。由于MIC的核心是Intel的CPU,核心数即使上不去了,Intel处理器可以超线程。在MIC上,每个核心能同时并发执行4个线程,而且这4个线程被Intel成为“硬件线程”,其性能大幅提升,几乎可以把每个线程看作真正的核心。因此,MIC“执行核”的数量,核GPGPU实际差不多。 另外,MIC采用了SMP结构,以一致性共享缓存为中心,这种设计使得MIC可以使用传统CPU的编程模型,而不需要针对性的硬件,设计新的程序结构。 MIC对现有程序改动之小还体现在编程简易性和工具方面。编程简易性上,MIC常用的offload模式只需要加上少数几条编译指导语句,就可以使程序利用MIC进行运算,而此时的程序源代码,是可以与传统的CPU程序共用的,减少了维护成本。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值