第十一章 内存

第十一章 内存

使用纹理储存通用数据

着色器中可以将图像定义为uniform。

在这里插入图片描述
在这里插入图片描述

采样器类型和图像类型的主要区别是:首先图像类型表达的单一层的纹理,不是完整的mipmap链;其次,图像类型不支持滤波采样操作;还有包括包括深度比较的采样操作。

三种基本的图像类型分别为image*、iimage*和uimage*,分别对应浮点数、有符号整数和无符号整数。

除了与图像变量相关通用数据类型,还需要使用一个format限定符来设置数据在内存中的图像格式:

在这里插入图片描述
在这里插入图片描述

在使用变量format限定符的时候,必须与图像本身的基本数据类型匹配,例如image2D必须使用浮点数类型的限定符例如r32f而非浮点型的限定符rg8ui是不行的。

限定符的使用:

layout (rgba32f) uniform image2D image1;
layout (rg32i) uniform iimage2D image2;

format类型不一定与图像真实数据格式(internal format)完全一致,只要符合兼容要求就行,总体上说就是两种格式所对应的每个纹素中储存的数据量是相同的,就可以认为是兼容的。

绑定纹理对象的一层到一个图像单元

void glBindImageTexture(GLuint unit, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access, GLenum format);

将level层的纹理texture绑定到图像单元unit。如果是数组纹理的类型,可以选择将整个数组还是某一层绑定到图像单元上,如果layered为true则绑定整个数组并忽略layer的设置,反之则绑定layer这一层。access可以是GL_READ_ONLY、GL_WRITE_ONLY或者GL_READ_WRITE,format为上表中的值。

GLuint tex;
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D, tex);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA32F, 512, 512);
glBindTexture(GL_TEXTURE_2D, 0);
glBIndImageTexture(0, tex, 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA32F);

如果要将一个缓存对象所谓imageBuffer图像的存储空间,需要创建一个缓存纹理并将缓存对象关联到纹理对象上,然后将缓存纹理绑定到图像单元上。

GLuint tex, buf;

glGenBuffers(1, &buf);
glBindBuffer(GL_TEXTURE_BUFFER, buf);
glBufferData(GL_TEXTURE_BUFFER, 4096, NULL, GL_DYNAMIC_COPY);

glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D, tex);

glTexBuffer(GL_TEXTURE_BUFFER, GL_R32F, buf);
glBIndImageTexture(0, tex, 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA32F);

图像的读取和写入

图像的读取

gvec4 imageLoad(readonly gimage<T> image, <T> P[, int sample]);

P为坐标,对于多重采样的图像,需要使用sample参数来给定采样数。imageLoad的工作方式与texelFetch类似,但不经过滤波。

图像存储

gvec4 imageStore(writeonly gimage<T> image, <T> P[, int sample], gvec4 data);

将数据存储到坐标P处。

查询图像大小

T imageSize(gimage<T> image);

返回image的维度大小,对于数组形式的图像,返回值的最后一个分量记录数组大小。Cube形式的图像只会返回一个面的大小;如果是数组形式,那么还有cube map数组中的元素数量。

需要注意的是图像存储的次数是没有限制的,而帧缓存对象关联的附件数量要受到严格的限制,并且每个附件都只能写入一个片元值。

着色器存储缓存对象

为了方便处理大型结构化数据块,可以使用一个buffer缓存变量来储存数据。

layout (std430, binding = 0) buffer BufferObject{
    int mode;
    vec4 points[];
}

binding = 0表示这个块需要和索引为0的GL_SHADRED_STORAGE_BUFFER相关联。与GL_UNIFORM_BUFFER的最大区别是,着色器存储缓存可以再着色器中读写。

原子操作和同步

