1.工作组及其执行

compute shader是在OpenGL4.3(Opengl es 3.1)以后引入的一种专门用于并行计算的着色器。在计算着色器中,任务以组为单位进行执行,我们称之为工作组(work group)。拥有邻居的工作组被称为本地工作组(local workgroup), 这些组可以组成更大的组,称为全局工作组(global workgroup),而其通常作为执行命令的一个单位。

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

大部分OpenGL硬件会将这些执行单元打包成较小地集合(lockstep),然后将这些小集合拼成本地工作组。本地工作组的大小在计算着色器的代码中输入布局限定符莱设置。全局工作组的大小则是本地工作组大小的整数倍。当计算着色器执行时,它可以通过内置变量来知道当前在本地工作组中的相对坐标、本地工作组的大小,及本地工作组在全局工作组中的相对坐标。基于这些还能进一步获得执行单元在全局工作组红的坐标。着色器根据这些变量来决定应该负责计算任务中的哪些部分,同时也能知道一个工作组中的其他执行单元,以便共享数据。

通过布局限定符在计算着色器中声明本地工作组的大小,分别使用local_size_x,local_size_y,local_size_z,它们的默认值为1.如忽略local_size_z,就会创建一个NXM的2维组。如声明一个本地工作组大小为16x16的着色器。

#version 430 core
layout (local_size_x = 16, local_size_y = 16) in;
void main(void){...
}

当创建并链接一个计算着色器后,就可以通过glUseProgram将它设置为当前要执行的程序,然后用glDispatchCompute将工作组发送到计算管线上。

void glDispatchCompute(GLuint num_group_x, GLuint num_group_y, GLuint num_group_z);
在3个维度上分发执行计算工作组,num_group_x、num_group_y和num_group_z分别设置工作组在X、Y、Z维度上的数量。
每个参数均需大于0,小于或等于一个与设备相关的常量数组GL_MAX_COMPUTE_WORK_GROUP_SIZE的对应元素。

2.知道工作组的位置

当执行计算着色器时,它可能需要对输入数组的多个单元赋值,或者需要读取输入数组的特定位置的数据。因此计算着色器需要知道当前处于本地和全局工作组的具体位置。这是坐标是通过OpenGL的一组内置变量获得的。

  • gl_WorkGroupSize是一个用于存储本地工作组大小的常数
  • gl_NumWorkGroups是一个向量,它包含传给glDispatchCompute的参数(num_group_x、num_group_y、num_group_z)
  • gl_LocalInvocationID表示当前执行单元在本地工作组中的位置。它的范围从uvec3(0)到gl_WorkGroupSize-uvec3(1)
  • gl_WorkGroupID表示当前本地工作组在全局工作组中的位置。该变量的范围在uvec3(0)和gl_NumWorkGroups-uvec3(1)之间
  • gl_GlobalInvocationID由gl_LocalInvocationID、gl_WorkGroupSize和gl_WorkGroupID派生而来。它的准确值是gl_WorkGroupID *gl_WorkGroupSize + gl_LocalInvocationID,所以它是当前执行单元在全局工作组中的位置的一种有效的3维索引。
  • gl_LocalInvocationIndex是gl_LocalInvocationID的一种扁平形式。其值等于gl_LocalInvocationID.z * gl_WorkGroupSize.x * gl_WorkGroupSize.y + gl_LocalInvocationID.y * gl_WorkGroupSize.x + gl_LocalInvocationID.x。它可以用1维的索引来代表2维或3维的数据。

3.通信

在计算着色器中可以使用shared关键字来声明着色器变量,其格式与其他关键字uniform等类似。

//一个共享的无符号整型变量
shared uint foo;
//一个共享的向量数组
shared vec4 bar[128];
//一个共享的数据块
shared struct baz_struct{vec4 a_vector;int an_integer;ivec2 an_array_of_integers[27];
}baz[42];

