【OpenGL经验谈03】关于缓冲区对象流

2024-03-17 00:28

本文主要是介绍【OpenGL经验谈03】关于缓冲区对象流,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

  • 说明
  • 一、问题
  • 二、显式多重缓冲
  • 三、缓冲区重新指定
  • 四、缓冲区更新
  • 五、持久映射流
    • 5.1、持续可见性
  • 六、流媒体优化

说明

   缓冲区对象流是在使用这些缓冲区时用新数据频繁更新缓冲区对象的过程。流媒体的工作原理如下。您对缓冲区对象进行修改,然后执行从缓冲区读取的 OpenGL 操作。然后,在调用该 OpenGL 操作后,您可以使用新数据修改缓冲区对象。接下来,您执行另一个 OpenGL 操作以从缓冲区中读取数据。

   流式传输是一个修改/使用周期。在一个修改/使用周期与另一个修改/使用周期之间可能存在交换缓冲区(或等效的帧改变过程),但不是必须的。

一、问题

   OpenGL 提供了使该过程正常运行的所有保证,但使其快速运行才是真正的问题。流式传输中最大的危险,也是导致最多问题的危险,是隐式同步。

   OpenGL 规范允许延迟绘图命令的执行。这允许您绘制很多东西,然后让 OpenGL 自行处理。因此,在调用使用缓冲区对象的任何操作之后,完全有可能开始尝试将新数据上传到该缓冲区。如果发生这种情况,OpenGL 规范要求线程停止,直到所有可能受缓冲区对象更新影响的绘图命令完成。

   这种隐式同步是流式传输顶点数据时的主要敌人。

   有多种策略可以解决这个问题。某些实现对于某些实现比其他实现效果更好。每一种都有其优点和缺点。

   当使用非不可变缓冲区时,您应该确保STREAM在缓冲区的提示中。

二、显式多重缓冲

   这个解决方案相当简单。您只需创建两个或多个相同长度的缓冲区对象即可。当您使用一个缓冲区对象时,您可以修改另一个缓冲区对象。根据您的实现可以提供多少并行性,您可能需要两个以上的缓冲区才能完成这项工作。

   该解决方案的主要缺点是它需要使用许多不同的缓冲区对象(单独的缓冲区句柄)。因此,您需要更改每帧用于 GPU 操作的缓冲区。

三、缓冲区重新指定

   此解决方案是在开始修改缓冲区对象之前重新分配它。这被称为缓冲区“孤立”。有两种方法可以做到这一点。

   第一种方法是使用NULL指针调用glBufferData,其大小和使用提示与之前完全相同。这允许实现简单地在后台为该缓冲区对象重新分配存储。由于分配存储(可能)比隐式同步更快,因此与同步相比,您可以获得显着的性能优势。由于您传递了 NULL,如果一开始就不需要同步,则可以将其简化为无操作。之前发送的 OpenGL 命令仍将使用旧存储。如果您继续一遍又一遍地使用相同大小,则 GL 驱动程序可能根本不会执行任何分配,而只是从未使用的缓冲区队列中拉出旧的空闲块并使用它(尽管当然这不能保证),所以它可能非常有效。

   将glMapBufferRange与GL_MAP_INVALIDATE_BUFFER_BIT一起使用时,您可以执行相同的操作。您还可以使用glInvalidateBufferData(如果可用)。

   所有这些都使 GL 实现能够自由地孤立以前的存储并分配新的存储。这就是为什么这被称为“孤儿”。

   每当您看到其中任何一个时,请将其视为 OpenGL 的指令:1) 分离旧的存储块,2) 为您提供一个新的存储块来使用,所有这些都在同一个缓冲区句柄后面。旧的存储块将被 OpenGL 放入空闲列表中,并在队列中没有可能引用它的绘制命令时重新使用(例如,一旦所有排队的 GL 命令都已完成执行)。

   显然,这些方法将缓冲存储与客户端可访问的工作空间分离,因此仅当不再需要从 GL 客户端读取或更新此特定存储块时,它们才实用。除非您计划将缓冲区更新与此技术结合使用,否则最好是在整个缓冲区而不是缓冲区的一部分上进行更新,并且每次都覆盖该缓冲区中的所有数据。

   此方法的一个问题是它依赖于实现。仅仅因为一个实现可以自由地做某事并不意味着它会这样做。