为了避免着色器运行过程中,并发执行导致的资源访问错误,OpenGL提供了一系列原子函数,直接对内存进行操作。它们有两个属性可以帮助我们访问和修改共享的内存区域。首先,它们总是在一个单一的时间间隔内进行操作,不会被别的着色器请求打断;其次,图形硬件提供一些机制,来确保多个同时发生的请求即使在同一时刻对同一处内存位置进行原子操作,它们也是会被重新序列化并且依次执行,以产生预期的效果。但并不保证顺序,只保证所有请求操作不会相互影响。

内置原子函数

uint imageAtomicAdd(IMAGE_PARAMS mem, uint data);
int imageAtomicAdd(IMAGE_PARAMS mem, int data);
uint imageAtomicMin(IMAGE_PARAMS mem, uint data);
int imageAtomicMin(IMAGE_PARAMS mem, int data);
uint imageAtomicMax(IMAGE_PARAMS mem, uint data);
int imageAtomicMax(IMAGE_PARAMS mem, int data);
uint imageAtomicAnd(IMAGE_PARAMS mem, uint data);
int imageAtomicAnd(IMAGE_PARAMS mem, int data);
uint imageAtomicOr(IMAGE_PARAMS mem, uint data);
int imageAtomicOr(IMAGE_PARAMS mem, int data);
uint imageAtomicXor(IMAGE_PARAMS mem, uint data);
int imageAtomicXor(IMAGE_PARAMS mem, int data);
uint imageAtomicExchange(IMAGE_PARAMS mem, uint data);
int imageAtomicExchange(IMAGE_PARAMS mem, int data);
uint imageAtomicCompSwap(IMAGE_PARAMS mem, uint compare, uint data);
int imageAtomicCompSwap(IMAGE_PARAMS mem, int compare, int data);

函数的返回值是执行操作之前内存中的值。
imageAtomicExchange试讲data写入给定坐标上,并返回原来的值。
imageAtomicCompSwap可以比较compare与图像中的值,如果相等,则将data写入,并返回原来的值。

IMAGE_PARAMS的宏定义:

#define IMAGE_PARAMS gimage1D image, int P      // or
#define IMAGE_PARAMS gimage2d image, ivec2 P    // or
...

原子操作只能作用于单一的有符号或者是无符号整数,不支持浮点数的图像或者是任何向量类型的图像。

缓存的原子操作

uint atomicAdd(inout uint mem, uint data);
int atomicAdd(inout int mem, int data);
uint atomicMin(inout uint mem, uint data);
int atomicMin(inout int mem, int data);
uint atomicMax(inout uint mem, uint data);
int atomicMax(inout int mem, int data);
uint atomicAnd(inout uint mem, uint data);
int atomicAnd(inout int mem, int data);
uint atomicOr(inout uint mem, uint data);
int atomicOr(inout int mem, int data);
uint atomicXor(inout uint mem, uint data);
int atomicXor(inout int mem, int data);
uint atomicExchange(inout uint mem, uint data);
int atomicExchange(inout int mem, int data);
uint atomicCompSwap(inout uint mem, uint compare, uint data);
int atomicCompSwap(inout int mem, int compare, int data);

同步对象

为了保证客户端和服务端也就是CPU和GPU采用同步的方式运行,可以使用同步对象(sync object),也可以成为栅栏,本质上栅栏就是流命令中的一个标记,它可以在GPU绘制或者状态变化命令过程中被发送,栅栏的其实生命是无信号的黄台,而GPU执行过之后变成有信号的状态,因此可以通过栅栏的状态了解GPU是否已经达到过栅栏。

插入栅栏

GLsync glFenceSync(GLenum condition, GLbitfield flags);

创建一个新的展览同步对象,并插入OpenGL的命令流当中,并返回栅栏句柄。condiiton的唯一可用值时GL_SYNC_GPUCOMMANDS_COMPLETE。flag只能是0。

判断是否执行过栅栏的命令

void glGetSynciv(GLsync sync, GLenum pname, GLsizei bufSize, GLsizei * length, GLint * values);

sync是同步对象句柄,从中读取有pname设置的属性值。bufSize设置接收缓存的大小,其中缓存地址由values给出,lenght是一个地址,用来写入values数据byte大小。

