仓酷云

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 1564|回复: 12
打印 上一主题 下一主题

[学习教程] IOS设计Android字体衬着器――利用OpenGL ES举行高效笔墨衬着仓酷云

[复制链接]
灵魂腐蚀 该用户已被删除
跳转到指定楼层
楼主
发表于 2015-1-18 11:30:22 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
很少去思考,没有去多问几个为什么。这是学习的大忌,我认识到了自己学习方法上的错误。孔子说,学而不思则罔,思而不学则殆。一点也没错,学和思是要结合的,这样才能进步。现在回想一下,我学到了什么?自己也无言以对了。任何有多年客户端开辟履历的开辟者都应当晓得庞大的笔墨衬着是怎样事情的。最少在2010年之前,我刚入手下手写libhwui的时分(这是一个基于Android2.0的2D绘画库),我就意想到处置笔墨偶然会比其他方面更庞大,出格是当你实验用GPU在屏幕长进行绘制的时分。
笔墨与Android

Android上的笔墨衬着减速器硬件最后是由Renderscript团队写的,然后被良多工程师改善和优化,包含我亲睦友ChetHaase。在收集上,能够很简单找到良多关于怎样利用OpenGLES衬着笔墨的教程。假如以为还不敷,能够看看关于游戏的文章,只看关于笔墨衬着部分就行。
本文说不是很别致的常识,只是关于良多开辟者来讲,经由过程本文能够从深条理上懂得怎样完成一个基于GPU的笔墨衬着体系,文章最初还先容了一些对照简单完成的优化办法。
用OpenGL衬着笔墨的经常使用办法是盘算包括所需字形的一切纹理集。这个操纵一般是利用一些相称庞大的算法举行离线操纵,如许能够在机关字形的时分加倍高效。在创立如许一个纹理集之前,起首必要晓得使用程序在运转时要利用的字体,包含字体款式、巨细和别的属性。
在Android上,提早举行字体纹理天生不是一个有用的计划。Android上的UI工具其实不能晓得使用体系会利用甚么字体和字形,而且使用还能够在运转时载进自界说的字体,这是次要的限定。Android字体衬着还必需遵守以下条例:


  • 它必需在运转时创建字体缓存;
  • 它必需可以处置大批的字体;
  • 它必需能够处置大批的标记;
  • 它必需要尽量削减字体上的资本损耗;
  • 必需运转要疾速;
  • 在低端和高端呆板上也可以优秀运转;
  • 能完善与别的组件分离(驱动程序或GPU)。
字体衬着器的完成

在进进底层OpenGL字体衬着器事情道理之前,我们先从使用层利用的初级其余API入手下手。这些API关于了解libhwui很主要。
笔墨API

用于结构和绘制笔墨次要有4个API:


  • android.widget.TextView:一个能够处置笔墨结构和衬着的视图组件。
  • android.text.*:一个能够创立作风化笔墨和结构的类汇合。
  • android.graphics.Paint:用于丈量笔墨。
  • android.graphics.Canvas:用于衬着笔墨。
TextView和android.text的都是在Paint和Canvas上的初级API。Android3.0今后,Paint和Canvas间接被完成在Skia之上,这是一个开源的衬着库。SKia供应了一个很好的Freetype笼统完成,这是一个很抢手的开源字体栅格化程序。

<br>
关于Android4.4,情形变得有些庞大。Paint和Canvas都利用了一个外部的JNIAPI,叫做TextLayoutCache。它能够处置庞大的笔墨结构(CTL)。这个API依附Harfbuzz,一个空间开源的字形引擎。TextLayoutCache的输出是一个字体和一个Java的UTF-16的字符串,输入是一个带有x/y坐标的字形列表。
TextLayoutCache是撑持非拉丁言语的要点,好比阿拉伯言语、希伯来语、泰国语等,本文不会注释TextLayoutCache和Harfbuzz的事情道理,但自己激烈倡议读者往进修进修CTL。假如在开辟使用的时分必要撑持非拉丁言语情况,那末就要进修它了。假如你已经介入过OpenGL衬着笔墨的文章中的会商,就会发明这类特别的成绩是很少见的。绘制笔墨比复杂排布字形更庞大。某些言语中,好比阿拉伯语是从右到左的,另有泰语乃至必要把字形排布在前一个字形的下面大概上面。

