GLSL-Compute Shader

本文深入探讨OpenGL计算着色器的原理与应用,解释如何利用计算着色器进行高性能并行计算,包括其在图像处理、科学计算等领域的实践。文章详细介绍了计算着色器的创建、执行流程、数据通信与同步机制,以及如何利用共享内存提升性能。

概述


由于图形处理器每秒能够进行数以亿计次的计算,它已成为一种性能十分惊人的器件。过去,这种处理器主要被设计用于承担实时图形渲染中海量的数学运算。然而,其潜在的计算能力也可用于处理与图形无关的任务,特别是当无法很好地与固定功能的图形管线结合的时候。为了使得这种应用成为可能,OpenG引入一种特殊的着色器:计算着色器。计算着色器可以认为是一个只有一级的管线,没有固定的输入和输出,所有默认的输入通过一组内置变量来传递。当需要额外的输入时,可以通过那些固定的输入输出来控制对纹理和缓冲的访问。所有可见的副作用是图像存储,原子操作,以及对原子计数器的访问。然而加上通用的显存读写操作,这些看上去似乎有限的功能使计算着色器获得一定程度的灵活性,同时摆脱图形相关的束缚,以及打开广阔的应用空间。

OpenGL中的计算着色器和其他着色器很相似。它通过glCreateShader() 函数创建,用glCompilerShader()进行编译,通过glAttachShader()对程序进行绑定,最后按通用的做法用glLinkProgram()对这些程序进行链接。计算着色器使用GLSL编写,原则上,所有其他图形着色器(比如顶点着色器,几何着色器或者片元着色器)能够使用的功能它都可以使用。当然,这不包括诸如几何着色器中的EmitVertex()或者EndPrimitive()等功能,以及其他类似的与图形管线特有的内建变量。另一方面,计算着色器也包含一些独有的内置变量和函数,这些变量和函数在OpenGL管线的其他地方无法访问。

 

工作组及其执行


正如图形着色器被置于管线的不同阶段用来操作与图形相关的单元一样,将计算着色器被有效地放入一个一级的计算管线中,然后处理与计算相关的单元。按照这种类比,顶点着色器作用于每个顶点,几何着色器作用于每个图元,而片元着色器则作用于每个片元。图形硬件主要通过并行来获得性能,这种并行则通过大量的顶点、图元和片元流过相应的管线阶段而得以实现。而在计算着色器中,这种并行性则显得更为直接,任务以组为单位进行执行,我们称为工作组(work group)。拥有邻居的工作组被称为本地工作组(local workgroup), 这些组可以组成更大的组,称为全局工作组(global workgroup),而其通常作为执行命令的一个单位。

计算着色器会被全局工作组中每一个本地工作组中的每一个单元调用一次,工作组的每一个单元称为工作项(work item),每一次调用称为一次执行。执行的单元之间可以通过变量和显存进行通信,且可执行同步操作保持一致性。图12-1 对这种工作方式进行了说明。在这个简化的例子中,全局工作组包含16个本地工作组, 而每个本地工作组又包含16个执行单元,排成4*4的网格。每个执行单元拥有一个2维向量表示的索引值。
 

尽管在图12-1中,全局和本地工作组都是2维的,而事实上它们是3维的,为了能够在逻辑上适应1维、2维的任务,只需要把额外的那2维或1维的大小设为0即可。计算着色器的每一个执行单元本质上是相互独立的,可以并行地在支持OpenGL的GPU硬件上执行。实际中,大部分OpenGL硬件都会把这些执行单元打包成较小的集合(lockstep),然后把这些小集合拼起来组成本地工作组。本地工作组的大小在计算着色器的源代码中用输入布局限定符来设置。全局工作组的大小则是本地工作组大小的整数倍。当计算着色器执行的时候,它可以内置变量来知道当前在本地工作组中的相对坐标本地工作组的大小, 以及本地工作组在全局工作组中的相对坐标。基于这些还能进一步获得执行单元在全局工作组中的坐标等。着色器根据这些变量来决定应该负责计算任务中的哪些部分,同时也能知道一个工作组中的其他执行单元,以便于共享数据。

输入布局限定符在计算着色器中声明本地工作组的大小,分别使用local_size_xlocal_size_y以及local_size_z,它们的默认值都是1。举例来说如果忽略local_size_z,就会创建N * M的2维组。比如在例子12.1中就声明了一个本地工作组大小为16 * 16的着色器。
 

例12.1简单的本地工作组声明

#version 430 core  
  
// 输入布局限定符声明一个16*16(*1)的本地工作组  
layout(local_size_x = 16, local_size_y = 16) in;  
  
void main(void)  
{  
    // 什么都不做  
}  


尽管例子12.1中的着色器什么事情也没做,它仍然是一个“完整”的着色器,可以正常的编译、链接并且在OpenGL硬件中执行。要创建一个计算着色器,只需调用glCreateShader ()函数,将类型设置为GL_COMPUTE_SHADER,并且调用glShaderSource()函数来设置着色器的源代码, 接着就能按正常编译了。然后把着色器附加到一个程序上,调用glLinkProgram()。这样就会产生计算着色器阶段需要的可执行程序。例12.2展示了从创建到链接一个计算程序(使用“计算程序”来表示使用计算着色器来编译的程序)的完整步骤。

