OpenGL ES 渲染优化策略

-- CRT 显示器原理
  首先从过去的 CRT 显示器原理说起。CRT 的电子枪按照上面方式,从上到下一行行扫描,扫描完成后显示器就呈现一帧画面,随后电子枪回到初始位置继续下一次扫描。为了把显示器的显示过程和系统的视频控制器进行同步,显示器(或者其他硬件)会用硬件时钟产生一系列的定时信号。当电子枪换到新的一行,准备进行扫描时,显示器会发出一个水平同步信号(horizonal synchronization),简称 HSync;而当一帧画面绘制完成后,电子枪回复到原位,准备画下一帧前,显示器会发出一个垂直同步信号(vertical synchronization),简称 VSync。显示器通常以固定频率进行刷新,这个刷新率就是 VSync 信号产生的频率。尽管现在的设备大都是液晶显示屏了,但原理仍然没有变。

  CPU 计算好显示内容提交到 GPU,GPU 渲染完成后将渲染结果放入帧缓冲区,随后视频控制器会按照 VSync 信号逐行读取帧缓冲区的数据,经过可能的数模转换传递给显示器显示。  显示系统通常会引入两个缓冲区,即双缓冲机制。在这种情况下,GPU 会预先渲染好一帧放入一个缓冲区内,让视频控制器读取,当下一帧渲染好后,GPU 会直接把视频控制器的指针指向第二个缓冲器。如此一来效率会有很大的提升。
  为了解决这个问题,GPU 通常有一个机制叫做垂直同步(简写也是 V-Sync),当开启垂直同步后,GPU 会等待显示器的 VSync 信号发出后,才进行新的一帧渲染和缓冲区更新。这样能解决画面撕裂现象,也增加了画面流畅度,但需要消费更多的计算资源,也会带来部分延迟。
  iOS 设备会始终使用双缓存,并开启垂直同步。而安卓设备直到 4.1 版本,Google 才开始引入这种机制,目前安卓系统是三缓存+垂直同步。 双缓冲的两个缓冲称之为 前帧缓冲区 (front frame buffer) 和 后帧缓冲区 (back frame buffer) 。
  每一个 iOS 原生用户界面对象都有对应的 Core Animation Layer, layer 会保存所有绘制操作的结果。苹果的 Core Animation 合成器使用 OpenGL ES 来尽可能高效地控制 GPU 、混合 layer 和切换帧缓冲区。图形程序员经常使用混合 (composite) 来描述混合图像来形成一个合成结果的过程。所有显示的图画都是通过 Core Animation 合成器来完成的,因此最终都涉及 OpenGL ES 。
  GPU 能干的事情比较单一:接收提交的纹理(Texture)和顶点描述(三角形),应用变换(transform)、混合并渲染,然后输出到屏幕上。通常你所能看到的内容,主要也就是纹理(图片)和形状(三角模拟的矢量图形)两类。

OpenGL ES优化的主要工作是在图形管道中找到影响性能的bottleneck,其bottleneck一般表现在以下几方面:
• 在应用程序代码中,如冲突检测;
• GPU与主内存间的数据传输;
• 在VP(Vertex Processor)中的顶点处理;
• 在FP(Fragment Processor)中的片断处理;

高分辨率的纹理占用大量的内存,它是Mali GPU上的主要负荷,可从以下几方面进行优化:
• 除非必要,尽量不要使用大纹理
• 总是打开纹理映射(mipmapping),有时可能降低了渲染质量
• 如果可能,排序三角形,当按render的次序render时,使其有相互覆盖的三角形放在一起
• 压缩纹理,可减少内存占用、传输带宽,Mali-400 MP GPU支持ETC纹理压缩(每个像素占4bits,且不支持alpha通道),GPU硬件可解压ETC纹理,缺点是将降低图像质量

-- 离屏渲染
OpenGL 中,GPU 屏幕渲染有以下两种方式:
 1.On-Screen Rendering
意为当前屏幕渲染,指的是 GPU 的渲染操作是在当前用于显示的屏幕缓冲区中进行。
 2.Off-Screen Rendering
意为离屏渲染,指的是 GPU 在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作。

-- 相比于当前屏幕渲染,离屏渲染的代价是很高的,主要体现在两个方面:
 1.创建新缓冲区
要想进行离屏渲染,首先要创建一个新的缓冲区。
 2.上下文切换
离屏渲染的整个过程,需要多次切换上下文环境:先是从当前屏幕(On-Screen)切换到离屏(Off-Screen);等到离屏渲染结束以后,将 离屏缓冲区的渲染结果显示到屏幕上有需要将上下文环境从离屏切换到当前屏幕。而上下文环境的切换是要付出很大代价的。
所以在图形生成的步骤我们要尽可能的避免离屏渲染,或者开启 shouldRasterize 属性。

