相关文章推荐

前面介绍了opengl中的uniform对象,今天继续介绍一种新的对象---Shader Storage Block,我们简称为SSB对象,相比之前的uniform block,这种新的数据类型最大的变化在于,shader可以对SSB对象进行读写操作,而uniform block对象在shader中是只读的。众所周知,opengl相对于cpu最大的优点是快,而快的基础是其强大的并行特性,同一份shader代码可能同时在几千几万甚至更多的gpu unit上同时执行,如果只有只读的uniform对象,这是没有问题的,但如果引入了SSB对象,问题就来了,怎么样才能保证数据的正确性,这和cpu的多线程其实是一样的,都需要一些同步机制来保证,下面我们将详细介绍。

SSB对象

首先,我们来看看怎么在一个shader中定义一个SSB对象,如下

layout (binding = 0,std430) buffer my_storage_block

vec4 foo;

vec3 bar;

如上代码,SSB对象通过关键字“buffer”来指定,SSB对象和uniform block一样,也和一个buffer object绑定,读取和写入实际上都是指向buffer的相应位置。定义中的binding指定buffer所在的binding point,std430为一种新的布局方式,相对于之前见到的std410,这种布局方式更为紧凑。430或者之前的410这些数字代表的是opengl的版本号,像std430是4.3版本引入的,所以才会叫这个名字。理论上,uniform block可以做的事,SSB对象都可以完成,以前的vbo对象做的事也都可以通过SSB对象完成,因为SSB更为通用,但这种通用性带来的问题是效率不如uniform block高,因为后者的只读性可以有很多的优化空间。因为SSB对象绑定的是一个buffer object对象,可通过之前的glMapBufferRange或glBufferSubData等方法对SSB对象进行读取和写入操作。

SSB对象还有一个比较好的用途是变长数组,即在shader中指定数组但不指定大小,如下所示,vertices变量是一个数组,但大小没有指定,只要绑定的buffer够大,数组大小可以非常大。需要特别注意的是每一个SSB对象只能有一个变成数组成员变量,而且必须放在block的最后面。

layout (binding = 0,std430) buffer my_block

vec4 foo;

float a[3];

vec4 vertices[]

Atomic Memory Operations

这一部分我们介绍原子操作,这主要是为了解决同步问题,防止出现逻辑错误。比如两个shader同时执行m = m +1,两次执行都从m的地址读取到旧的value,然后对value +1,再写入到m的地址,如果不能正确协调顺序,两次执行后,m的值可能为m+1,也可能为m+2.shader的情况更为复杂,因为并行执行的shader非常多,而原子操作能保证多个并行执行的shader对同一内存同时操作时有一个正确的时序,一个原子操作在执行完成前是不允许中断的,在执行的时候也不允许别的shader对同一个内存区域进行访问,这样就保证了最终结果的正确性,这些常用的方法定义如下:

atomicAdd(mem,data)  //mem = mem + data

atomicAnd(mem,data) // mem = mem & data

atomicOr(mem,data) // mem = mem | data

atomicXor(mem,data) // mem = mem ^ data

atomicMin(men,data) // mem = min(mem,data)

.......

大部分的函数的含义都是显而易见的,需要注意的是函数返回的是之前的值,而不是修改后的值。函数只有int 和uint两个版本,不支持其他数据类型。

Memory Barrier

接下来我们介绍openglk的内存同步机制,这种机制和原子操作并无太多关系,原子操作保证的是读写结果的正确性,而Memory Barrier机制保证的是代码间的顺序,确保如下几种问题都能被有效避免。就拿上面的原子操作。举个例子,shader对SSB对象通过原子操作进行写入,比如对SSB对象的某个成员变量加1,而后再进行读操作,期望的结果是所有写操作完成后再进行读操作,虽然原子操作保证了各个shader间能有序写入从而保证结果的正确性,但由于opengl的并行特性,所有shader是并行执行的,某个shader读取时并不能保证其他shader都已经完成写入操作,这时候就需要用到Memory Barrier机制了。介绍之前,首先总结下可能遇到几种问题类型,如下

read-after-write,由于指令顺序可能会被优化时改变,可能导致读取的是write之前的值

write-after-write,可能导致两次写入顺序错乱,进而写入的最终结果错误

write-after-read,可能导致读取到write后的新值

由于opengl固有的并行行,内存同步机制非常重要,而其中最重要的一个就是memory barrier,这个机制的实现只有一句代码即可,这行代码的含义直白点说就是保证opengl优化和改变代码顺序时,保证该行之前的所有代码块都在该行之后的代码块之前执行。函数定义如下:

void glMemoryBarrier(GLbitfield barriers);

参数barriers是一个位操作符参数,具体含义不在这详述,下面介绍几个目前常用的,其他的等以后再进行补充:

GL_SHADER_STORAGE_BARRIER_BIT, 确保该行代码之前对SSB对象读取或写入的操作先完成,然后才运行该行代码之后的shader继续对SSB对象进行存取。

GL_UNIFORM_BARRIER_BIT,说明该行代码执行前的一些shader对某些buffer object对象进行了写入,而该行代码后的一些shadre会用这个buffer object作为uniform object,设置该属性位保证之前的代码写入完成后才可能执行后面的读取操作。

GL_VERTEX_ATTRIB_ARRAY_BUFFER_BIT,说明该行代码执行前的一些shader对某些buffer object 对象进行了写入,而该行代码后的一些shadre 会用这个buffer object作为顶点输入的vbo对象,设置该属性位保证之前的代码写入后才会执行后面的读取操作。

GL_ATOMIC_COUNTER_BARRIER_BIT,说明该行代码执行前的一些shader 对某些buffer object对象进行了写入,而该行代码之后的一些shader 将此buffer 作为Atomic Counter 对象的绑定buffer,可参加下节内容。

... ... 其他属性后,等以后用到再补充。

需要特别注意的是,glMemoryBarrier函数是cpu调用执行的,但协调的是gpu shader的写入和读取顺序。shadre中也有类似的同步机制,shader 函数定义如下:

void memeoryBarrier()

在shader 中执行此函数,该函数会让gpu进入等待状态,只到shader 之前的读取和写入操作都完成后才继续往后执行,该函数针对的是opengl中的所有内存对象,类似的还有BarrierBuffer()等针对某些特殊内存对象的保证机制。

Atomic Counters

最后介绍一种opengl的计数机制Atomic Counters,这是一种特殊的uniform对象,需要绑定特定的buffer object到GL_ATOMIC_COUNTER_BUFFER这个Target上,对该对象的读写操作都是原子操作,从而保证计数的正确性,具体定义如下:

layout (binding = 3,offset = 8) uniform atomic_uint my_variable;

其中,binging = 3代表绑定GL_ATOMIC_COUNTER_BUFFER的第三个binding point 上的对象,offset=8代表存储位置位于该buffer 的offset为8的地址,除了在shader中设定好binding point和offset,还需要客户端代码把buffer 绑定到对应的bingding point上,代码如下:

GLUint buf ;

glGenBuffers(1,&buf);

glBindBuffer(GL_ATOMIC_COUNTER_BUFFER,buf);

glBufferData(GL_ATOMIC_COUNTER_BUFFER,16*sizeof(GLuint),NULL,GL_DYNAMIC_COPY);

glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER,3,buf);

设置好Atomic Counter对象绑定的缓冲区后,就可以通过shader进行相应的操作了,具体操作如下:

uint  atomicCounterIncrement(atomic_uint c)  //加1操作

uint atomicCounterDecrement(atomic_uint c) //减1操作

uint atomicCounter(atomic_uint c)  //获取couter 数目

每一个Atomic Counter 对象都有一个绑定的buffer object对象,也会遇到上面的内存同步问题,因此,也需要用glMemoryBarrier (GL_ATOMIC_COUNTER_BARRIER_BIT)函数来确保代码的执行顺序,特别需要注意的是,这行代码保证的是先写入buffer object然后将buffer object 作为Atomic Counter的绑定buffer 的情况,如果先通过上面的原子操作写入buffer,而后对该buffer 进行读取操作,并不一定要用GL_ATOMIC_COUNTER_BARRIER_BIT来保证,需要根据该buffer 的实际用途来指定对应的bit。

到此,关于SSB对象及OpenGL的一些内存保护机制都已讲完,希望对大家有所帮助。下一篇,我将介绍下opengl里用于存储图像的texture对象,欢迎大家继续阅读。

