Adreno OpenGL ES 3.1 介绍(2)
- 4. 使用OpenGL ES 3.1与Adreno
-
- 4.1 OpenGL ES 3.1中的新特性
-
- 4.1.4 图像和内存屏障
- 4.1.5 间接绘制调用
- 4.1.6 多样本纹理
4. 使用OpenGL ES 3.1与Adreno
4.1 OpenGL ES 3.1中的新特性
4.1.4 图像和内存屏障
在本章的前面,介绍了原子计数器,并展示了它们如何提供着色器(如计算着色器)与外部之间的通信方式。然而,原子计数器是有限的工具。他们只能表示无符号整数值,对其进行操作的函数集受到限制。如果着色器需要更强大的数据交换手段,那么可以使用的一个工具是图像,这是OpenGL ES 3.1中引入的另一个新特性。
图像是指向纹理特定级别的不透明均匀体。图像支持多种不同的纹理类型,例如:
- 2D纹理(由ES着色器语言类型image2D、iimage2D和uimage2D定义)
- 2D数组纹理(由ES着色器语言类型image2DArray、iimage2DArray和uimage2DArray定义)
- 3D纹理(由ES着色器语言类型image3D、iimage3D和uimage3D定义)
- 立方体贴图纹理(由ES着色器语言类型imageCube、iimageCube和uimageCube定义)
图像仅限于使用OpenGL ES 3.1中提供的内部格式的子集:
- GL_R32I
- GL_R32F
- GL_R32UI
- GL_RGBA16F
- GL_RGBA16I
- GL_RGBA16UI
- GL_RGBA32I
- GL_RGBA32F
- GL_RGBA32UI
- GL_RGBA8
- GL_RGBA8I
- GL_RGBA8UI
- GL_RGBA8_SNORM
将纹理的mipmap级别指定给图像后,可以直接从着色器读取纹理并将其写入该图像。与该操作相关的成本通常比使用原子计数器的成本更高,因此仅在必要时使用图像。
注意:在图像上执行加载操作时,必须使用整数提供纹理像素位置。图像不提供纹理过滤功能/函数。
任何符合标准的opengles3.1实现都将在计算着色器阶段支持至少四种图像的一致性。如果在其他着色器阶段使用图像,则无法保证支持,因此请始终首先使用适当的GL_MAX_*_IMAGE_UNIFORMS常量进行检查。
使用图像时,请注意内存一致性问题。有许多因素需要考虑,包括:
- 着色器调用以基本上未定义的顺序执行
- 可以通过映像访问的底层内存也可以通过其他动态调用进行更改
- OpenGL ES可能会缓存来自一个着色器调用的存储操作,因此其他调用可能看不到更新
- OpenGL ES还可以缓存内存读取获取的值,并将缓存的值返回到访问同一内存的任何着色器调用
通过对图像声明使用以下内存限定符,可以对这些OpenGL ES行为施加一定程度的控制:
- Coherent – 对该图像执行的任何写入操作都必须反映在随后由其他着色器调用执行的读取结果中
- Volatile – 对图像执行的任何读取操作都必须反映对底层内存的更新结果,这些更新可能是由另一个着色器调用完成的
- Restrict – 对OpenGL ES实现的提示,并且断言仅在当前着色器阶段以及通过使用此关键字定义的图像才能修改基础内存
- Readonly – 限制图像用于加载操作
- Writeonly – 限制图像用于存储操作;跨多个着色器的内存访问在很大程度上是不同步的:从多个单独的着色器调用读取和写入单个共享内存地址的相对顺序基本上未定义
- 与其他着色器调用一样,单个着色器调用对多个内存地址的访问顺序也未定义
为了同步内存事务,着色器可以使用新的memoryBarrier函数。这个函数会等待所有与使用图像变量相关的挂起内存访问的完成。在memoryBarrier调用之前,所有存储操作的结果将会在任何着色器阶段的任何着色器调用中加载操作时可见。
这些是在着色器代码中使用的同步结构。memoryBarrier可能需要在API层注入(参见OpenGL ES 3.1参考页中的glMemoryBarriert。考虑一个着色器访问与着色器图像加载/存储操作相同的内存,但通过其他方式,例如,纹理获取。着色器代码中的memoryBarrier函数只保证同步那些使用图像变量和原子计数器的内存访问。在这种情况下,正确的同步可能需要使用api级memoryBarrier。对于所有需要实时更新任意位置纹理的技术,图像都是有用的。用例包括动态场景体素化。
4.1.5 间接绘制调用
在opengles3.0中,进行绘制调用时,需要传递以下函数参数:
- Primitive mode
- Start index
- 索引数
- 其他参数,取决于绘制调用的类型
在 OpenGL ES 3.0的上下文中,这已经足够了,因为缺少可以直接在GPU上动态生成内容的工具。然而,随着计算着色器、着色器存储缓冲区对象(shader storage buffer objects,SSBOs)和原子计数器的出现,需要一个draw调用来从存储在VRAM中的信息中获取这些参数值。
例如,考虑计算着色器处理渲染帧的内容并检测该帧中的亮点位置的情况。然后将这些位置存储在图像或SSBO中。这些信息稍后将用于混合与小波基纹理四边形亮点。计算着色器需要使用原子计数器来跟踪检测到的斑点数量。请记住,原子计数器使用缓冲区对象存储。这意味着在VRAM中亮点的数量是可用的,并且它应该能够将该值直接传递到draw调用中。如果没有OpenGL ES 3.1中引入的间接绘制调用,这是不可能的。它需要将缓冲区对象区域映射到进程空间,读取计数器值,然后将值作为draw调用参数传递回opengles。
OpenGL ES 3.1通过间接绘制调用解决了此问题:
- glDrawArraysIndirect – 与 glDrawArraysInstanced的功能相同
- glDrawElementsIndirect – 与glDrawElementsInstanced的功能相同
这两个绘制调用的间接版本从绑定到GL_DRAW_INDIRECT_BUFFER缓冲区绑定点的缓冲区对象读取它们的输入参数值,而不是将它们作为函数调用的形式参数。例外情况是模式和类型参数仍然作为形式参数传递。
本章的上一节将讨论计算着色器。用于启动计算着色器的函数glDispatchCompute也有一个名为glDispatchComputeIndirect的间接版本。在这种情况下,用于参数值的绑定点是GL_DISPATCH_INDIRECT_BUFFER,而不是GL_DRAW_INDIRECT_BUFFER。
4.1.6 多样本纹理
在OpenGL ES 3.0中引入了对多样本附件的渲染。这种支持有几个明显的局限性:
- 在进程中使用renderbuffers
- Renderbuffer要求意味着着色器中的多采样数据无法采样,必须通过将多采样Renderbuffer存储点放到单个采样的常规纹理中来“展开”内容,然后才可以使用常用的纹理采样方法访问该纹理
- 没有读取单个样本值的可行方法
- 无法在API级别指定在执行draw调用期间应修改哪些样本
这些是OpenGL ES 3.1中引入的多样本纹理旨在解决的一些问题。
可以使用新的glTexStorage2DMultisample入口点和GL_TEXTURE_2D_MULTISAMPLE纹理目标创建多样本纹理。不支持可变多样本纹理。
多重采样纹理没有mipmap。此外,由于底层数据的实际物理布局依赖于硬件,因此将数据写入多样本纹理的唯一方法是对其进行渲染。同样,读取内容的唯一允许方法是对纹理进行采样。只能使用最近的过滤进行采样。如果将纹理配置为线性或三线性过滤,则该纹理将被视为不完整。
若要渲染到多样本纹理,请将其附加到帧缓冲区对象附着点之一。使用glFramebufferTexture2D函数执行此操作。
注意:如果其中一个是多样本,帧缓冲区完整性规则要求所有附件都是多样本的。
要从多样本纹理采样,使用名为sampler2DMS的新采样器类型。多样本纹理采样器只能使用一个纹理采样函数texelFetch。除了采样函数的常用参数外,texelFetch还采用一个额外的整数参数,指定采样的采样索引。
注意:texelFetch函数采用整数纹理坐标。这表明不支持双线性插值。
新的API函数glSampleMaski提供了在后续draw调用中屏蔽要更新的样本集的功能。这将适用于两种类型的帧缓冲区附件:新的多样本纹理和用于OpenGL ES 3.0中多采样的renderbuffers。
多样本纹理最重要的用例是在延迟渲染器中。它们现在能够使用更复杂的消除混叠机制,因为在对G缓冲区内容进行采样时,它们现在有机会访问单个样本。
提示
多样本纹理是昂贵的,因为当切换渲染目标时,Adreno驱动程序需要加载或存储 * 字节数。当不再需要多样本数据时,考虑将多样本纹理位元到单样本容器中。然后使用单采样表示代替多采样表示,节省带宽和内存使用。