<br>
也就是说,当间接或直接挪用Canvas.drawText()函数的时分,OpenGL衬着器不会收到你发送的参数,而是收到一串数字、标记标识,另有x/y坐标汇合。
点阵化弛缓存

字体衬着器的每个绘制办法都是和字体相干的。字体用于缓存一般字形标记,而字形标记又被存储在缓存布局中(缓存布局能够包括分歧字体的字形标记)。缓存布局是持有多个缓冲区的一个主要的对象,有block汇合、pixel缓冲区、OpenGL布局处置器,另有点阵缓冲区(也就是网格)。

<br>
这个对象存储的数据布局对照复杂:


  • 在字体衬着器中字体是存储在一个LRU缓存中的;
  • 字形标记分离存储在对应的map字体汇合中(key就是字形文件的identifier);
  • 缓存布局利用一个块链表汇合来纪录空间的巨细;
  • 像素缓冲区是一个uint8_t大概uint32_t范例的数组(作alpha值和RGBA的缓存);
  • 网格实在就是一个极点数组,带有两个属性:x/y地位和u/v坐标;
  • 一个GLuint的处置器。
字体衬着器对分歧范例的缓存布局供应了几种缓存纹理实例,也就是依据分歧的巨细辨别,这个巨细大概会依据分歧设备而有所分歧,这里这里说的是默许的巨细(缓存的数目是硬编码的):


  • 1024*512alpha缓存。
  • 2048*256alpha缓存。
  • 2028*512alpha缓存。
  • 1024*512alpha缓存。
  • 2048*256alpha缓存。
当缓存纹理对象创立以后,其对应的缓冲区不会主动分派空间,除1024*512的alpha缓存老是主动分派外,别的的都是依据必要来分派空间。
字形标记以列的情势打包在纹理中,只需字体衬着器碰到没有缓存的标记,它就会向缓存纹理哀求呼应的范例(存储在以上的有序列表中),然后缓存该标记。
这是上述的blocks列表利用到的中央,这个列表包括了以后已分派的列和一切未分派的空间。假如字形标记和已存在的列婚配,那该字形标记就会被加到该列的底部。
假如一切列都被占用,从右边的残剩空间启示新列。由于一切字体都是等宽的,衬着器会把每一个字形的宽度弄成4像素的倍数(默许是4像素)。这是对列的厚利用和字形打包的一个折中,这个打包今朝还不是很好,可是完成起来对照快。
一切的字形标记都存储在一个含有1个像素边框的布局中,如许在双线过滤采样的时分能够制止伪迹的发生。
在笔墨带有缩放变形操纵的衬着中,懂得笔墨什么时候被衬着也长短常主要的。这个变形操纵间接到Skia/Freetype来处置,这就意味着字形标记是在缓存布局中变形存储的。如许能够改良衬着的质量。侥幸的是,笔墨一样平常很少做缩放动画效果,就算是利用了,也只是计划很少的字形标记。自己做过良多实行,也没有找到一个实践利用的场景。
另有别的关于paint的属性会影响字形标记的栅格化和存储的:粗体、斜体、另有X缩放(在Canvas上做矩阵变更)、字体作风和线条宽度等。
栅格化的可选计划