一个变量被声明维shared,那么它将被保存到特定的位置,从而对同一个本地工作组内所有计算着色器可见。如果某个计算着色器请求对共享变量进行写入,那么这个数据的修改信息将最终通知给同一个本地工作组的所有着色器。通常访问共享shared变量的性能会远好于访问图像或者着色器存储缓存(如主内存)的性能。因为着色器会将共享内存作为局部量处理,并且可以在设备中进行拷贝,所以访问共享变量可能比使用缓冲区的方法更迅速。因此,如果着色器需要对同一处内存进行大量的访问,优先考虑将内存拷贝到共享变量中,然后操作。

4.同步

如果本地工作组请求的执行顺序,以及全局工作组中所有本地工作组的执行顺序都没有定义,那么请求执行操作的时机与其他请求完全无关。如果请求之间不需要相互通信,只需要完全独立执行,那么没有问题。但如果请求之间需要进行通信,无论是通过图像,缓存还是共享内存,那么我们就有必要对它们的操作进行同步处理。

同步命令有两种。首先是运行屏障(execution barrier),可以通过barrier()函数触发。如果计算着色器的一个请求遇到barrier,那么它会停止运行,等待同一个本地工作组的所有请求也到达barrier,然后才会执行后面的代码。

第二种同步叫做内存屏障(memory barrier)。它的最直接版本就是memoryBarrier()。如果调用memoryBarrier,那么久可以保证着色器请求内存的写入操作一定是提交到内存端,而不是通过缓冲区(cache)或者调度队列之类的方式。所有发生在memoryBarrier之后的操作在读取同一处内存时,都可以使用这些内存写入的结果,即使同一个计算着色器的其他请求也是如此。

5.例子

本例运行环境为ubuntu(非虚拟机,虚拟机一运行compute shader相关的代码就报 exit code 139),需要先安装好OpenGL环境,GLUT和GLEW。在这个例子中,会先产生一个 1024 个数据,并将这些数据赋予一个 16 x 16 x 4 的image2D,然后通过计算着色器对这个image2D的每个坐标点的4个通道分别加上0、1、2、3,最后将计算结果读取出来。

首先初始化GL环境

void initGLUT(int argc, char **argv) {glutInit(&argc, argv);glutWindowHandle = glutCreateWindow("GPGPU Tutorial");
}

创建FBO

void initFBO() {// create FBO (off-screen framebuffer)glGenFramebuffers(1, &fb);// bind offscreen framebuffer (that is, skip the window-specific render target)glBindFramebuffer(GL_FRAMEBUFFER, fb);
}

创建texture

/*** create textures and set proper viewport etc.*/
void createTextures() {glGenTextures(1, &outputTexID);glGenTextures(1, &intermediateTexID);glGenTextures(1, &inputTexID);// set up texturessetupTexture(outputTexID);setupTexture(intermediateTexID);setupTexture(inputTexID);glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, textureParameters.texTarget, inputTexID, 0);glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, textureParameters.texTarget, intermediateTexID, 0);glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, textureParameters.texTarget, outputTexID, 0);transferToTexture(pfInput, inputTexID);// set texenv modeglTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
}/*** Sets up a floating point texture with the NEAREST filtering.*/
void setupTexture(const GLuint texID) {// make active and bindglBindTexture(textureParameters.texTarget, texID);glTexStorage2D(GL_TEXTURE_2D, 8, GL_RGBA32F, 16, 16);// turn off filtering and wrap modesglTexParameteri(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);
}

注意:不要用 glTexImage2D生成纹理。

A very important restriction for using shader images is that the underlying texture must have been allocated using "immutable" storage, i.e. via glTexStorage*()-like functions, and not glTexImage2D().

将数据赋予inputTexID

void transferToTexture(float *data, GLuint texID) {glBindTexture(textureParameters.texTarget, texID);glTexSubImage2D(textureParameters.texTarget, 0, 0, 0, unWidth, unHeight, textureParameters.texFormat, GL_FLOAT, data);
}

执行计算

void performCompute(const GLuint inputTexID, const GLuint outputTexID) {// enable GLSL programglUseProgram(glslProgram);glUniform1fv(glGetUniformLocation(glslProgram, "v"), 4, v);// Synchronize for the timing reason.glFinish();CTimer timer;long lTime;timer.reset();glBindImageTexture(0, inputTexID, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA32F);glBindImageTexture(1, outputTexID, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA32F);glDispatchCompute(1, 1, 1);glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);glFinish();lTime = timer.getTime();cout << "Time elapsed: " << lTime << " ms." << endl;
}