-- openGL 渲染优化的注意点,简单的汇总一些优化渲染的注意点:
  隐藏的绘制:catextlayer 和 uilabel 都是将 text 画入 backing image 的。如果改了一个包含 text 的 view 的 frame 的话,text 会被重新绘制。
  Rasterize:当使用 layer 的 shouldRasterize 的时候(记得设置适当的 layer 的 rasterizationScale),layer 会被强制绘制到一个 offscreen image 上,并且会被缓存起来。这种方法可以用来缓存绘制耗时(比如有比较绚的效果)但是不经常改的 layer,如果 layer 经常变,就不适合用。
  离屏绘制: 使用 Rounded corner, layer masks, drop shadows 的效果可以使用 stretchable images。比如实现 rounded corner,可以将一个圆形的图片赋值于 layer 的 content 的属性。并且设置好 contentsCenter 和 contentScale 属性。
  Blending and Overdraw :如果一个 layer 被另一个 layer 完全遮盖,GPU 会做优化不渲染被遮盖的 layer,但是计算一个 layer 是否被另一个 layer 完全遮盖是很耗 cpu 的。将几个半透明的 layer 的 color 融合在一起也是很消耗的。

  在优化时可以结合 Instrument 中的 Core Animation 和 GPU Driver 来进行测试。对于局部需要特别要求性能的地方可以尝试 Facebook 开源的 AsyncDisplayKit(https://github.com/facebookarchive/AsyncDisplayKit) 或者国内大牛的 YYKit (https://github.com/ibireme/YYKit)。

Android上的文字渲染加速器硬件最初是由Renderscript团队写的,然后被很多工程师改进和优化。
OpenGL ES渲染文字,基于GPU的文字渲染系统。
  用OpenGL渲染文字的常用方法是计算包含所需字形的所有纹理集。这个操作通常是使用一些相当复杂的算法进行离线操作,这样可以在构造字形的时候更加高效。在创建这样一个纹理集之前,首先需要知道应用程序在运行时要使用的字体,包括字体样式、大小以及其它属性。
  Android3.0以后,Paint和Canvas直接被实现在Skia之上,这是一个开源的渲染库。SKia提供了一个很好的Freetype抽象实现,这是一个很热门的开源字体栅格化程序。https://www.freetype.org/
  对于Android4.4,情况变得有些复杂。Paint和Canvas都使用了一个内部的JNI API,叫做TextLayoutCache。它可以处理复杂的文字布局(CTL)。这个API依赖Harfbuzz(https://www.freedesktop.org/wiki/Software/HarfBuzz/),一个空间开源的字形引擎。TextLayoutCache的输入是一个字体和一个Java的UTF-16的字符串,输出是一个带有x/y坐标的字形列表。TextLayoutCache是支持非拉丁语言的要点,比如阿拉伯语言、希伯来语、泰国语等,本文不会解释TextLayoutCache和Harfbuzz的工作原理。
  Android4.3引入的绘制批处理和合并操作是一项重要的优化,彻底减少了大量往OpenGL驱动发送指令的问题。

字体渲染器的实现,可以浏览libhwui的GitHub- https://github.com/aosp-mirror/platform_frameworks_base/tree/master/libs/hwui

GPUImage(OpenGL ES)的性能优化、爬坑与架构改善- https://www.jianshu.com/p/fb53538a6bec
无论OpenGL ES、Metal还是Vulkan,优化都是两块:CPU和GPU。目标是减少CPU调用、降低I/O以及简化Shader代码中的复杂逻辑。
  1.减少不必要的I/O和绘制是非常有必要的。特别地,有些算法的优化时就使用了这个伎俩。
  2.Shader优化
Shader代码实现了我们的算法,想要大幅降低Shader的耗时,降低图像质量实现减少计算量是最有效的办法。在尽量可能保证质量的基础上进行优化,一般有这些办法:
(1)避免在Shader中使用循环或分支判断语句
(2)避免依赖纹理读取。即,避免在Fragment Shader计算纹理坐标,将计算提前到Vertex Shader,减少计算次数。
(3)避免交换纹理坐标分量。这会造成依赖纹理读取。
(4)避免在Fragment Shader做pow等数据计算。同样的,尽可能将计算提前到Vertex Shader,减少计算次数
(5)使用更少的颜色分量参与计算。选择影响结果的主要颜色分量参与计算,这也是减少计算量的有效方法。
(6)降低数据精度。比如,从Vertex Shader传递到Fragment Shader的纹理坐标精度从highp改成mediump也会降低一些消耗。
 

你可能感兴趣的:(OpenGL与图形学,方案及流程系列)