现实上,另有别的的体例往在GPU上处置笔墨字形标记。能够间接被衬着程向量,可是如许做开支很年夜。我查询拜访过标志间隔字段的办法,可是复杂完成的时分碰到了精度的成绩(创立曲线的时分会不不乱)。
自己倡议读者能够看看Glyphy这个项目。这是一个开源库,作者是Harfbuzz。项目在标志间隔字段手艺长进行延长,同时也办理了精度的成绩。我临时没有花太多工夫看这个项目。可是上一次在做着色器的时分,发明这类手艺在Android上是被克制利用的。
预缓存手艺

字形标记缓存是必定要做的。假如做预缓存的话,效果会更好。由于libhwui是一个提早的衬着器(和Skia的疾速形式恰好相反),一切屏幕上呈现的字形都是一帧一帧入手下手的。在一系列的显现操纵(批处置和兼并操纵)中,字体衬着器必要尽量多地缓存字形标记。
利用预缓存手艺的次要上风在于,能够完整大概最小化纹理加载的工夫。纹理加载操纵是损耗十分年夜的,它会推迟CPU大概GPU。乃至在帧衬着过程当中,改动纹理还会在GPU系统布局带来更多内存的压力。
ImaginationTech的PowerVRmlSGXGPUs利用了提早叠加手艺架构,能够供应良多风趣的特征。但假如在衬着帧时必要修正纹理,会强迫请求驱动程序对纹理举行复制。由于字体布局相称年夜,假如欠好优点理纹理加载的话,很简单就内存耗尽了。
如许的场景的确产生在GooglePlay的一个使用中。这个APP是一个复杂的盘算器,仅利用一些数学标记和数字举行复杂的绘制按钮。字体衬着器在某的时分乃至衬着不出第一帧。由于按钮是一连举行绘制的,每个按钮城市触发一个纹理加载,然后复制全部字体缓存。体系基本没有这么多内存往存储这么多缓存的备份。
清空缓存

由于用作字形缓存的纹理长短常年夜的,它们偶然会被体系接纳再使用,以便为别的程序更多的RAM。
当用户埋没以后的使用时,体系给使用发送一条动静请求开释尽量多的内存。很分明,这就必要烧毁最年夜的字形缓存布局。在Android中,这个年夜缓存布局就是一切字形的缓存。除默许第一个创立的之外(1024*512的默许缓存)。
纹理布局在没有存储空间的时会被清空。字体衬着器利用LRU算法对素有字体举行纪录,仅仅是纪录罢了。假如必要,就会依据比来起码利用的纹理来扫除内存。今朝没有供应这个操纵,可是它的确是一个不错的优化战略。
批处置和兼并操纵

Android4.3引进的绘制批处置和兼并操纵是一项主要的优化,完全削减了大批往OpenGL驱动发送指令的成绩。
为了举行兼并操纵,字体衬着器在举行多种绘制挪用的时分会缓存笔墨,每一个缓存纹理城市具有一个客户真个2048quads的数组(1quad=1glyph)。当挪用lilbhwui中的一个笔墨绘制API时,字体衬着器猎取符合的网格为每一个字形标记举行地位和u/v坐标的绘制。网格在批处置的末了被发送到GPU上(由提早显现体系决意)。大概当一个quad的缓冲区满了的时分,大概会呈现多网格衬着统一个字符串的情形——一个字符缓存占用一个网格。
这个优化历程很简单完成,对显现效果匡助也很年夜。由于字体衬着器利用多缓存布局,以是在一个字符串的衬着历程汇总,大概字形标记会来自分歧的纹理。假如没有批处置好兼并操纵的话,每一个绘制挪用都要传送给GPU。字体衬着器就必要不休切换分歧的缓存布局,如许会带来很年夜的损耗。
在测试字体衬着器的时分,我已在一个测试App中发明了这个成绩。这个App只是复杂地用分歧的款式和巨细衬着一句“helloworld”。个中字母“o”被存储在分歧的纹理中,和别的的字符纷歧样。这类情形招致字体衬着器入手下手时只绘制了“hell”,然后衬着“o”,然后再衬着“w”,然后在衬着“o”,接着才是“rld”。这5个绘制挪用和5个纹理举行绑定毗连后,只要个中两个是实践必要的,如今衬着器先绘制“hellwrld”,然后在一同绘制两个“o”,这就是批处置和兼并操纵的优点了。
优化纹理加载