前面介绍了opengl中的uniform对象,今天继续介绍一种新的对象---Shader Storage Block,我们简称为SSB对象,相比之前的uniform block,这种新的数据类型最大的变化在于,shader可以对SSB对象进行读写操作,而uniform block对象在shader中是只读的。众所周知,opengl相对于cpu最大的优点是快,而快的基础是其强大的并行特性,同一份shader代码可能同时在几千几万甚至更多的gpu unit上同时执行,如果只有只读的uniform对象,这是没有问 使用 时需要给ClipMask参数给一张噪点图,设置合适的cliplinesize和cliplinecolor,然后调整clipalpha就可以了。 原理是通过获取噪点图上对应的颜色,转换成灰度,然后用灰度与clipalpha对比,如果大于则被剪裁掉。 shader 实现如下: Shader XM/CorrosionEffect { Properties { _Color (Color, Color) = (1,1,1,1) _MainTex (Albedo (RGB) using System.Collections; public class Translating : MonoBehaviour { public float speed = 10.0f; public Vector3 startPoint = Vector3.zero; public Vector3 endPoint = Vector3.zero; public Vector3 lookAt = Vector 映射缓冲区 在C++中,用glBufferData来进行开辟缓冲区空间和填充内容,如果想修改缓冲区内容,可用glMapBuffer和glMapBufferRange。glMapBufferRange是提供一个指向内存的指针,可用这个指针直接读取或更新某个缓冲区的数据,尽量避免读取和写入同时进行的情况。(注意:修改的是已经在GPU内存中的数据,但不会阻止GPU工作) void *glMapBuffe
20170321- shader storage block 1、 shader storage block 与uniform block 最大的区别是在 shader 中可以对前者进行写入操作,甚至是对其成员的内存进行原子操作,而后者在 shader 中是只读的。 shader storage block 拥有更高的大小上限 2、 shader storage block 支持std140的布局方式,同时也支持
OpenGL超级宝典(第7版)笔记21 着色器存储区块 shader storage block 清单5.29-5.30 文章目录OpenGL超级宝典(第7版)笔记21 着色器存储区块 shader storage block 清单5.29-5.30前言1 着色器存储区块 shader storage block 初介绍6 总结 上一篇为大家介绍了uniform变量和uniform块的用法,其中有许许多多要注意的点,uniform变量和块使得我们可以更方便的传入数据到着色器中去,但是我们的着色器并不能对
Shader Storage Buffer Object是GLSL着色器的buffer,不同于uniform buffer在着色器不可修改, shader storage buffer是可读可写的。修改的内容给其他着色器调用或者应用程序本身。初始化 m_Uniform Block Data.uLightDirectionE = glm::vec4(1, 1, 1, 0); m_Uniform
glGenBuffers(1, & shader Storage BufferID); glBindBuffer(GL_ SHADER _ STORAGE _BUFFER, shader Storage BufferID); glBufferData(GL_ SHADER _ STORAGE _BUFFER, numParticles*sizeof(Partic
OpenGL Compute Shader 是OpenGL 4.3引入的一种新型 Shader ,它主要用于通用计算(GPGPU)任务而不是图形渲染。它可以在GPU上执行高度并行的计算任务,例如物理模拟、图像处理和机器学习等。Compute Shader 与其他 Shader 不同之处在于它没有渲染管道的输入和输出,并且可以从CPU上下文中直接调用。它还可以读写各种类型的缓冲区和纹理,使其非常灵活和强大。 Compute Shader 使用 步骤如下: 1.创建Compute Shader 对象 使用 glCreate Shader 函数创建Compute Shader 对象 。 2.编译Compute Shader 代码: 使用 gl Shader Source和glCompile Shader 函数将Compute Shader 代码编译为OpenGL可识别的二进制格式。 3.创建Compute Program 对象 使用 glCreateProgram函数创建Compute Program 对象 。 4.将Compute Shader 附加到Compute Program 对象 上: 使用 glAttach Shader 函数将Compute Shader 附加到Compute Program 对象 上。 5.链接Compute Program 对象 使用 glLinkProgram函数将Compute Program 对象 链接到OpenGL渲染管道。 6. 使用 Compute Shader 使用 glUseProgram函数激活Compute Program 对象 ,并通过glDispatchCompute函数调用Compute Shader 。 7.清理资源: 使用 glDelete Shader 和glDeleteProgram函数删除Compute Shader 和Compute Program 对象 。 下面是一个简单的Compute Shader 示例代码: ```glsl #version 430 layout(local_size_x = 16, local_size_y = 16) in; layout(std430, binding = 0) buffer InputBuffer { float data[]; layout(std430, binding = 1) buffer OutputBuffer { float result[]; void main() { uint idx = gl_GlobalInvocationID.x + gl_GlobalInvocationID.y * gl_NumWorkGroups.x * gl_WorkGroupSize.x; result[idx] = data[idx] * data[idx]; 这个Compute Shader 使用 输入缓冲区和输出缓冲区,对输入缓冲区的每个元素进行平方运算,并将结果存储在输出缓冲区中。在主函数中, 使用 gl_GlobalInvocationID获取全局线程ID,计算出要处理的输入元素的索引,并在输出缓冲区中存储计算结果。 最后,通过调用glDispatchCompute函数启动Compute Shader 。该函数需要指定调度的工作组数量,以及每个工作组中线程的数量。在这个示例中,我们 使用 16x16的工作组,并将其应用于输入缓冲区的所有元素。 ```c++ glDispatchCompute(numGroupsX, numGroupsY, numGroupsZ); 以上就是OpenGL Compute Shader 的原理与 使用 方法。
 
推荐文章