GLSL

本文的目的是为了介绍GPU编程的背景及在GPU上运算所需要的步骤,借助GLSL实现GPU编程。

环境准备

本文介绍GLSL的使用,因此需要OpenGL库,具体如下:

GL: 即核心库,包含 了全 部 核 心 的 OpenGL 函数。所 有 以 g l为前缀的函数都是属于G L 库 的 ,比如各种模函数、坐标变换函数、光照函数、纹理映射函数等,共有一百多个.

GLU: 即 实 用 函 数 库 (OpenGL UtilityL ib rary ),它包含了用核心庳编写的新函数,提供比核心库更高一层函数接口,使用更为方便 。实用函数库共有四十多个函数,均 以 glu 开头。

GLUT: (OpenGL Utility Toolkit), 是一个与窗 口系统无关的工具包,以 隐 藏 不 同 窗 口 系 统 A P I的复杂性.

GLEW: OpenGL扩展库是个简单的工具,用于帮助C/C++开发者初始化扩展(OpenGL扩展功能)并书写可移植的应用程序。GLEW当前支持各种各样的操作系统,包含Windows,Linux,Darwin,Irix与Solaris。

第一个程序

第一个简单的程序用来延时OpenGl的工作流程。

Code

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <GL/glew.h>
#include <GL/glut.h>

GLuint v, f, p;
float lpos[4] = { 1, 0.5, 1, 0 };
float a = 0;

void onRendering(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();
    gluLookAt(0.0, 0.0, 5.0, 0.0, 0.0, -1.0, 0.0, 1.0, 0.0);
    glLightfv(GL_LIGHT0, GL_POSITION, lpos);
    glRotatef(a, 0, 1, 1);
    glutSolidTeapot(1);
    a += 0.1;
    glutSwapBuffers();
}

void onReshape(int w, int h)
{
    if (h == 0) {
        h = 1;
    }
    float ratio = 1.0 * w / h;
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glViewport(0, 0, w, h);
    gluPerspective(45, ratio, 1, 1000);
    glMatrixMode(GL_MODELVIEW);
    onRendering();
}

int main(int argc, const char **argv)
{
    const int width = 800;
    const int height = 600;
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    glutInitWindowPosition((glutGet(GLUT_SCREEN_WIDTH) - width) / 2, (glutGet(GLUT_SCREEN_HEIGHT) - height) / 2);
    glutInitWindowSize(width, height);
    int windows = glutCreateWindow("opengl");
    glutDisplayFunc(onRendering);
    glutIdleFunc(onRendering);
    glutReshapeFunc(onReshape);
    glEnable(GL_DEPTH_TEST);
    glClearColor(0.2, 0.2, 0.2, 1.0);
    glColor3f(1.0, 1.0, 1.0);

    glewInit();

    glutMainLoop();
    glutDestroyWindow(windows);
    return 0;
}

这个例子中的着色器在哪呢 ?既然这个例子足个完整的OpenGL程, 那么它也一定是一个完整的形流水线的实现。下而通过分析程序中数据处理的流程,让例子所对应的图形流水线的每个步骤(包括着色器部分)水落石出。

应用程序生成了由一系列顶点坐标描述的三维模型,并将它们送至图形流水线的入口。glutSolidTeapot ( ) 完成了这项工作.

顶点着色器负责逐顶点计算,即求它们的位移、旋转和投影变换。这个例程使用了变换观察点的方法达到了使模 型看上去旋转的目的。因此顶点着色器处理的是观察点的坐标 。

光栅化器把投影在二维平面上的点转化为离散的像素点,它决定帧缓存里的每个像 素该取什么值。

片段卷色器负责逐像素计算,即在绘制二维图形时给每个帧缓存像素赋值。

帧缓存是最终图像被绘制完成并可以输出显示的地方。

着色器

如果用户不使用自定义的着色器,则 OpenGL将使用默认的着色器,实现基本的渲染功能 。 能够实现这样的基本渲染功能的着色器 ,可以被认为是一个最小着色器 (minimum shader)毫无疑问,用户也可以通过GLSL编写一个和最小着色器作用相同的着色器,从而达OpenGL默认的渲染效果。注意 ,一旦用户定义了自己的着色器,那么这个着色器必须保证完成它应完成的所有工作,即,它必须具有完整性。比如,如果我们用自定义的顶点着色器修改了顶点的颜色,那么我们还必须完成顶点的模型视图变换和投影变换。