之条件到过字体衬着在更新缓存纹理的时分(纪录每一个纹理中的脏数据块)会尽量加载少一点数据。可是很不幸,这个办法仍是有两个限定。
起首,OpenGLES2.0不同意随便上传一个矩形地区。glTextSubImage2D会让你指定矩形的x/y坐标和宽高来更新矩形内里的纹理。而且它会把矩形的宽当作内存里的数据幅度,这个能够经由过程创立一个符合巨细的CPU缓冲区来办理,可是也必要事前晓得这个矩形的究竟有多年夜。
有一个很好的折中,就是加载包括脏数据块(矩形)的最小像素带。由于这个像素带和纹理一样宽,如许就能够节俭空间。比每次都要更新全部纹理效果好很多。
第二个成绩是纹理加载属于异步伐用,如许大概形成相称长的CPU提早(乃至大概会到达1毫秒,依附纹理的巨细、驱动程序另有GPU)。像之前说的那样,假如利用预缓存应当是没有成绩的。可是假如利用的是“重字体”的场景,大概是地区化言语的场景的话(较多的利用字形标记好比中文),那末成绩就仍是会呈现的。
使人欣喜的是,OpenGL3.0为这两个成绩供应懂得决计划,如许就能够间接利用一个像素存储的属性来加载数据矩形了。GL_UNPACK_ROW_LENGTH这个属性指定了内存源数据的宽度。必要注重的是,这个属性会影响到以后OpenGL高低文的全局形态。
加载纹理时,CPU提早能够经由过程利用像素缓冲对象(PBOs)来制止。就像一切OpenGL里的缓冲区对象一样PBO会驻留在GPU中,但也能够映照到内存中。PBOs有良多风趣的属性,可是我们体贴的是一个在主存中作废映照干系后还能够举行异步加载纹理的属性,此时操纵行列酿成:
glMapBufferRange→writeglyphstobuffer→glUnmapBuffer→glPixelStorei(GL_UNPACK_ROW_LENGTH)→glTexSubImage2D
挪用glTexSubImage2D能够当即前往,而不必堵塞衬着器,字体衬着器能够在内存中映照全部缓冲区,并且仿佛不会呈现成绩。这关于缓存纹理的更新操纵是一个不错的计划。
这两种OpenGLES3.0的优化办法会呈现在Android4.4中。
暗影效果

一样平常笔墨在衬着的时分城市带有暗影效果,这是一个相称泯灭资本的操纵。在邻近的字形标记能够举行互相含混操纵以后,字体衬着器不再举行自力的预含混操纵。有良多中办法能够完成含混化,可是为了在统一帧中把这些分配操纵和纹理采样操纵最小化,暗影效果会被复杂存储为纹理,在多帧切换的时分能够保留。
由于使用程序能够容易地拖垮GPU,以是我们仍是得依托CPU来对笔墨举行含混化。最复杂和高效的体例就是利用Renderscript的C++API,只必要复杂几行代码就能够完成中心功效。最复杂的办法是在初始化Renderscript的时分指定RS_INIT_LOW_LATENCY标志来强迫运转在CPU上。
将来的优化操纵

有一个优化办法我但愿能够在我分开Android团队之前完成。笔墨预缓存、异步和部分纹理更新都是一些主要的优化操纵。可是栅格化笔墨标记一向都是一个很泯灭资本的操纵,在systrace能够很简单看到(启用gfs标识然后看precacheText事务)。
对预缓存的一个复杂的优化体例就是,把这个操纵放到另外一个事情线程往实行,把栅格化操纵放到背景。这个手艺已被用到一些庞大的路径栅格化操纵中,可是没有增加到OpenGL架构当中。
改善批处置和兼并操纵也是一个大概的优化体例,用于绘制笔墨的色彩通常为被发送到一个fragment暗影一致操纵。如许能够削减发送到GPU的极点数据,但反作用会发生良多不必要的批处置指令:一个批处置操纵只能包括一种笔墨色彩。假如笔墨色彩也存储为极点属性,那末就能够网GPU传送更少的数据。