例12.2 创建,编译和链接计算着色器

 

 

[cpp] view plain copy

 

GLuint shader, program;  
static const GLchar* source[] =   
{  
    "#version 430 core\n"  
    "\n"  
    "// 输入布局限定符声明一个16*16(*1)的本地工作组\n"  
    "layout(local_size_x = 16, local_size_y = 16) in;\n"  
    "\n"  
    "void main(void)\n"  
    "{\n"  
        "// 什么都不做\n"  
    "}\n"  
};  
  
shader = glCreateShader(GL_COMPUTE_SHADER);  
glShaderSource(shader, 1, source, NULL);  
glCompileShader(shader);  
  
program = glCreateProgram();  
glAttachShader(program, shader);  
glLinkProgram(program);  


一旦像例12.2中那样创建并链接一个计算着色器后,就可以用glUseProgram()函数把它设置为当前要执行的程序,然后用glDispatchCompute()把工作组发送到计算管线上,其原型如下:

void glDispatchCompute(GLuint num_groups_x, GLuint num_groups_y, GLuint num_groups_z);  


在3个维度上分发计算工作组。num_groups_x,num_groups_y和num_groups_z分别设置工作组在X,Y和Z维度上的数量。每个参数都必须大于0,小于或等于一个与设备相关的常量数组GL_MAX_COMPUTE_WORK_GROUP_SIZE的对应元素。

在调用glDispatchCompute()时,OpenGL会创建一个包含个数为num_groups_x * num_groups_y * num_gourps_z的本地工作组的3维数组。注意三个维度中一个或两个维度可以为1或者glDispatchCompute()的参数的任何值。所以计算着色器中执行单元的总数是这个3维数组的大小乘以着色器代码中定义的本地工作组的大小。可想而知,这种方法可以为图像处理器创建非常大规模的工作负载,

### Compute Shader 的相关教程与文档 Compute Shader 是一种专门用于计算任务的着色器类型,运行在 GPU 上,能够以并行方式高效处理大量数据[^1]。它通常用于物理模拟、粒子系统、图像处理等需要高性能计算的任务。 以下是一些推荐的教程和文档资源: #### 1. 官方文档与教程 - **Unity Compute Shader 文档** Unity 提供了详细的 Compute Shader 文档,涵盖了从基础概念到实际应用的内容。这是学习 Compute Shader 的良好起点[^3]。 - [Unity Compute Shader Documentation](https://docs.unity3d.com/Manual/ComputeShaders.html) - **DirectX 11 Compute Shader 文档** 如果你使用的是 DirectX 技术栈,可以参考微软官方的 DirectX 11 Compute Shader 文档。该文档详细介绍了如何编写和使用 Compute Shader- [DirectX 11 Compute Shader Documentation](https://learn.microsoft.com/en-us/windows/win32/direct3d11/overviews-direct3d-11-shader-compute) #### 2. 在线教程 - **Kyle Halladay 的 Compute Shader 教程** Kyle Halladay 提供了一个非常受欢迎的 Compute Shader 教程,涵盖了从入门到高级的多个主题[^1]。你可以通过以下链接访问: - [Kyle Halladay Compute Shader Tutorial](https://www.kylehalladay.com/blog/tutorial/compute-shader/01-Introduction) - **Sample_TutorialCompute01_UavTexture** 这是一个关于如何设置和使用 UAV(无序访问视图)纹理的示例教程,适合希望深入了解 Compute Shader 高级功能的开发者。 - [UAV Texture with Compute Shader](https://github.com/Microsoft/DirectX-Graphics-Samples/tree/master/Samples/Desktop/D3D12Raytracing/src/Tutorials/ComputeTutorial) #### 3. 编译工具 如果你需要编译 Compute Shader 文件,可以使用以下工具: - **glslc** Google 提供的 glslc 是一个类似于 GCC 和 Clang 的 GLSL 着色器编译器。它可以轻松编译 GLSL 代码为 SPIR-V 格式[^4]。 ```bash glslc shader.comp -o shader.spv ``` #### 4. 示例代码 以下是一个简单的 Compute Shader 示例代码,展示了如何进行基本的计算任务: ```hlsl RWTexture2D<float4> OutputTexture : register(u0); [numthreads(8, 8, 1)] void CSMain(uint3 dispatchThreadID : SV_DispatchThreadID) { OutputTexture[dispatchThreadID.xy] = float4(dispatchThreadID.xy / 255.0f, 0, 1); } ``` 此代码将生成一个基于线程 ID 的颜色图案,并将其写入输出纹理中。 #### 5. 图形学算法库 如果你对图形学领域的算法感兴趣,可以参考 HXA Graphics Library。它提供了许多实用的算法实现,包括随机数生成器、颜色空间转换、辐射度 RGBE 输入输出等[^5]。 - [HXA Graphics Library](https://hxa.name/about/) ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值