顶点着色器

void main(void)
{
    gl_Position = gl_ProjectionMatrix * gl_modelViewMatrix * gl_vertex;
}

片段着色器

void main(void)
{
    gl_FragColor = gl_FrontColor;
}

通常情况下,为了方便编译,着色器程序都写在单独的文本文件里。当然,对于短小 的着色器,用户也可以选择用字符串的形式来把它们定义在OpenGL应用程序中。但这样做 的问题是,一旦着色器被修改,即使 OpenGL程序没有改变,整个文件都必须重新编译。在 用GLSL编写着色器时,一 般 OpenGL应用程序已经完成,用户需要边修改边调试着色器的 效果 ,使用独立的文件存储着色器程序能够使用户在每次修改着色器程序后不用专门编译着 色器程序,只需要执行OpenGL应用程序即可(定点着色器.vert, 片段着色器.frag)。

Code

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <GL/glew.h>
#include <GL/glut.h>

GLuint v, f, p;
float lpos[4] = { 1, 0.5, 1, 0 };
float a = 0;
GLint time_id;

void onRendering(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();
    gluLookAt(0.0, 0.0, 5.0, 0.0, 0.0, -1.0, 0.0, 1.0, 0.0);
    glLightfv(GL_LIGHT0, GL_POSITION, lpos);
    glRotatef(a, 0, 1, 1);
    glutSolidTeapot(1);
    a += 0.1;
    glUniform1f(time_id, a);
    glutSwapBuffers();
}

void onReshape(int w, int h)
{
    if (h == 0) {
        h = 1;
    }
    float ratio = 1.0 * w / h;
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glViewport(0, 0, w, h);
    gluPerspective(45, ratio, 1, 1000);
    glMatrixMode(GL_MODELVIEW);
    onRendering();
}

void setShaders(void)
{
    char * vert_shader =
        "attribute vec4 vPositon;  \n"
        "attribute vec4 aColor;    \n"
        "varying vec4 vertexColor; \n"
        "void main() {             \n"
        "   gl_Position = gl_ModelViewProjectionMatrix * vPositon; \n"
        "}"
        ;

    char * frag_shader =
        "uniform float v_time;"
        "void main() {"
        "    float r = 0.9 * sin(0.0 + v_time * 0.05) + 1.0;"
        "    float g = 0.9 * cos(0.33 + v_time * 0.05) + 1.0;"
        "    float b = 0.9 * sin(0.67 + v_time * 0.05) + 1.0;"
        "    gl_FragColor = vec4(r/2.0, g/2.0, b/2.0, 1.0);"
        "}"
        ;
    v = glCreateShader(GL_VERTEX_SHADER);
    f = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(v, 1, &vert_shader, NULL);
    glShaderSource(f, 1, &frag_shader, NULL);
    glCompileShader(v);
    glCompileShader(f);

    p = glCreateProgram();
    glAttachShader(p, v);
    glAttachShader(p, f);
    glLinkProgram(p);
    glUseProgram(p);

    time_id = glGetUniformLocation(p, "v_time");
}

int main(int argc, const char **argv)
{
    const int width = 800;
    const int height = 600;
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    glutInitWindowPosition((glutGet(GLUT_SCREEN_WIDTH) - width) / 2, (glutGet(GLUT_SCREEN_HEIGHT) - height) / 2);
    glutInitWindowSize(width, height);
    int windows = glutCreateWindow("opengl");
    glutDisplayFunc(onRendering);
    glutIdleFunc(onRendering);
    glutReshapeFunc(onReshape);
    glEnable(GL_DEPTH_TEST);
    glClearColor(0.2, 0.2, 0.2, 1.0);
    glColor3f(1.0, 1.0, 1.0);

    glewInit();
    setShaders();
    glutMainLoop();
    glutDestroyWindow(windows);
    return 0;
}


GLSL 语法

变量

GLSL的变量命名方式与C语言类似。变量的名称可以使用字母,数字以及下划线,但变量名不能以数字开头,还有变量名不能以gl_作为前缀,这个是GLSL保留的前缀,用于GLSL的内部变量。当然还有一些GLSL保留的名称是不能够作为变量的名称的。

基本类型

除了布尔型,整型,浮点型基本类型外,GLSL还引入了一些在着色器中经常用到的类型作为基本类型。这些基本类型都可以作为结构体内部的类型。如下表:

类型 描述
void 跟C语言的void类似,表示空类型。作为函数的返回类型,表示这个函数不返回值。
bool 布尔类型,可以是true 和false,以及可以产生布尔型的表达式。
int 整型 代表至少包含16位的有符号的整数。可以是十进制的,十六进制的,八进制的。
float 浮点型
bvec2 包含2个布尔成分的向量
bvec3 包含3个布尔成分的向量
bvec4 包含4个布尔成分的向量
ivec2 包含2个整型成分的向量
ivec3 包含3个整型成分的向量
ivec4 包含4个整型成分的向量
mat2 或者 mat2x2 2×2的浮点数矩阵类型
mat3或者mat3x3 3×3的浮点数矩阵类型
mat4x4 4×4的浮点矩阵
mat2x3 2列3行的浮点矩阵(OpenGL的矩阵是列主顺序的)
mat2x4 2列4行的浮点矩阵
mat3x2 3列2行的浮点矩阵
mat3x4 3列4行的浮点矩阵
mat4x2 4列2行的浮点矩阵
mat4x3 4列3行的浮点矩阵
sampler1D 用于内建的纹理函数中引用指定的1D纹理的句柄。只可以作为一致变量或者函数参数使用
sampler2D 二维纹理句柄
sampler3D 三维纹理句柄
samplerCube cube map纹理句柄
sampler1DShadow 一维深度纹理句柄
sampler2DShadow 二维深度纹理句柄

结构体

结构体可以组合基本类型和数组来形成用户自定义的类型。在定义一个结构体的同时,你可以定义一个结构体实例。或者后面再定义。

struct surface {
    float indexOfRefraction;
    vec3 color;float turbulence;
} mySurface;

surface secondeSurface;

你可以通过=为结构体赋值,或者使用 ==,!=来判断两个结构体是否相等。

mySurface = secondSurface;
mySurface == secondSurface;

只有结构体中的每个成分都相等,那么这两个结构体才是相等的。访问结构体的内部成员使用. 来访问。

vec3 color = mySurface.color + secondSurface.color;

结构体至少包含一个成员。固定大小的数组也可以被包含在结构体中。GLSL的结构体不支持嵌套定义。只有预先声明的结构体可以嵌套其中。

struct myStruct {
  vec3 points[3]; //固定大小的数组是合法的
  surface surf;  //可以,之前已经定义了
  struct velocity {  //不合法float speed;
    vec3 direction;
  } velo;
  subSurface sub; //不合法,没有预先声明;
};

数组

GLSL中只可以使用一维的数组。数组的类型可以是一切基本类型或者结构体。下面的几种数组声明是合法的:

surface mySurfaces[];
vec4 lightPositions[8];
vec4 lightPos[] = lightPositions;const int numSurfaces = 5;
surface myFiveSurfaces[numSurfaces];float[5] values;

指定显示大小的数组可以作为函数的参数或者使返回值,也可以作为结构体的成员.数组类型内建了一个length()函数,可以返回数组的长度。

lightPositions.length() //返回数组的大小 8

最后,你不能定义数组的数组。

修饰符

变量的声明可以使用如下的修饰符。

修饰符 描述
const 常量值必须在声明是初始化。它是只读的不可修改的。
attribute 表示只读的顶点数据,只用在顶点着色器中。数据来自当前的顶点状态或者顶点数组。它必须是全局范围声明的,不能再函数内部。一个attribute可以是浮点数类型的标量,向量,或者矩阵。不可以是数组或则结构体
uniform 一致变量。在着色器执行期间一致变量的值是不变的。与const常量不同的是,这个值在编译时期是未知的是由着色器外部初始化的。一致变量在顶点着色器和片段着色器之间是共享的。它也只能在全局范围进行声明。
varying 顶点着色器的输出。例如颜色或者纹理坐标,(插值后的数据)作为片段着色器的只读输入数据。必须是全局范围声明的全局变量。可以是浮点数类型的标量,向量,矩阵。不能是数组或者结构体。
centorid varying 在没有多重采样的情况下,与varying是一样的意思。在多重采样时,centorid varying在光栅化的图形内部进行求值而不是在片段中心的固定位置求值。
invariant (不变量)用于表示顶点着色器的输出和任何匹配片段着色器的输入,在不同的着色器中计算产生的值必须是一致的。所有的数据流和控制流,写入一个invariant变量的是一致的。编译器为了保证结果是完全一致的,需要放弃那些可能会导致不一致值的潜在的优化。除非必要,不要使用这个修饰符。在多通道渲染中避免z-fighting可能会使用到。
in 用在函数的参数中,表示这个参数是输入的,在函数中改变这个值,并不会影响对调用的函数产生副作用。(相当于C语言的传值),这个是函数参数默认的修饰符
out 用在函数的参数中,表示该参数是输出参数,值是会改变的。
inout 用在函数的参数,表示这个参数即是输入参数也是输出参数。

