目录
♠ 着色器存储区块♥ 声明♥ 应用♥ 原子内存操作♥ 内存屏障♣ 什么是内存屏障♣ 在应用中使用屏障♣ 在着色器中使用屏障♠ 推送♠ 结语♠ 着色器存储区块
我们在上一张已经简单的认识到了uniform
统一变量和一致区块
,这一章节我们学习一个新的着色器存储区块(shader storage block)
,它和uniform很像
一致性
1. 着色器存储区块和uniform都可以像着色器提供数据
2. 二者声明类似,着色器区块使用限定符buffer
而非uniform
优点
1. 存储区块更大,几乎没有上限
2. 区别uniform,着色器存储区块可以被着色器修改
3. 存储区块还支持原子内存操作
缺点
1. 由于非常灵活,OpenGL难以真正优化对存储块的访问
♥ 声明
用buffer
限定符声明,支持std140
和std430
打包限定符
layout (binding=0,std430) buffer color_block{vec4 out_color;};
♥ 应用
绑定到缓存和使用的方式和uniform
几乎一样,区别是索引使用的是GL_SHADER_STORAGE_BUFFER
我们来看一个完整的演示示例吧,很简单,我们通过区块内的变量给三角形上色
注:该例子直接修改OpenGl超级宝典官方示例singletri.cpp
,只需修改startup
方法即可
virtual void startup(){static const char * vs_source[] ={"#version 450 core \n""\n"" \n""void main(void) \n""{\n"" const vec4 vertices[] = vec4[](vec4( 0.25, -0.25, 0.5, 1.0), \n""vec4(-0.25, -0.25, 0.5, 1.0), \n""vec4( 0.25, 0.25, 0.5, 1.0)); \n""\n"" gl_Position = vertices[gl_VertexID];\n""}\n"};static const char * fs_source[] ={"#version 450 core \n""\n""layout (binding=0,std430) buffer color_block \n""{\n"" vec4 out_color; \n""}; \n""\n""out vec4 color; \n""\n""void main(void) \n""{\n"" color = out_color; \n""}\n"};program = glCreateProgram();GLuint fs = glCreateShader(GL_FRAGMENT_SHADER);glShaderSource(fs, 1, fs_source, NULL);glCompileShader(fs);GLuint vs = glCreateShader(GL_VERTEX_SHADER);glShaderSource(vs, 1, vs_source, NULL);glCompileShader(vs);glAttachShader(program, vs);glAttachShader(program, fs);glLinkProgram(program);glGenVertexArrays(1, &vao);glBindVertexArray(vao);GLfloat sColor[] = { 1.0f, 0.5f, 0.0f, 1.0f };GLuint ssbo;glGenBuffers(1, &ssbo);glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo);glBufferData(GL_SHADER_STORAGE_BUFFER, 4*8, NULL, GL_DYNAMIC_COPY);glBindBufferRange(GL_SHADER_STORAGE_BUFFER, 0, ssbo, 0, 4 * 8);glBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, 4 * 4, sColor);}
要点1:
在该片段着色器中我们声明了一个着色器存储区块color_block
,其存有唯一变量out_color
,该变量会作为三角形颜色被赋值,注意了这里限定符是buffer
,绑定缓存的索引是GL_SHADER_STORAGE_BUFFER
要点2:
自定义颜色sColor
,作为数值通过glBufferSubData
接口更新到了区块内,以下是最终显示效果
♥ 原子内存操作
区别去unifom的只读特性,着色器区块允许对内存进行简单的读写,这其中包括的原子操作
,
什么是原子操作
原子操作的作用是一段从内存读取的序列,可能会伴随内存的写入
保证了单次数据读写的安全性
原子操作可在其他调用有机会从内存读取数据之前,就完成读取-修改-写入循环以完成一次调用
♥ 内存屏障
只读数据没有任何问题,如果伴随写入数据,可能存在风险,风险大致分为以下三种
先写后读(RAW)风险
写后写(WAW)风险刚写入内存后,立即读取该位置的数据,根据系统架构,读写顺序可能会被重排,进而读写到错误数据
先读后写(WAR)风险在同一内存地址连续写入数据,根据系统架构,最后一次写入并不一定是最终写入内存的值
通常发生在并行系统中,读取和写入的顺序可能被重排,读取到后被写入的数据
内存屏障
就是用来处理这些内存风险的工具
♣ 什么是内存屏障
相当于一个标记,告诉OpenGL,如果准备重新排序,必须完成屏障之前发送的命令,不要先执行后边的命令
♣ 在应用中使用屏障
函数void glMemoryBarrier(GLbitfield barriers);
参数barriers
不同的值代表不同的含义,例如:
GL_SHADER_STORAGE_BARRIER_BIT
GL_UNIFORM_BARRIER_BIT屏障执行前的所有操作(尤其是写入),一定执行在屏障调用后的数据操作之前被完成
GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT如果我们向缓存内写入的数据,在屏障执行后作为统一变量缓存,设置该选项
OpenGL会等待向缓存写入的着色器完成,然后通过顶点属性将这些缓存作为顶点数据源
♣ 在着色器中使用屏障
我们可以直接在着色器中使用屏障
void memoryBarrier();
已执行的读写函数会在该屏障执行完成前返回
♠ 推送
Github/KingSun5
♠ 结语
若是觉得博主的文章写的不错,不妨关注一下博主,点赞一下博文,另博主能力有限,若文中有出现什么错误的地方,欢迎各位评论指摘。
👉 本文属于原创文章,转载请评论留言,并在转载文章头部著名作者出处👈