如果你现在开始学到编出像样的APPiOS5可能已经普及了可以直接用ARC(另之前对ARC的了解很粗浅现在开发程序完全可以直接ARCiOS4不支持的weak是有办法替代的用unsafe_unretained
老尸 该用户已被删除
沙发
发表于 2015-1-21 08:52:04 | 只看该作者
我也从简单的状态栏适配开始,先研究了下关于状态栏的适配,特总结如下,供广大网友一起讨论交流。
海妖 该用户已被删除
板凳
发表于 2015-1-22 22:05:13 | 只看该作者
在百度搜索你想要了解的类名(苹果的cocoa和cocoatouch框架的类名很有特点很容易搜到,前缀都是NS or UI),看别人写的博客详解
灵魂腐蚀 该用户已被删除
地板
 楼主| 发表于 2015-1-24 15:33:21 | 只看该作者
培训时可以选择安卓,iOS,Java,因为实习的时候我选了安卓,当时实习时间只有三周,学的晕头转向,而java我也没学过,iOS的基础是C语言,这个大学里还是学过的,于是选择了iOS。
山那边是海 该用户已被删除
5#
发表于 2015-2-1 20:31:35 | 只看该作者
看《iPhone 4与iPad开发基础教程》,跟着一步步来
小女巫 该用户已被删除
6#
发表于 2015-2-4 15:54:01 | 只看该作者
培训的时候很痛苦,每天要待12个小时,上午讲课,下午和晚自习解决作业,看文档,学习的时候感觉就是资料太少,而且看着资料也不明所以,非常痛苦,
分手快乐 该用户已被删除
7#
发表于 2015-2-7 02:46:20 | 只看该作者
同很多iOS开发者一样,我也是通过培训进入到iOS开发这个行业,开始没有打算培训,只准备自己学习一些计算机编程相关的知识,毕业时找一份编程相关工作(本人是信息与计算科学这个专业,是数学系)。
第二个灵魂 该用户已被删除
8#
发表于 2015-2-19 21:21:27 | 只看该作者
看《iPhone 4与iPad开发基础教程》,跟着一步步来
admin 该用户已被删除
9#
发表于 2015-2-21 21:08:14 | 只看该作者
边吃零食边看Stanford的视频教程
乐观 该用户已被删除
10#
发表于 2015-3-2 10:53:53 | 只看该作者
然而,在vmware软件环境下,安装Mac OS X操作系统也是一件非常复杂的事情,而且还有可能花费了大量时间,最后却跑不起来。笔者也是经过了大量的实践,
因胸联盟 该用户已被删除
11#
发表于 2015-3-7 10:26:24 | 只看该作者
最后在做项目的时候一定要认真对待,毕竟这个直接和你的就业挂钩,这也是锻炼你实际操作的能力。
飘飘悠悠 该用户已被删除
12#
发表于 2015-3-14 21:36:09 | 只看该作者
培训时可以选择安卓,iOS,Java,因为实习的时候我选了安卓,当时实习时间只有三周,学的晕头转向,而java我也没学过,iOS的基础是C语言,这个大学里还是学过的,于是选择了iOS。
兰色精灵 该用户已被删除
13#
发表于 2015-3-21 14:11:11 | 只看该作者
这个办法就是在WindowsXP或Win7的电脑上,使用vmware虚拟机来搭建一个真实的Mac OS X环境。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|仓酷云 鄂ICP备14007578号-2

GMT+8, 2024-12-22 23:22

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表