优化纹理加载之前提到过字体渲染在更新缓存纹理的时候(记录每个纹理中的脏数据块)会尽可能加载少一点数据。但是很不幸,这个方法还是有两个限制。 首先,OpenGL ES2.0不允许随意上传一个矩形区域。glTextSubImage2D 会让你指定矩形的x/y坐标和宽高来更新矩形里面的纹理。并且它会把矩形的宽当做内存里的数据幅度,这个可以通过创建一个合适大小的CPU缓冲区来解决,但是也需要事先知道这个矩形的到底有多大。 有一个很好的折衷,就是加载包含脏数据块(矩形)的最小像素带。因为这个像素带和纹理一样宽,这样就可以节省空间。比每次都要更新整个纹理效果好得多。 第二个问题是纹理加载属于异步调用,这样可能造成相当长的CPU延迟(甚至可能会达到1毫秒,依赖纹理的大小、驱动程序还有GPU)。像之前说的那样,如果使用预缓存应该是没有问题的。但是如果使用的是“重字体”的场景,或者是区域化语言的场景的话(较多的使用字形符号比如中文),那么问题就还是会出现的。 令人欣慰的是,OpenGL3.0为这两个问题提供了解决方案,这样就可以直接使用一个像素存储的属性来加载数据矩形了。GL_UNPACK_ROW_LENGTH这个属性指定了内存源数据的宽度。需要注意的是,这个属性会影响到当前OpenGL上下文的全局状态。 加载纹理时,CPU延迟可以通过使用像素缓冲对象(PBOs)来避免。就像所有OpenGL里的缓冲区对象一样PBO会驻留在GPU中,但也可以映射到内存中。PBOs有很多有趣的属性,但是我们关心的是一个在主存中取消映射关系后还可以进行异步加载纹理的属性,此时操作队列变成: glMapBufferRange → write glyphs to buffer → glUnmapBuffer → glPixelStorei(GL_UNPACK_ROW_LENGTH) → glTexSubImage2D 调用glTexSubImage2D可以立即返回,而不用阻塞渲染器,字体渲染器可以在内存中映射整个缓冲区,而且似乎不会出现问题。这对于缓存纹理的更新操作是一个不错的方案。 这两种OpenGL ES3.0的优化方法会出现在Android4.4中。 阴影效果一般文字在渲染的时候都会带有阴影效果,这是一个相当耗费资源的操作。在临近的字形符号可以进行相互模糊操作之后,字体渲染器不再进行独立的预模糊操作。有很多中方法可以实现模糊化,但是为了在同一帧中把这些调配操作和纹理采样操作最小化,阴影效果会被简单存储为纹理,在多帧切换的时候可以保存。 因为应用程序可以轻易地拖垮GPU,所以我们还是得依靠CPU来对文字进行模糊化。最简单和高效的方式就是使用Renderscript的C++ API,只需要简单几行代码就可以实现核心功能。最简单的方法是在初始化Renderscript的时候指定RS_INIT_LOW_LATENCY标记来强制运行在CPU上。 未来的优化操作有一个优化方法我希望可以在我离开Android团队之前实现。文字预缓存、异步和部分纹理更新都是一些重要的优化操作。但是栅格化文字符号一直都是一个很耗费资源的操作,在systrace可以很容易看到(启用gfs标识然后看precacheText事件)。 对预缓存的一个简单的优化方式就是,把这个操作放到另一个工作线程去执行,把栅格化操作放到后台。这个技术已经被用到一些复杂的路径栅格化操作中,但是没有添加到OpenGL架构之中。 改进批处理和合并操作也是一个可能的优化方式,用于绘制文字的颜色一般是被发送到一个fragment阴影统一操作。这样可以减少发送到GPU的顶点数据,但副作用会产生很多不需要的批处理指令:一个批处理操作只能包含一种文字颜色。如果文字颜色也存储为顶点属性,那么就可以网GPU传递更少的数据。 源代码如果想详细地看看字体渲染器的实现,可以浏览libhwui的GitHub,可以从FontRender.cpp开始,因为很多惊喜都在这里发生,它的支持类可以在font或者sub目录找到。对了,PixelBuffer.cpp这个文件也不错,可以看看。这就是一个像素缓冲区的抽象实现,可以用于CPU(uint8_t类型的数组)或者GPU缓冲区(PBO)。 最后的话本文只是对Android的字体渲染器进行简单介绍,还有很多实现的细节没有考虑到,或者很多问题以后会说明,所以有什么问题可以尽管向我提问。 原文链接: medium 翻译: 伯乐在线 - chris 译文链接: http://blog.jobbole.com/70468/ |