如果pname为GL_SYNC_STATUS那么values对应的值可以是GL_SIGNALED或者GL_UNSIGNALED。

同步等待直到有信号的状态

GLenum glClientWaitSync(GLsync sync, GLbitfields flags, GLuint64 timeout);

让客户端一直等待同步对象sync直到有信号状态。timeout为纳秒。如果flags中包含GL_SYNC_FLUSH_COMMNDS_BIT那么函数将会自动将所有当前等待的命令传递到服务端,然后开始等待,以确保栅栏进行服务端。

函数的返回值:

GL_ALREADY_SIGNALED: 调用函数时,同步对象已经有信号
GL_TIMEOUT_EXPIRED:超时
GL_CONDITION_SATISFIED:在等待过程中进入有信号状态
GL_WAIT_FAILED:由于某些原因失败了

删除同步对象

void glDeleteSync(GLsync sync);

使用同步对象的例子:

GLsync s;
glBindVertexArray(vao);
glDrawArrays(GL_TRIANGLES, 0, 3000);
s = glFneceSync();  // 创建栅栏,当之前的命令结束后就会变成有信号状态
void * data = glMapBufferRange(GL_UNIFORM_BUFFER, 0, 256, GL_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT);

do_something_time_consuming();

switch (glClientWaitSync(s, 0, 1000000)));

glDeleteSync(s);
memcpy(data, source_data, source_data_size);
glUnmapBuffer(GL_UNIFORM_BUFFER);

是否是合法同步对象

GLboolean glIsSync(GLsync sync);

如果需要在两个或者多个环境之间共享对象,那么可以再一个环境下等待同步对象获得信号,并作为另一个环境的命令结果。可以在源环境(需要等待的环境)中调用glFenceSync()然后再目标环境(执行等待过程的环境)中调用glWaitSync()。

void glWaitSync(GLsync sync, GLbitfield flags, GLuint64 timeout);

要求服务器等待sync指定的同步对象变成有信号状态,flags必须为0,timeout必须为GL_TIMEOUT_IGNORED。

图像限定符

如果要编译器不对图像的读取或者存储操作做任何的优化处理,可以使用volatile关键字。volatile关键字可以用于全局的声明,uniform和函数参数或者是局部变量。

第二个关键字是restrict关键字,他负责只是编译器,某个图像中引用的数据并不是其他图像的数据别名(也就是说,不存在引用同一片内存区域的图像),这种情况下,写入一个图像是不会影响到其他图像内容的,因此编译器可以更加积极的执行优化操作而不担心安全性的问题。restrict的应用范围与volatile相同。

第三个关键字是coherent,它负责控制图像的缓存机制。GPU通常包含大规模、多级别的缓存机制,它们可能是完全连续的也可能不是,如果图像中的数据是存放在不连续的缓存中,那么一个客户端对于缓存的修改是不会被另一个客户端所知,除非在内存中显示的将缓存刷新到一个更低的级别。如果数据只需要从内存中读取而不写入,那么可以保存到不连续的缓存当中也就是最接近处理器的最高级缓存中,以提高性能。但是如果发生写入内存操作并且需要传递给其他处理器,那么就必须存放到一个连续的内存位置上。这里有两种选择:第一是直接忽略所有缓存,第二是忽略一级缓存,并且将数据放置到二级缓存中,这样确保所有需要共享的数据的操作罗都是在对应缓存的着色器处理器组中运行,coherent关键字就是用来提出这种需求的,让OpenGL来保证二级缓存的连续性。

最后两个关键字:readonlywriteonly,负责控制图像的访问权限。readonly与const的区别是,const是作用于变量本身的,readonly是应用于底层的图像数据的;writeonly会组织对于声明为writeonly的图像变量的读取操作。

内存屏障