四、缓冲区更新

   缓冲区更新是一种流式传输形式,您需要非常小心。它通常与缓冲区重新指定结合使用,以提高提交性能。

   为了实现缓冲区更新,我们使用GL_MAP_UNSYNCHRONIZED_BIT调用glMapBufferRange。这告诉 OpenGL 不要进行任何隐式同步。当你看到这个时,想一想“OpenGL,请给我一个‘快速’的缓冲区。如果你给我这个缓冲区对象与上次相同的缓冲区,那就没问题了。我保证不会修改这个缓冲区的任何可能被修改的部分。正在被我已经提交的 GL 命令使用。请相信我。”

   虽然没有同步,但这并不意味着同步不重要。事实上,如果您修改 GPU 上已排队的 GL 命令(例如绘图命令)将从中读取的部分缓冲区,您将得到未定义的结果。不要那样做。

   使用缓冲区更新的基本用例是,您可以使用未同步的映射逐步填充缓冲区对象、写入、取消映射、使用该缓冲区子区域发出 GL 命令、冲洗/重复。只要您的写入永远不会重叠,那么您就是安全的,并且在填满该缓冲区之前不需要考虑“弄乱 GPU 的数据”。填满后,您可以执行以下两种操作之一来继续避免破坏 GPU 的缓冲区数据:1) orphan或 2) Synchronize。孤儿是首选方法,因为避免同步通常会产生更高的性能(因为同步通常涉及等待)。

   对于orphan,只需使用缓冲区重新指定技术(glBufferData(NULL)、glMapBufferRange(GL_MAP_INVALIDATE_BUFFER_BIT)或glInvalidateBufferData)。然后,您将在缓冲区句柄下方获得一个新的存储块,可以在其上进行书写,其他 GL 命令无法引用该存储块,因此不需要同步。

   或者,要同步,请使用同步对象。如果您在从缓冲区读取的所有命令后面放置一个栅栏,则可以在映射缓冲区之前检查该栅栏是否已完成。如果没有,那么您可以等待更新缓冲区,同时执行一些其他重要任务。如果没有其他任务要执行,您还可以使用栅栏强制同步。一旦栅栏完成,您就可以使用 GL_MAP_UNSYNCHRONIZED_BIT 自由映射缓冲区,以防实现不知道缓冲区可以更新。

   有关一般缓冲流的更多详细信息,请参阅 此线程。请特别注意 Rob Barris 的帖子。

五、持久映射流

   鉴于 OpenGL 4.4或ARB_buffer_storage的可用性,使用缓冲区的持久映射成为可能。

   这里的想法是分配一个不可变的缓冲区,其大小是所需大小的 2-3 倍,当您从缓冲区的一个区域执行操作时,同时写入另一区域。先前的映射方案之间的区别在于您不经常映射和取消映射缓冲区。当您创建缓冲区时,您可以持久地映射它,并保持它的映射状态,直到删除缓冲区为止。

   这需要将glBufferStorage与GL_MAP_WRITE和GL_PERSISTENT_BIT一起使用。它还需要在映射时使用具有相同位的 glMapBufferRange 。

   一般算法如下。缓冲区在逻辑上分为 3 个部分:您正在写入的部分和当前可能正在使用的两个部分。

   第一步是写入缓冲区的第 1 部分。一旦完成写入,您必须通过刷新来使该范围的数据对 OpenGL 可见(如果您没有连贯地映射)。然后,您需要采取任何措施来确保该数据对 OpenGL 可见。一旦数据可见,您就可以发出一些从缓冲区的该部分读取的渲染命令。发出从缓冲区读取的所有命令后,您将创建一个栅栏同步对象。

   下一帧,您开始写入缓冲区第 2 部分。您执行上述所有操作,并创建一个新的栅栏同步对象。将每个缓冲区部分的同步对象分开。

   您对下一帧的缓冲区第 3 部分执行相同的操作。

   在第四帧上,您想再次开始使用第 1 部分。但是,您需要先检查第 1 部分的同步对象,看看它是否已完成,然后才能开始。仅当该部分的同步对象已完成时,您才能开始写入该部分。

5.1、持续可见性

   写入持久映射缓冲区并不能保证 OpenGL 自动看到写入的数据。为了确保可见性,您必须执行以下三件事之一。

   使用GL_COHERENT_BIT一致地映射缓冲区。这还需要使用GL_COHERENT_BIT分配缓冲区。一致映射的缓冲区始终确保后续操作的可见性(但这并不意味着您可以写入当前正在读取的内容。您仍然需要同步)。虽然这听起来可能很慢,但有一些证据表明性能成本可以忽略不计,至少在某些硬件上是这样。
   使用GL_MAP_FLUSH_EXPLICIT_BIT映射缓冲区,并在缓冲区的写入部分上调用glFlushMappedBufferRange 。