compute shader代码

#version 430 corelayout (local_size_x = 16, local_size_y = 16) in;// 传递卷积核
uniform float v[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 = v[0] + data.r;data.g = v[1] + data.g;data.b = v[2] + data.b;data.a = v[3] + data.a;imageStore(output_image, pos.xy, data);
}

读回数据

void transferFromTexture(float *data) {glReadBuffer(GL_COLOR_ATTACHMENT2);glReadPixels(0, 0, unWidth, unHeight, textureParameters.texFormat, GL_FLOAT, data);
}

主流程代码

#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <GL/glew.h>
#include <GL/glut.h>
#include "utils/CReader.h"
#include "utils/CTimer.h"#define WIDTH    16    //data block width
#define HEIGHT    16    //data block heightusing namespace std;void initGLSL(GLenum type);
void initFBO();
void initGLUT(int argc, char **argv);
void createTextures(void);
void setupTexture(const GLuint texID);
void performCompute(const GLuint inputTexID, const GLuint outputTexID);
void transferFromTexture(float *data);
void transferToTexture(float *data, GLuint texID);// 纹理标识
GLuint outputTexID;
GLuint intermediateTexID;
GLuint inputTexID;// GLSL 变量
GLuint glslProgram;
GLuint fragmentShader;// FBO 标识
GLuint fb;// 提供GL环境
GLuint glutWindowHandle;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;GLfloat v[4];  // 传如compute shader中int main(int argc, char **argv) {int i;// 创建测试数据unsigned unNoData = 4 * unSize;        //total number of DatapfInput = new float[unNoData];float *pfOutput = new float[unNoData];for (i = 0; i < unNoData; i++) pfInput[i] = i * 0.001f;for (i = 0; i < 4; i++) {v[i] = i;}// create variables for GLtextureParameters.texTarget = GL_TEXTURE_2D;textureParameters.texInternalFormat = GL_RGBA32F;textureParameters.texFormat = GL_RGBA;CReader reader;initGLUT(argc, argv);glewInit();initFBO();createTextures();char c_convolution[] = "../convolution.cs";textureParameters.shader_source = reader.textFileRead(c_convolution);initGLSL(GL_COMPUTE_SHADER);performCompute(inputTexID, intermediateTexID);performCompute(intermediateTexID, outputTexID);// get GPU resultstransferFromTexture(pfOutput);for (int i = 0; i < unNoData; i++) {cout << "input:" << pfInput[i] << " output:" << pfOutput[i] << endl;}// clean upglDetachShader(glslProgram, fragmentShader);glDeleteShader(fragmentShader);glDeleteProgram(glslProgram);glDeleteFramebuffersEXT(1, &fb);glDeleteTextures(1, &inputTexID);glDeleteTextures(1, &outputTexID);glutDestroyWindow(glutWindowHandle);// exitdelete pfInput;delete pfOutput;return EXIT_SUCCESS;
}/*** Set up the GLSL runtime and creates shader.*/
void initGLSL(GLenum type) {// create program objectglslProgram = glCreateProgram();// create shader object (fragment shader)fragmentShader = glCreateShader(type);// set source for shaderconst GLchar *source = textureParameters.shader_source;glShaderSource(fragmentShader, 1, &source, nullptr);// compile shaderglCompileShader(fragmentShader);// attach shader to programglAttachShader(glslProgram, fragmentShader);// link into full program, use fixed function vertex shader.// you can also link a pass-through vertex shader.glLinkProgram(glslProgram);}

全部代码

GPGPU基础(五):使用compute shader进行通用计算及示例相关推荐

  1. Compute Shader次世代优化方案

    这是侑虎科技第498篇文章,感谢作者凯奥斯供稿.欢迎转发分享,未经作者授权请勿转载.如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨.(QQ群:793972859) 作者主页:https://z ...

  2. 华为云计算基础之Fusion Compute介绍

    华为云计算基础之Fusion Compute介绍 一.Fusion compute 1.FusionCompute解释 2.FusionCompute特性 3.华为FusionSphere 4.华为云 ...

  3. Directx 计算着色器(compute shader)

    原文 :http://www.cnblogs.com/Ninputer/archive/2009/12/11/1622190.html 博者注:计算着色器调试(http://msdn.microsof ...

  4. Catlike Coding Unity教程系列 中文翻译 Basics篇(五)Compute Shaders

    计算着色器 渲染一百万个立方体 原文地址:https://catlikecoding.com/unity/tutorials/basics/compute-shaders/ 在计算缓冲区中存储位置. ...

  5. Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十三章:计算着色器(The Compute Shader)...

    Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十三章:计算着色器(The Compute Shader) 原文: Int ...

  6. android device monitor命令行窗口在哪里_Vulkan在Android使用Compute shader

    oeip 相关功能只能运行在window平台,想移植到android平台,暂时选择vulkan做为图像处理,主要一是里面有单独的计算管线且支持好,二是熟悉下最新的渲染技术思路. 这个 demo(git ...

  7. OpenGL Compute Shader计算着色器的实例

    OpenGL Compute Shader计算着色器 先上图,再解答. 完整主要的源代码 源代码剖析 先上图,再解答. 完整主要的源代码 // #define USE_GL3W #include &l ...

  8. OpenGL Compute Shader Raytracing 计算着色器光线追踪的实例

    OpenGL Compute Shader Raytracing 计算着色器光线追踪 先上图,再解答. 完整主要的源代码 源代码剖析 先上图,再解答. 完整主要的源代码 // #define USE_ ...

  9. OpenGL Compute Shader Particle System计算着色器粒子系统的实例

    OpenGL Compute Shader Particle System计算着色器粒子系统 先上图,再解答. 完整主要的源代码 源代码剖析 先上图,再解答. 完整主要的源代码 #include &l ...

  10. OpenGL Compute Shader Image Processing计算着色器图像处理的实例

    OpenGL Compute Shader Image Processing计算着色器图像处理 先上图,再解答. 完整主要的源代码 源代码剖析 先上图,再解答. 完整主要的源代码 #include & ...

最新文章

  1. mysql数据库验证登陆不上_MySQL数据库连接不上、密码修改问题
  2. 【网络】TCP基础总结
  3. 视图解析器中配置前缀和后缀---SpringMVC学习笔记(五)
  4. 原来js的parseInt函数还可以这样用
  5. 猎豹浏览器怎么设置双击关闭网页 双击关闭网页设置方法
  6. PostgreSQL 辟谣存在任意代码执行漏洞:消息不实
  7. pickle 在python 2和python 3中兼容性问题
  8. Django 路由系统
  9. Mysql 按 create_time 排序导致的问题
  10. FPGA按键消抖—两种按键消抖形式的对比
  11. 中职计算机专业教程购买渠道,中职计算机论文精选
  12. 单细胞测序的入门操作
  13. 计算机音乐名词解释,音乐常见名词解释
  14. C语言杨辉三角的程序分析,C语言:打印杨辉三角
  15. unity接入quick sdk报错总结(ios第一版)
  16. Swift4 2 UITextView基本用法
  17. Linux下通过Shell脚本快速进入指定目录
  18. 大蟒蛇python头像_程序员用Python获取了自己以前的QQ历史头像,以前的非主流形象简直不忍直视...
  19. matlab bar 填充花纹,Matlab中画花纹填充的柱状图
  20. csv文件更改文本的编码格式

热门文章

  1. C++实现走迷宫算法
  2. 云端地球:让每个人都能在线生成大场景三维
  3. Java 常用的依赖包
  4. VBA常用实例 | OUTLOOK批量下载选中邮件中的附件
  5. 遗传算法(二 )——通用框架
  6. Javascript设计模式-00-说明
  7. energy plus matlab,Energyplus教程系列1—Energyplus到底能干啥.ppt
  8. 20155236 《信息安全概论》实验二(Windows系统口令破解)实验报告
  9. 【测试管理】版本定义
  10. 教你实现一个 iOS 重签名工具