glsl中包含memoryBarrier()函数,以确保对于内存中某个位置的写入操作罗可以按照正确的顺序,被其他着色器请求先后观察到。也可以同过OpenGL中提供的glMemoryBarrier()函数实现一些内存处理的控制和缓存机制。

void glMemoryBarrier(GLbitfield barriers);

barriers可以包括:

GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT:作用于顶点缓存的数据,屏障命令之后读取的结果必然是屏障之前执行命令对于缓存写入的数据
GL_ELEMEN_ARRAY_BARRIER_BIT:作用于绑定的元素数组缓存的数据,屏障命令之后读取的结果必然是屏障之前执行命令对于缓存写入的数据
GL_UNIFORM_BARRIER_BIT:作用于uniform缓存对象中的uniform数据,屏障命令之后读取的结果必然是屏障之前执行命令对于缓存写入的数据
GL_TEXTURE_FETCH_BARRIER_BIT:作用于任何取自于纹理的数据,屏障命令之后读取的结果必然是屏障之前执行命令对于缓存写入的数据
GL_SHADER_IMAGE_ACCESS_BARRIER_BIT:作用于着色器图像变量中的数据,屏障命令之后读取的结果必然是屏障之前执行命令对于缓存写入的数据
GL_COMMAND_BARRIER_BIT:作用于glDraw*Indirect系列命令中的命令缓存对象数据,屏障命令之后读取的结果必然是屏障之前执行命令对于缓存写入的数据
GL_PIXEL_BUFFER_BARRIER_BIT:作用于绑定到GL_PIXEL_UNPACK_BUFFER或者GL_PIXEL_PACK_BUFFER的缓存数据,屏障命令之后读取的结果必然是屏障之前执行命令对于缓存写入的数据
GL_TEXTURE_UPDATE_BARRIER_BIT:作用于glTexImage*D()、glTexSubImag*D()等命令写入,并通过glGetTexImage()读取纹理数据,屏障命令之后读取的结果必然是屏障之前执行命令对于纹理写入的数据
GL_BUFFER_UPDATE_BARRIER_BIT:作用于glCopyBufferSubData()或者glGetBufferSubData()或者映射获取的缓存对象数据,屏障命令之后读取的结果必然是屏障之前着色器对缓存写入的数据。类似的,通过映射或者glBufferData()或者glBufferSubData(),在屏障之前写入缓存对象也会直接作用于屏障之后的着色器读取的结果。
GL_FRAMEBUFFER_BARRIER_BIT:作用于帧缓存附件中读取或者写入的数据,屏障命令之后读取的结果必然是屏障之前着色器写入缓存对象附件的内容。此外,在屏障之后写入的帧缓存的命令,其执行顺序必然落后于屏障之前的处理命令。
GL_TRANSFORM_FEEDBACK_BARRIER_BIT:作用于transform feedback期间处理的数据,屏障命令之后读取的结果必然来自于屏障之前的transform feedback。类似的屏障命令之后写入transform feedback,其执行孙旭必然落后于屏障之前的处理命令。
GL_ATOMIC_COUNTER_BARRIER_BIT
GL_ALL_BARRIER_BIT包括以上所有标记位

除了缓存特性的控制之外,glMemropyBarrier()还可负责控制顺序,可以控制多次绘制之间的重叠问题。

预先片元测试的优化控制

使用early_fragment_tests将深度测试和模板测试提前到片元着色器执行之前,如果写入了gl_FragDepth好像会失效。

#version 420 core

layout (early_fragment_tests) in;

高性能的原子计数器

在glsl中可以使用atomic_uint来创建原子计数器:

layout (binding = 0, offset = 0) uniform atomic_uint red_texels;

binding为缓存绑定点的索引位置,offset为计数器在缓存中的偏移量(一个缓存中可以有多个计数器)。OpenGL中使用GL_ATOMIC_COUNTER_BUFFER来创建原子计数器缓存。

原子计数器的增加和减少使用:

atomicCounterIncrement(atomic_uint);
atomicCounterDecrement(atomic_uint);
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值