内置变量

内置变量可以与固定函数功能进行交互。在使用前不需要声明。顶点着色器可用的内置变量如下表:

名称 类型 描述
gl_Color vec4 输入属性-表示顶点的主颜色
gl_SecondaryColor vec4 输入属性-表示顶点的辅助颜色
gl_Normal vec3 输入属性-表示顶点的法线值
gl_Vertex vec4 输入属性-表示物体空间的顶点位置
gl_MultiTexCoordn vec4 输入属性-表示顶点的第n个纹理的坐标
gl_FogCoord float 输入属性-表示顶点的雾坐标
gl_Position vec4 输出属性-变换后的顶点的位置,用于后面的固定的裁剪等操作。所有的顶点着色器都必须写这个值。
gl_ClipVertex vec4 输出坐标,用于用户裁剪平面的裁剪
gl_PointSize float 点的大小
gl_FrontColor vec4 正面的主颜色的varying输出
gl_BackColor vec4 背面主颜色的varying输出
gl_FrontSecondaryColor vec4 正面的辅助颜色的varying输出
gl_BackSecondaryColor vec4 背面的辅助颜色的varying输出
gl_TexCoord[] vec4 纹理坐标的数组varying输出
gl_FogFragCoord float 雾坐标的varying输出

片段着色器的内置变量如下表:

名称 类型 描述
gl_Color vec4 包含主颜色的插值只读输入
gl_SecondaryColor vec4 包含辅助颜色的插值只读输入
gl_TexCoord[] vec4 包含纹理坐标数组的插值只读输入
gl_FogFragCoord float 包含雾坐标的插值只读输入
gl_FragCoord vec4 只读输入,窗口的x,y,z和1/w
gl_FrontFacing bool 只读输入,如果是窗口正面图元的一部分,则这个值为true
gl_PointCoord vec2 点精灵的二维空间坐标范围在(0.0, 0.0)到(1.0, 1.0)之间,仅用于点图元和点精灵开启的情况下。
gl_FragData[] vec4 使用glDrawBuffers输出的数据数组。不能与gl_FragColor结合使用。
gl_FragColor vec4 输出的颜色用于随后的像素操作
gl_FragDepth float 输出的深度用于随后的像素操作,如果这个值没有被写,则使用固定功能管线的深度值代替

表达式

操作符

GLSL语言的操作符与C语言相似。如下表(操作符的优先级从高到低排列)

操作符 描述
() 用于表达式组合,函数调用,构造
[] 数组下标,向量或矩阵的选择器
. 结构体和向量的成员选择
++ – 前缀或后缀的自增自减操作符
+ – ! 一元操作符,表示正 负 逻辑非
* / 乘 除操作符
+ - 二元操作符 表示加 减操作
<> <= >= == != 小于,大于,小于等于, 大于等于,等于,不等于 判断符
&& || ^^ 逻辑与 ,或, 异或
?: 条件判断符
= += –= *= /= 赋值操作符
, 表示序列

像求地址的& 和 解引用的 * 操作符不再GLSL中出现,因为GLSL不能直接操作地址。类型转换操作也是不允许的。 位操作符(&,|,^,~, <<, >> ,&=, |=, ^=, <<=, >>=)是GLSL保留的操作符,将来可能会被使用。还有求模操作(%,%=)也是保留的。

数组访问

数组的下标从0开始。合理的范围是[0, size - 1]。跟C语言一样。如果数组访问越界了,那行为是未定义的。如果着色器的编译器在编译时知道数组访问越界了,就会提示编译失败。

vec4 myColor, ambient, diffuse[6], specular[6];
myColor = ambient + diffuse[4] + specular[4];

构造函数

构造函数可以用于初始化包含多个成员的变量,包括数组和结构体。构造函数也可以用在表达式中。调用方式如下:

vec3 myNormal = vec3(1.0, 1.0, 1.0);
greenTint = myColor + vec3(0.0, 1.0, 0.0);
ivec4 myColor = ivec4(255);

还可以使用混合标量和向量的方式来构造,只要你的元素足以填满该向量。

vec4 color = vec4(1.0, vec2(0.0, 1.0), 1.0);
vec3 v = vec3(1.0, 10.0, 1.0);
vec3 v1 = vec3(v);
vec2 fv = vec2(5.0, 6.0);
float f = float(fv); //用x值2.5构造,y值被舍弃

对于矩阵,OpenGL中矩阵是列主顺序的。如果只传了一个值,则会构造成对角矩阵,其余的元素为0.

mat3 m3 = mat3(1.0);

构造出来的矩阵式:

1.0 0.0 0.0
0.0 1.0 0.0
0.0 0.0 1.0

mat2 matrix1 = mat2(1.0, 0.0, 0.0, 1.0);
mat2 matrix2 = mat2(vec2(1.0, 0.0), vec2(0.0, 1.0));
mat2 matrix3 = mat2(1.0); 
mat2 matrix4 = mat2(mat4(2.0)); //会取 4×4矩阵左上角的2×2矩阵。

构造函数可以用于标量数据类型的转换。GLSL不支持隐式或显示的转换,只能通过构造函数来转。其中int转为float值是一样的。float转为int则小数部分被丢弃。int或float转为bool,0和0.0转为false,其余的值转为true. bool转为int或float,false值转为0和0.0,true转为1和1.0.

float f = 1.7;
int I = int(f); // I = 1

数组的初始化,可以在构造函数中传入值来初始化数组中对应的每一个值。

ivec2 position[3] = ivec2[3]((0,0), (1,1), (2,2));
ivec2 pos2[3] = ivec2[]((3,3), (2,1), (3,1));

构造函数也可以对结构体进行初始化。其中顺序和类型要一一对应。

struct surface {  int  index;
  vec3 color;  float rotate;
};
surface mySurface = surface(3, vec3(red, green, blue), 0.5);

成分选择

向量中单独的成分可以通过{x,y,z,w},{r,g,b,a}或者{s,t,p,q}的记法来表示。这些不同的记法用于顶点,颜色,纹理坐标。在成分选择中,你不可以混合使用这些记法。其中{s,t,p,q}中的p替换了纹理的r坐标,因为与颜色r重复了。下面是用法举例:

vec3 myVec = {0.5, 0.35, 0.7};float r = myVec.r;float myYz = myVec.yz;float myQ = myVec.q;//出错,数组越界访问,q代表第四个元素float myRY = myVec.ry; //不合法,混合使用记法

较特殊的使用方式,你可以重复向量中的元素,或者颠倒其顺序。如:

vec3 yxz = myVec.yxz; //调换顺序vec4 mySSTT = myVec.sstt; //重复其中的值

在赋值时,也可以选择你想要的顺序,但是不能重复其中的成分。

vec4 myColor = {0.0, 1.0, 2.0, 1.0};
myColor.x = -1.0;
myColor.yz = vec2(3.0, 5.0);
myColor.wx = vec2(1.0, 3.0);
myColor.zz = vec2(2.0, 3.0); //不合法

我们也可以通过使用下标来访问向量或矩阵中的元素。如果越界那行为将是未定义的。

float myY = myVec[1];

在矩阵中,可以通过一维的下标来获得该列的向量(OpenGL的矩阵是列主顺序的)。二维的小标来获得向量中的元素。

mat3 myMat = mat3(1.0);
vec3 myVec = myMat[0]; //获得第一列向量 1.0, 0.0, 0.0float f = myMat[0][0]; // 第一列的第一个向量。

控制流

循环

与C和C++相似,GLSL语言也提供了for, while, do/while的循环方式。使用continue跳入下一次循环,break结束循环。

for (l = 0; l < numLights; l++)
{
    if (!lightExists[l])continue;
    color += light[l];
}

while (i < num)
{
    sum += color[i];
    i++;
}

do{
    color += light[lightNum];
    lightNum--;
}while (lightNum > 0)

if/else

color = unlitColor;
if (numLights > 0)
{
    color = litColor;
}else{
    color = unlitColor;
}

discard

