设计OpenGL ES 方针介绍 (1)

6 设计OpenGL ES 方针

6.1概述

设计性能良好的OpenGL ES应用程序可能是一项艰巨的任务。 每个ES应用程序都应尝试:

  • 调整其行为以适应基础硬件的功能

  • 为应用程序中使用的渲染上下文版本选择最快和最合适的机制

  • 识别可用的扩展并充分利用它们

  • 确保抽奖电话井井有条,以便:
    1.all不对任何片段进行栅格化的所有绘制调用的数量都减少到最少
    2.the绘制调用产生的网格从最远到最远进行排序,以使透支量减少到最小
    3.绘制调用首先按渲染目标排序,然后按活动程序对象排序,最后按需要进行其他任何状态调整才能使受影响的绘制操作正常运行

  • 如果之前已经分配了完全相同的值,请避免进行冗余的API调用,以尝试将ES状态设置为特定值

  • 如果应用程序需要编译着色器或动态流处理几何或纹理数据,则使用多个渲染上下文

本章讨论了ES应用程序设计人员在创建软件时应始终牢记的所有这些方面。

进一步的信息描述了Open GL ES的发展以及为解决CPU瓶颈问题而采取的不同方法,这些问题目前是影响3D应用程序性能的最大问题。

6.2 OpenGL ES作为客户端-服务器体系结构

从根本上讲,OpenGL ES从概念上讲是基于客户端-服务器体系结构的,其中ES实施为应用程序提供了渲染服务。 单个应用程序可以通过创建多个ES渲染上下文来打开多个连接。 每个连接都分配有默认状态配置,可以通过通过该连接发送ES命令来对其进行修改或使用(例如,用于计算或渲染操作)。

在台式机和嵌入式平台上,这转化为一个模型,在该模型中,服务器的概念被运行在操作系统的内核模式和用户模式层中的驱动程序代替。 驱动程序的用户模式部分的作用就像垫片层,它从应用程序中拦截ES命令并将它们排队在命令缓冲区中。 当缓冲区空间不足或执行了刷新命令时,缓冲区将被传输到驱动程序的内核模式部分,然后由驱动程序将其传输到GPU进行实际执行。

与任何设计一样,该模型具有很多优点和缺点。 随着OpenGL ES API随着新版本的不断发展和硬件功能的改进,API的开销逐渐减少。 这方面的一个例子是,通用顶点属性数组管理在OpenGL ES中如何随着时间的推移而发展,以改善源于原始模型设计的性能问题。

发出绘制调用时,用于渲染过程的顶点着色器通常从某种形式的外部存储中获取每个顶点的属性数据。 在OpenGL ES 1.1和ES 2.0中,关于数据源,有两个选项可供选择:

  • 由应用程序维护的进程侧缓冲区。
  • 另外,假设硬件具有足够的可用提交内存量,则将数据存储在VRAM(或驱动程序认为适当的任何其他存储区域,给定分配存储时传递的使用提示)。 在OpenGL ES 1.1中,此功能被认为是客户端功能,与ES 2.0相反,后者被称为通用顶点属性数组。 尽管名称不同,但这些概念背后的总体思路是相同的。

上面列表中描述的第一个数据源非常昂贵。 由于应用程序可以随时自由更改所有客户端缓冲区,因此OpenGL ES实现必须缓存执行绘图调用所需的所有进程缓冲区区域的内容。 只有在缓存了数据之后,执行流才能返回给应用程序。 此策略有两个问题:

  • 这些区域会占用大量空间,驾驶员需要即时分配这些空间,然后再进行维护。 在内存可用性有限的嵌入式环境中,这可能是一个严重的问题。
  • 内存复制操作不是免费的。

客户端缓冲区的最大问题是GPU通常无法直接访问该类型的内存区域。 相反,需要将其复制到硬件可以直接访问的另一个存储块中。 此时要注意的重要一点是,由缓冲区对象存储(也称为顶点缓冲区对象)支持的通用顶点属性数组不受此问题的影响。 毕竟,数据可能已经存储在GPU友好的内存区域中。

