OpenGL纹理缓冲对象的目的是什么?

9
我们使用缓冲对象来减少CPU-GPU之间的复制操作,对于纹理缓冲对象,我们可以在缓冲对象中将目标从顶点更改为纹理。这里纹理缓冲对象还有其他优势吗?此外,它不允许过滤,这有什么缺点吗?
2个回答

20

缓冲纹理类似于一维纹理,但具有不是纹理对象的后备缓冲存储区(与任何其他纹理对象相反),而是通过绑定到TEXTURE_BUFFER的实际缓冲对象来实现。使用缓冲纹理具有几个含义和一个用例,我认为它不能映射到任何其他类型的纹理。

请注意,缓冲纹理不是缓冲对象-缓冲纹理仅使用glTexBuffer与缓冲对象关联。

相比之下,缓冲纹理可以非常巨大。核心OpenGL 4.4规范的表23.53及其后续定义了最小最大值(即实现必须提供的最小值)texelsMAX_TEXTURE_BUFFER_SIZE。计算存储在缓冲对象中的潜在像素数如下(如GL_ARB_texture_buffer_object所述):

floor(<buffer_size> / (<components> * sizeof(<base_type>))

计算结果取整并限制在MAX_TEXTURE_BUFFER_SIZE内,即为可寻址纹素的数量。

示例:

您有一个存储4MiB数据的缓冲对象。您想要一个用于寻址RGBA纹素的缓冲纹理,因此选择了内部格式RGBA8。可寻址的纹素数为

floor(4MiB / (4 * sizeof(UNSIGNED_BYTE)) == 1024^2 texels == 2^20 texels

如果您的实现支持这个数字,您可以寻址缓冲对象中的全部值范围。上述内容并不太令人印象深刻,并且可以通过当前实现中的任何其他纹理轻松实现。但是,我编写本答案的机器支持2^28 == 268435456个纹素。

使用OpenGL 4.4(以及4.3和可能的早期4.x版本),MAX_TEXTURE_SIZE是每个1D纹理2 ^ 16像素,因此缓冲区纹理仍然可以大4倍。在我的本地机器上,我可以分配一个2GiB的缓冲区纹理(实际上更大),但只有一个1GiB的1D纹理,当使用RGBAF32纹素时。

缓冲区纹理的用例是随机(如果需要,则为原子)读/写访问(通过图像加载/存储)到着色器内部的大型数据存储。是的,您可以对一个或多个块内的uniform数组进行随机读取访问,但如果您必须处理大量数据并且必须使用多个块进行工作,那么这将变得非常繁琐,即使在单个阶段的所有uniform组件的最大组合大小(其中单个float组件的大小为4字节)中查看所有uniform块的最大组合大小,

MAX_(stage)_UNIFORM_BLOCKS * MAX_UNIFORM_BLOCK_SIZE + MAX_(stage)_UNIFORM_COMPONENTS * 4

在着色器阶段中并不是真正要处理的空间很大(取决于您的实现允许上述数字的最大大小)。

纹理和缓冲纹理之间的一个重要区别是,数据存储作为常规缓冲对象可以用于纹理无法工作的操作。该扩展提到:
使用缓冲对象提供存储允许以多种不同方式指定纹理数据:通过缓冲对象加载(BufferData),直接CPU写入(MapBuffer),帧缓冲读取(EXT_pixel_buffer_object扩展)。缓冲对象还可以通过变换反馈(NV_transform_feedback扩展)进行加载,该扩展捕获GL处理的顶点的选定变换属性。其中有几个机制不需要额外的数据复制,在使用类似TexImage的传统入口时需要这些复制。
使用缓冲纹理的一个含义是,在着色器内部查找只能通过texelFetch完成。缓冲纹理也没有mip-mapped,并且如您已经提到的,在提取期间没有过滤。 附录:
自从OpenGL 4.3以来,我们拥有了所谓的着色器存储缓冲区。这些也提供对大型数据存储的随机(原子)读/写访问,但不需要像缓冲纹理那样使用texelFetch()或图像加载/存储函数进行访问。使用缓冲纹理还意味着必须处理gvec4返回值,无论是使用texelFetch()还是imageLoad()/imageStore()。一旦您想要使用结构(或其数组),并且您不想考虑使用多个vec4实例或使用多个缓冲纹理来实现类似的东西时,这将变得非常乏味。通过作为着色器存储访问的缓冲区,您可以简单地索引到数据存储并直接从缓冲区中提取一个或多个struct {}实例。
此外,由于它们与统一块非常相似,因此使用它们应该非常简单-如果您知道如何使用统一缓冲区,则不需要学习如何使用着色器存储缓冲区。

浏览相应ARB扩展的问题部分也是非常值得的。

性能影响

Daniel Rakos多年前进行了一些性能分析,既包括对统一缓冲区和缓冲区纹理的比较comparison of uniform buffers and buffer textures,也基于AMD OpenCL编程指南中的信息进行了一些更广泛的探索more general。现在有一个针对OpenCL optimization AMD平台的最新版本。

有许多因素会影响性能:

  • 访问模式和相应的缓存行为
  • 缓存行大小和内存布局
  • 访问的内存类型(寄存器、本地、全局、L1/L2等)及其相应的内存带宽
  • 通过同时进行其他操作来隐藏内存获取延迟的效果如何
  • 您所使用的硬件类型,例如具有专用内存的专用图形卡或某种统一内存架构
  • 等等。

像往常一样,在关注性能时:实现一个可行的解决方案,并查看该解决方案是否足够快速满足您的需求。否则,请实现两个或多个解决问题的方法,分析它们并进行比较。

此外,供应商特定的指南可以提供大量见解。上述OpenCL用户和优化指南提供了高级架构视角和有关如何优化CL内核的具体提示-这些内容在开发着色器时也很相关。


你能解释一下“缓冲纹理的一个用例是在着色器内部对数据存储进行随机访问。这是使用统一缓冲区或着色器存储缓冲区无法实现的。”吗?我原本以为 SSBO 用于随机访问大型数组。 - kai
我刚开始熟悉OpenGL 4,有一个问题想问。您的答案说明了缓冲纹理相对于其他纹理和统一缓冲区的优势,那么在着色器存储缓冲区上也有使用情况/性能差异吗? - kai
@kai:我大幅更新了答案。在大多数情况下,我不认为为什么SSBO应该表现出显着更好或更差的性能,因为它们本质上只是支持随机访问的线性数据数组 - 尽管使用不同的方式。我能想到的唯一原因是,用于执行缓冲区纹理查找的纹理单元可能会更有效地获取数据,但我没有任何依据来支持这一点。尝试并进行分析 - 实现相同问题的两种方法不应花费太多时间。 - thokra
嗯,“缓冲纹理的一个用例是随机(如果需要,原子)读/写访问[...]” - 人们可以使用非缓冲纹理来实现,不是吗? :) - Stefan Monov
有几种机制不需要额外的数据复制,您能说一下是哪些吗? - Stefan Monov

1

我发现的一个用例是,在索引网格中保持唯一顶点的同时存储每个基元属性(通过gl_PrimitiveID在片段着色器中访问)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接