片段着色器中有一种特殊的控制流成为discard。使用discard会退出片段着色器,不执行后面的片段着色操作。片段也不会写入帧缓冲区。

if (color.a < 0.9){
    discard;
}

函数

在每个shader中必须有一个main函数。main函数中的void参数是可选的,但返回值是void时必须的。

void main(void)
{
    ...
}

GLSL中的函数,必须是在全局范围定义和声明的。不能在函数定义中声明或定义函数。函数必须有返回类型,参数是可选的。参数的修饰符(in, out, inout, const等)是可选的。

结构体和数组也可以作为函数的参数。如果是数组作为函数的参数,则必须制定其大小。在调用传参时,只传数组名就可以了。

vec4 sumVectors(int sumSize, vec4 v[10]);void main()
{
    vec4 myColors[10];
    ...
    vec4 sumColor = sumVectors(5, myColors);
}

vec4 sumVectors(int sumSize, vec4 v[10])
{int i = 0;
    vec4 sum = vec4(0.0);for(; i < sumSize; ++i)
    {
        sum += v[i]; 
    }return sum;
}

GLSL的函数是支持重载的。函数可以同名但其参数类型或者参数个数不同即可。

float sum(float a, float b)
{
    return a + b;
}

vec3 sum(vec3 v1, vec3 v2)
{
    return v1 + v2;
}

GLSL中函数递归是不被允许的。其行为是未定义的。

GLSL中提供了许多内建的函数,来方便我们的使用。可以在官方手册中查找相关的函数

http://www.opengl.org/sdk/docs/man/

Compute Shader

在计算着色器中,任务以组为单位进行执行,我们称之为工作组(work group)。拥有邻居的工作组被称为本地工作组(local workgroup), 这些组可以组成更大的组,称为全局工作组(global workgroup),而其通常作为执行命令的一个单位。计算着色器会被每个本地工作组中的每个单元调用一次。工作组的每一个单元称为工作项(work item),每次调用称为一次执行。执行的单元之间可以通过变量和显存进行通信,且可以通过执行同步操作保持一致性。图显示了一个全局工作组。这个全局工作组包括16个本地工作组,每个本地工作组又包括16个执行单元,排成4X4的网格,每个执行单元拥有一个二维向量表示的索引值。尽管图示中,全局和本地工作组都是2维的,而事实上它们都是3维的,为了适应1维、2维的任务,只需把额外的2维或1维设为0即可。计算着色器的每个执行单元本质是相互独立的,可以并行地在支持OpenGL地GPU上执行。

/**
******************************************************************************
* @file
* @author  maky <chengwei920412@outlook.com>
* @version
* @date    2019-02-20 15::18::38
* @brief
******************************************************************************
* @attention
*
*
******************************************************************************
*/
#include <iostream>
#include <opencv2/opencv.hpp>
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <GL/glut.h>

#define SHADER_STRINGIFY(x) # x 

char shader_source[] = SHADER_STRINGIFY(
#version 430 core \n
layout(local_size_x = 16, local_size_y = 16) in;
uniform float kernel[4];
layout(rgba32f, binding = 0) uniform image2D input_image;
layout(rgba32f, binding = 1) uniform image2D output_image;
shared vec4 scanline[16][16];

void main(void)
{
    ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
    scanline[pos.x][pos.y] = imageLoad(input_image, pos);
    barrier();
    vec4 data = scanline[pos.x][pos.y];
    data.r = kernel[0] + data.r;
    data.g = kernel[1] + data.g;
    data.b = kernel[2] + data.b;
    data.a = kernel[3] + data.a;
    imageStore(output_image, pos.xy, data);
}\n

);