修改通用顶点属性数组的配置通常本身就是一项代价高昂的活动。 由于OpenGL ES 1.1或ES 2.0不支持任何封装该信息的状态容器,因此实施被迫在绘制调用之间即时更改配置,这增加了渲染单个帧所需的时间。 如果应用程序想要使用缓冲区对象支持的存储,则在执行实际绘制操作之前,它通常必须使用glBindBuffer()调用来更新GL_ARRAY_BUFFER绑定。 因此,绑定操作可能很昂贵。

OpenGL ES 3.0引入了顶点数组对象,以降低效率。 由于VAO会缓存每个通用顶点属性数组配置更新时最新的GL_ARRAY_BUFFER绑定,因此在渲染一组网格时,应用程序现在唯一要做的就是在发出网格之前在特定于网格的顶点数组对象之间进行切换 相应的绘制调用集。 这不是进行大量昂贵的API调用。

OpenGL ES 3.0禁止开发人员使用客户端缓冲区作为通用顶点属性数组数据的支持,因此不允许应用程序将这些缓冲区用于默认顶点编号为0的默认顶点数组对象之外的任何顶点数组对象。 保持向后兼容性:所有OpenGL ES 2.0应用程序都必须在OpenGL ES 3.0上下文中完美运行,并且如果删除了默认顶点数组对象的客户端缓冲区支持,则将无法满足该要求。

OpenGL ES 3.0还引入了一些新的机制,这些机制使应用程序可以命令GPU直接写入VRAM支持的缓冲区,或者让其将缓冲区对象区域视为纹理数据的源。 有关更多信息,请参见OpenGL ES 3.0规范,网址为:
http://www.khronos.org/registry/gles/specs/3.0/es_spec_3.0.3.pdf

  • 像素缓冲区对象–将缓冲区对象绑定到GL_PIXEL_PACK_BUFFER绑定点,以便glReadPixels()调用会将结果数据写入指定的缓冲区对象区域。 通过将缓冲区对象与GL_PIXEL_UNPACK_BUFFER绑定点相关联,任何获取纹理数据的调用(例如glCompressedTex * Image()和glTex * Image())都将使用该内存区域作为源,而不是客户端缓冲区。
  • 转换反馈–顶点着色器保存到任何输出变量的数据可以存储在缓冲区对象区域中。

这些机制将OpenGL ES引导到GPU生成数据然后将其重新用于不同目的的方向。 通过避免CPU干预,API开销得以进一步降低。 Open GL ES 3.1引入了更多工具,可用于使GPU更加自给自足。 例如。:

  • 计算着色器–利用GPU的SIMD架构,并允许GPU程序修改缓冲区对象存储和纹理mipmap内容。
  • 图像–着色器现在可以随时根据需要修改缓冲区对象的存储
  • 间接绘图调用–现在可以使用缓冲区对象存储中存储的参数来触发实例化绘图调用

在许多情况下,通过组合使用这些工具,可以大大减少每帧RAM-VRAM传输的次数,从而显着提高应用程序性能。

6.3 OpenGL ES作为图形管线

OpenGL ES是一种异步状态机,即使API调用通常可以快速执行,也并不意味着它们已由GPU处理。 取而代之的是,他们很可能被排队等待执行,而这将在未来某个时候发生。

如果有任何API函数,则可能发生流水线刷新,这对应于所有排队的命令向下传输到GPU的命令缓冲区,和/或停顿,发生在驱动程序需要等待硬件完成对刷新命令的处理时 下面描述的由应用程序调用:

  • eglSwapBuffers()
  • glCheckFramebufferStatus()
  • glClientWaitSync()或glWaitSync()调用
  • glCompressedTexImage2D()和glCompressedTexImage3D()
  • glCompressedTexSubImage2D()和glCompressedTexSubImage3D()
  • 如果已配置任何启用的顶点属性数组,则任何glDraw *()绘制调用使用客户端缓冲区
  • 任何glGet *()getter调用
  • 任何glIs *()getter调用。
  • glMapBufferRange(),除非使用了GL_MAP_UNSYCHRONIZED_BIT标志
  • glReadPixels()
  • glTexImage2D()和glTexImage3D()
  • glTexSubImage2D()和glTexSubImage3D()
  • glBufferSubData()