六、流媒体优化

   glMapBufferRange还有另一个您应该了解的标志: GL_MAP_INVALIDATE_RANGE_BIT。这与上面已经介绍过的GL_MAP_INVALIDATE_BUFFER_BIT不同。

   根据 Rob Barris 的说法,MAP_INVALIDATE_RANGE_BIT与WRITE位(但不是READ位)相结合基本上向驱动程序表明它不需要包含任何有效的缓冲区数据,并且您承诺写入映射的整个范围。这可以让驱动程序为您提供一个指向尚未初始化的暂存内存的指针。例如,驱动程序分配了直写式未缓存内存。请参阅这篇文章了解更多详细信息。

这篇关于【OpenGL经验谈03】关于缓冲区对象流的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/817229

相关文章

Python打印对象所有属性和值的方法小结

《Python打印对象所有属性和值的方法小结》在Python开发过程中,调试代码时经常需要查看对象的当前状态,也就是对象的所有属性和对应的值,然而,Python并没有像PHP的print_r那样直接提... 目录python中打印对象所有属性和值的方法实现步骤1. 使用vars()和pprint()2. 使

MySQL JSON 查询中的对象与数组技巧及查询示例

《MySQLJSON查询中的对象与数组技巧及查询示例》MySQL中JSON对象和JSON数组查询的详细介绍及带有WHERE条件的查询示例,本文给大家介绍的非常详细,mysqljson查询示例相关知... 目录jsON 对象查询1. JSON_CONTAINS2. JSON_EXTRACT3. JSON_TA

Go语言中泄漏缓冲区的问题解决

《Go语言中泄漏缓冲区的问题解决》缓冲区是一种常见的数据结构,常被用于在不同的并发单元之间传递数据,然而,若缓冲区使用不当,就可能引发泄漏缓冲区问题,本文就来介绍一下问题的解决,感兴趣的可以了解一下... 目录引言泄漏缓冲区的基本概念代码示例:泄漏缓冲区的产生项目场景:Web 服务器中的请求缓冲场景描述代码

C#之List集合去重复对象的实现方法

《C#之List集合去重复对象的实现方法》:本文主要介绍C#之List集合去重复对象的实现方法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录C# List集合去重复对象方法1、测试数据2、测试数据3、知识点补充总结C# List集合去重复对象方法1、测试数据

Spring中管理bean对象的方式(专业级说明)

《Spring中管理bean对象的方式(专业级说明)》在Spring框架中,Bean的管理是核心功能,主要通过IoC(控制反转)容器实现,下面给大家介绍Spring中管理bean对象的方式,感兴趣的朋... 目录1.Bean的声明与注册1.1 基于XML配置1.2 基于注解(主流方式)1.3 基于Java

C++/类与对象/默认成员函数@构造函数的用法

《C++/类与对象/默认成员函数@构造函数的用法》:本文主要介绍C++/类与对象/默认成员函数@构造函数的用法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录名词概念默认成员函数构造函数概念函数特征显示构造函数隐式构造函数总结名词概念默认构造函数:不用传参就可以

C++类和对象之默认成员函数的使用解读

《C++类和对象之默认成员函数的使用解读》:本文主要介绍C++类和对象之默认成员函数的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、默认成员函数有哪些二、各默认成员函数详解默认构造函数析构函数拷贝构造函数拷贝赋值运算符三、默认成员函数的注意事项总结一

golang 对象池sync.Pool的实现

《golang对象池sync.Pool的实现》:本文主要介绍golang对象池sync.Pool的实现,用于缓存和复用临时对象,以减少内存分配和垃圾回收的压力,下面就来介绍一下,感兴趣的可以了解... 目录sync.Pool的用法原理sync.Pool 的使用示例sync.Pool 的使用场景注意sync.

SpringBoot项目中Redis存储Session对象序列化处理

《SpringBoot项目中Redis存储Session对象序列化处理》在SpringBoot项目中使用Redis存储Session时,对象的序列化和反序列化是关键步骤,下面我们就来讲讲如何在Spri... 目录一、为什么需要序列化处理二、Spring Boot 集成 Redis 存储 Session2.1

Java实例化对象的​7种方式详解

《Java实例化对象的​7种方式详解》在Java中,实例化对象的方式有多种,具体取决于场景需求和设计模式,本文整理了7种常用的方法,文中的示例代码讲解详细,有需要的可以了解下... 目录1. ​new 关键字(直接构造)​2. ​反射(Reflection)​​3. ​克隆(Clone)​​4. ​反序列化