int main(int argc, char **argv) {
    int width = 16, height = 16;
    int size = width * height * 4;
    float *input = new float[size];
    float *output = new float[size];
    for (auto pos = 0; pos < size; pos++) {
        input[pos] = 0.001f;
    }
    GLfloat kernel[4];
    for (auto pos = 0; pos < 4; pos++) {
        kernel[pos] = pos;
    }
    // init opengl
    glutInit(&argc, argv);
    auto window_handler = glutCreateWindow("gpgpu");
    glewInit();

    // frame buffer
    GLuint frame_buffer_id;
    // create FBO (off-screen framebuffer)
    glGenFramebuffers(1, &frame_buffer_id);
    // bind offscreen framebuffer (that is, skip the window-specific render target)
    glBindFramebuffer(GL_FRAMEBUFFER, frame_buffer_id);

    // input output textures
    GLuint output_texture_id;
    GLuint intermediate_texture_id;
    GLuint input_texture_id;

    glGenTextures(1, &output_texture_id);
    glGenTextures(1, &intermediate_texture_id);
    glGenTextures(1, &input_texture_id);
    {
        // make active and bind
        glBindTexture(GL_TEXTURE_2D, output_texture_id);
        glTexStorage2D(GL_TEXTURE_2D, 8, GL_RGBA32F, 16, 16);
        // turn off filtering and wrap modes
        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);
        // define texture with floating point format
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, width, height, 0, GL_RGBA, GL_FLOAT, nullptr);
    }
    {
        // make active and bind
        glBindTexture(GL_TEXTURE_2D, intermediate_texture_id);
        glTexStorage2D(GL_TEXTURE_2D, 8, GL_RGBA32F, 16, 16);
        // turn off filtering and wrap modes
        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);
        // define texture with floating point format
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, width, height, 0, GL_RGBA, GL_FLOAT, nullptr);
    }
    {
        // make active and bind
        glBindTexture(GL_TEXTURE_2D, input_texture_id);
        glTexStorage2D(GL_TEXTURE_2D, 8, GL_RGBA32F, 16, 16);
        // turn off filtering and wrap modes
        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);
        // define texture with floating point format
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, width, height, 0, GL_RGBA, GL_FLOAT, nullptr);
    }

    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, input_texture_id, 0);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, intermediate_texture_id, 0);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_2D, output_texture_id, 0);

    // transfer to texture
    glBindTexture(GL_TEXTURE_2D, input_texture_id);
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_FLOAT, input);
    // set texenv mode
    //glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

    // shader
    GLuint program = glCreateProgram();
    // create shader object (fragment shader)
    GLuint shader = glCreateShader(GL_COMPUTE_SHADER);
    // set source for shader
    const GLchar *source = shader_source;
    glShaderSource(shader, 1, &source, nullptr);
    // compile shader
    glCompileShader(shader);

    // attach shader to program
    glAttachShader(program, shader);
    // link into full program, use fixed function vertex shader.
    // you can also link a pass-through vertex shader.
    glLinkProgram(program);

    // compute
    {
        glUseProgram(program);
        // enable the read-only texture x
        //glActiveTexture(GL_TEXTURE0);
        glUniform1fv(glGetUniformLocation(program, "kernel"), 4, kernel);

        // Synchronize for the timing reason.
        glFinish();

        glBindImageTexture(0, input_texture_id, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA32F);
        glBindImageTexture(1, intermediate_texture_id, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA32F);
        glDispatchCompute(1, 1, 1);

        glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);

        glFinish();
    }
    {
        glUseProgram(program);
        // enable the read-only texture x
        //glActiveTexture(GL_TEXTURE0);
        glUniform1fv(glGetUniformLocation(program, "kernel"), 4, kernel);

        // Synchronize for the timing reason.
        glFinish();

        glBindImageTexture(0, intermediate_texture_id, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA32F);
        glBindImageTexture(1, output_texture_id, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA32F);
        glDispatchCompute(1, 1, 1);

        glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);

        glFinish();
    }

    // transfer from texture
    glReadBuffer(GL_COLOR_ATTACHMENT2);
    glReadPixels(0, 0, width, height, GL_RGBA, GL_FLOAT, output);
    for (int pos = 0; pos < size; pos++) {
        std::cout << "input:" << input[pos] << " output:" << output[pos] << std::endl;
    }


    glDetachShader(program, shader);
    glDeleteShader(shader);
    glDeleteProgram(program);
    glDeleteFramebuffersEXT(1, &frame_buffer_id);
    glDeleteTextures(1, &input_texture_id);
    glDeleteTextures(1, &output_texture_id);

    delete input;
    delete output;
    return 0;
}

Reference

http://www.opengl-tutorial.org/cn/

https://learnopengl-cn.github.io/intro/

https://github.com/wshxbqq/GLSL-Card

http://www.opengl.org/registry/doc/GLSLangSpec.Full.1.20.8.pdf

http://www.opengl.org/sdk/docs/man/

http://www.gpgpu.org/

GPGPU编程技术 从GLSL、CUDA到OpenCL.pdf

OpenGL编程指南(原书第8版)