由于以下原因,这些功能可能会将延迟引入渲染管道:

  • 任何依赖客户端数据的API调用都必须立即执行。 如果将执行推迟到将来的不确定时间,则在ES开始处理该调用时,调用时可用的缓冲区可能已经超出范围或已释放。
  • 任何将一条信息返回给调用者的API调用都可能需要清除管道。 如果未刷新它并且必须通过查询硬件来检索值(这是特定于实现的详细信息),则它们返回的值或值集可能无效,因为它/它们将不代表最新的ES状态配置。
  • 根据EGL规范,前/后缓冲区交换操作执行隐式刷新操作。 这是为了确保尽快渲染框架,以便可以将其显示在显示器上。
  • 在排队的API指令完成执行之前,缓冲区对象存储可能未映射到进程空间。 否则,可能会遇到进程读取仍在写入的数据存储的情况。 如果指定了GL_MAP_UNSYNCHRONIZED_BIT标志,则驱动程序可以忽略该标志,因为应用程序已意识到该风险。
  • 需要访问之前已渲染到的任何渲染目标的操作(例如glReadPixels())无法在仍可能渲染到的缓冲区上进行操作。 卡在消息队列中的所有绘图调用都必须先执行,然后流程才能检索可视数据或将其复制到缓冲区对象区域。

如第4.1节所述,OpenGL ES 3.1还引入了一种称为图像的新工具。 这些允许着色器调用发出直接对纹理mipmap的内容进行操作的读取和写入操作。 因为着色器调用以异步方式且以宽松定义的顺序运行,所以必须注意确保正确同步了基于图像的绘制调用之后并利用可能修改的内存的所有ES操作。 这可以通过调用glMemoryBarrier()或glMemoryBarrierByRegion()来实现。 这些函数采用单个位域参数,其中每个位对应于某种类型的操作,该操作可能依赖于修改后的内存区域。 有关这些位的更多信息,请参见以下网址的参考页:
http://www.khronos.org/opengles/sdk/docs/man31/

提醒一下,批绘制操作很重要。 在查看单个绘制调用调用时,大部分时间都花费在验证GL状态配置上(有关更多信息,请参见第6.4.8节)。 有多种降低成本的方法:

  • 对于OpenGL ES 2.0(及更高版本),请考虑使用基于索引三角带的绘制调用,这通常是在Adreno体系结构上绘制几何图形的最有效方法。 但是,这种方法可能会导致性能损失,因此请务必进行性能测试以确定要使用的方法。
  • 对于OpenGL ES 3.0和ES 3.1应用程序,对所有几何图形使用索引实例化绘制调用,这些几何图形的拓扑是恒定的,并且可以通过属性传递不同的数据来令人满意地更改其外观。 在所有其他情况下,请使用索引范围内的绘制调用:glDrawRangeElements()告诉ES绘制调用的索引数据集的最小/最大值是什么。 这使OpenGL ES实施可以将缓存的顶点数据预缓存到指定的索引范围,从而可以改善渲染性能。
  • 如果考虑到该应用程序是使用OpenGL ES 3.1编写的,则可以通过为特定场景图的所有静态几何图形填充绘制调用参数来预烘焙缓冲区对象存储。 一旦存储准备就绪,就发出索引化的绘图调用,而不是常规的绘图调用。 这会将验证成本的很大一部分转移到Adreno架构中的GPU,从而显着提高了渲染效率。
注意:
间接绘图调用是隐式实例化的。 这意味着,如果在编写引擎时考虑了实例化的绘制调用,则可以通过迁移到间接渲染调用来进一步改善渲染性能。

这些优化应该始终牢记在心。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值