Rule #0为移动平台进行优化


为移动平台进行优化是十分重要的,因为移动平台的性能大概只有桌面平台的1/10左右(*1),它通常意味着:
  1. 更慢的CPU速度,这意味着不经过优化的JavaScript代码,性能会十分糟糕;
  2. 更小的内存,没有虚拟内存的支持,这意味着加载太多的资源容易导致内存不足,JSVM更容易引发GC,并且GC造成的停顿时间也越长;
  3. 更慢的GPU速度,没有独立的显存,内存带宽相比PC要慢的多,这意味着即使使用GPU对Canvas进行加速,您仍然需要小心网页DOM树的复杂度,游戏所使用的分辨率(Canvas的大小),图片资源的分辨率,游戏场景的复杂度和尽量避免Overdraw等等;

注释:
  1. 如果您需要对移动平台浏览器的性能有一个全面的了解,建议阅读文章“Why mobile web apps are slow”(原文译文)和”5 Myths About Mobile Web Performance“(原文,译文)。

Rule #1 为Android而不是iOS而优化


牢记这一点非常重要,Mobile Safari的Canvas渲染机制跟Android平台有很大的不同,特别地针对Mobile Safari进行优化的Canvas游戏在Android平台的渲染性能会十分的糟糕。
Mobile Safari使用了iOS/MacOS平台特有的IOSurface作为Canvas的Buffer,当通过Canvas API往IOSurface绘制内容的时候是没有GPU加速的,iOS仍然使用CPU进行绘制,但是将一个IOSurface绘制到另外一个IOSurface上的时候,iOS会使用GPU的2D位拷贝加速单元进行加速(*1)。这种机制其实也是iOS UI界面Layer Rendering渲染架构的基础。所以为iOS优化的Canvas游戏会倾向于使用大量的Off-Screen Canvas(*2),不管是静态的图片也好,还是需要动态产生的内容也好,统统都缓存到一个Off-Screen Canvas上,最终游戏场景的绘制就是一个把一堆Off-Screen Canvas绘制到一个On-Screen Canvas的过程,这样就可以充分利用iOS绘制IOSurface到IOSurface使用了GPU加速的特性来提升渲染性能。
但是这种大量使用Off-Screen Canvas的做法在Android平台的浏览器上会非常糟糕。Android平台并没有IOSurface的同等物(一块同时支持CPU读写和GPU读写的缓冲区),所以它只能使用GL API对Canvas进行加速,一个加速的Canvas,它的Buffer是一个GL Texture(被attach到一个FBO上),这意味着:
  1. 无论是绘制到Canvas本身,还是Canvas绘制到Canvas都是GPU加速的,普通的位图要绘制到Canvas上,需要先被加载到一个Texture中;
  2. Texture Buffer只能通过GPU进行读写,如果要使用CPU访问,必须先通过glReadPixels把内容从显存拷贝到一块普通内存,而这个操作会非常慢并且会造成GL渲染流水线的阻塞;
  3. 如果游戏频繁创建和销毁一些比较小的Canvas,会很容易造成显存的碎片化,让显存的耗尽速度加快,并且创建太多的Canvas也容易把GPU资源都消耗光,导致后续的分配失败和渲染错误;
  4. 当每个Game Loop都对多个Canvas进行同时更新时,会导致GL Context不断地切换不同的Render Target(FBO),而这对GL的渲染性能有很大的影响;

后续的内容会进一步说明如何针对使用GL加速的Canvas渲染架构进行优化。
注释:
  1. 一般GPU都会带有多个独立的加速单元,包括3D加速单元,支持GL和D3D这样的3D API;2D位拷贝加速单元,对将一块缓冲区绘制到另外一块缓冲区进行加速;2D矢量绘制加速单元,支持像OpenVG这样的2D API,但是Android平台只支持通过GL API使用GPU加速,并没有公开的2D位拷贝加速API,虽然2.x的时候厂商可以提供一个copybit模块对位拷贝进行加速,供SurfaceFlinger使用,但这个模块不是通用的,并且不对外公开,另外在4.x的时候也已经移除了。
  2. Off-Screen Canvas在文中是指display:none,没有attach到DOM树上的Canvas,相对于On-Screen Canvas而言。

Rule #2 优化网页的DOM树结构

Canvas只是网页的一部分,它最终要显示出来还需要浏览器对网页本身的绘制,如果网页的DOM树结构越复杂,浏览器花在网页绘制上的时间也就越长,网页绘制占用的CPU/GPU资源也就越多,而留给Canvas绘制的CPU/GPU资源也就越少,这意味着Canvas绘制本身需要的时间也越长。并且网页绘制的耗时越长,Canvas最终更新到屏幕上的延迟也就越长,总之,这是一个此消彼涨的过程。所以,优化网页的DOM树结构,让其尽可能简单,浏览器就可以把更多的系统资源花费在Canvas的绘制上,从而提升Canvas的渲染性能。

最理想的DOM结构就是只包含一个<body>,加上一个<div>作为容器和加上一个<canvas>本身。如果Canvas上面需要显示其它的网页内容,最好只是用于一些临时使用的对话框之类的东西,而不是一直固定显示。

Rule #3 优化网页元素的css背景设置

跟#2的道理一样,背景设置越简单或者根本不设置背景,浏览器花费在网页本身绘制的开销也就越小,一般来说<canvas>元素本身都不应该设置css背景,它的背景应该通过Canvas API来绘制,避免浏览器在绘制<canvas>元素时还要先绘制背景,然后再绘制Canvas的内容。另外<body>和其它元素都应该首先考虑使用background-color而不是background-image,因为background-image的绘制耗时比一个纯色填充要大的多,而且背景图片本身还需要消耗额外的显存资源(生成Texture)。

Rule #4 使用合适大小的Canvas

考虑移动设备的性能限制,Canvas不适宜太大,否则需要消耗更多的GPU资源和内存带宽,480p或者600p是一个比较合适的选择(横屏游戏可以选择800p或者960p),一般不应该超过720p。并且游戏图片资源的分辨率应该跟Canvas的分辨率保持一致,也就是正常情况下图片绘制到Canvas上应该不需要缩放。

我们需要避免创建了一个较大的Canvas,但是仍然使用较低分辨率的图片资源,图片绘制到Canvas上还需要经过缩放的情况。这样做毫无意义,因为游戏本身的分辨率是由图片资源的分辨率来决定的,上述的情形既不能提升游戏的精美程度,也白白浪费了系统资源。

如果自己预先指定了Canvas的大小,又希望Canvas在网页中全屏显示,可以通过<meta viewport>标签设置viewport的大小(*1,*2),直接告诉浏览器网页虚拟viewport的宽度应该是多少,并且让viewport的宽度等于Canvas的宽度,而浏览器会自动按照viewport宽度和屏幕宽屏的比值对网页进行整体放大。

注释:
  1. <meta viewport>的设置可以参考这个例子:http://www.craftymind.com/factory/guimark3/bitmap/GM3_JS_Bitmap.html
  2. Android系统浏览器在网页不指定viewport宽度时,它会认为这是一个WWW页面,并且使用980的默认viewport宽度,UC浏览器也遵循了同样的做法。这意味着您不设置viewport宽度,并且直接使用window.clientWidth作为Canvas的宽度时,就会创建出一个980p的Canvas,通常这是毫无意义的;

Rule #5 避免使用多个On-Screen Canvas


如#1所述,多个Canvas同时更新会降低GL渲染的效率,并且如#2所述,多个On-Screen Canvas会导致网页本身的绘制时间增加,所以应该避免使用。

Rule #6 合理地使用Off-Screen Canvas


在GL加速的Canvas渲染架构下,合理地使用Off-Screen Canvas可以在某些特定的场景提升渲染性能,但是不合理的使用反而会导致性能下降。
  1. 将图片绘制到一个Off-Screen Canvas上,然后把这个Canvas当作原图片使用,这种用法,如#1所述,在iOS上是有用的,但是在Android上不必要的,甚至会导致额外的资源浪费(虽然渲染性能还是一样)。浏览器会自动将需要绘制到Canvas的图片加载成Texture并缓存起来,避免反复加载,只要这个缓存池大小没有超过限制,图片的绘制就只需要付出一次贴图的开销,这对GPU来说是很小的;
  2. 避免使用过大或者过小的Off-Screen Canvas —— 首先过大的Canvas会超过系统的Max Texture Size而无法进行加速(*1,*2),而太小的Canvas(*3,*4),因为对它加速不但不会加快渲染速度,反而会导致如#1所述的一些问题 —— 加快GPU资源的耗尽,频繁切换Render Target的额外开销等,所以也是不加速的;
  3. 避免频繁动态创建和销毁Canvas对象,这样很容易引发GC,而且浏览器为了避免大量的Canvas Buffer把GPU资源耗尽,还会在接近临界值时进行强制GC(*5),而强制GC造成的停顿比一般GC还要长,通常会达到500ms~1000ms。所以一般来说应该事先生成所有需要的Canvas然后一直使用,或者建立一个缓存池来回收和重用;
  4. Canvas初始大小设置后就不应该再改变,否则浏览器需要为它分配新的Buffer;
  5. 需要动态生成的内容,可以在一个Off-Screen Canvas上预先生成,然后直接将这个Canvas绘制到On-Screen Canvas上,但是这个生成应该是一次性的(或者偶尔),而不是每个Game Loop都需要更新,否则就会造成#1所述的问题 ——频繁切换Render Target的额外开销
  6. 如果场景中的部分内容很少发生变化,但是位置,缩放比例,旋转角度,透明度等属性需要频繁变化,可以把一个Off-Screen Canvas当作Layer使用,缓存这部分内容,然后在绘制这部分内容时就直接绘制这个Off-Screen Canvas

总结一下,Off-Screen Canvas的使用应该尽量遵循以下原则:
  1. 数量适中(越少越好);
  2. 大小适中(面积128x128以上,长宽2048以内,并且为2的幂次方对GPU来说是最友好的);
  3. 一次创建,大小固定,持续使用;
  4. 读多写少(可以在每个Game Loop都绘制到On-Screen Canvas上,但是自身更新/变化的次数应该很少,避免每个Game Loop都更新);

注释:
  1. 一般手机的Max Texture Size是2048,高端的机器可能会到4096或者8192,Canvas长宽任意一边超过这个大小都无法使用Texture做为自己的Buffer;
  2. 非加速的Canvas仍然使用普通的Bitmap作为自己的Buffer,这意味着它的绘制仍然使用CPU,并且它绘制到另外一个Canvas还需要先加载成Texture,而加速的Canvas本身就是一个Texture,所以它绘制到另外一个Canvas上只需要一次贴图的开销;
  3. WebKit默认的设置是128x128大小以内的Canvas不加速,UC和Chrome都使用了默认的设置;
  4. 把一个比较大,加速的Canvas绘制到一个比较小,不加速的Canvas会非常非常慢,这是因为浏览器需要从显存拷贝内容到普通内存,拷贝的速度很慢并且会造成GL渲染流水线的阻塞
  5. 一个小技巧是,如果一个Canvas不再使用,可以将它的长宽设置为0,这样在JSVM的垃圾收集器还没有回收该Canvas对象时,浏览器就可以先释放它的Buffer,这样可以避免浏览器因为Buffer占用太多而不得不强制GC,不过总的来说最好还是自己建立缓存池;

Rule #7 避免频繁调用getImageData,putImageData和toDataURL


因为它们都会需要从显存拷贝内容到普通内存,或者相反,拷贝的速度很慢并且会造成GL渲染流水线的阻塞。所以不要在每个Game Loop都调用这几个API。

Rule #8 如果需要最大帧率,优先使用requestAnimationFrame而不是Timer


如果您的游戏只需要20或者30帧,那么就只能使用Timer。但是如果希望达到设备本身的最大帧率,则应该使用rAF,因为rAF可以让浏览器把网页绘制,Canvas绘制跟屏幕刷新保持同步,减少Canvas更新的延迟,并且在网页不可见的时候还可以自动停止rAF回调,避免无谓的浪费电池。

Rule #9 图片资源大小应该对GPU友好


  1. 避免使用太多小图片,而是应该把它们拼接成一张大图;
  2. 拼接的图片长宽应该是2的幂次方,并且小于或者等于2048,512x512,1024x1024都是不错的选择;
  3. 拼接的图片应该尽量避免留下大量空白区域,造成无谓的浪费;

更多相关文章

  1. Android(安卓)记一次解决问题的过程:从源码中分析永远是解决问题
  2. Androidの重力感应
  3. Android添加图片水印
  4. [Android(安卓)Pro] 控制硬加速 hardwareAccelerated 在3.0才有
  5. Android(安卓)快速实现 ViewPager 滑动页卡切换(可用作整个 app上
  6. 通过创建一个位图的XY Chart来学习Android绘图类Rect,Paint,Bitm
  7. (4.1.36.3)android Graphics(一):概述及基本几何图形绘制
  8. Android中UI视图之试图绘制机制
  9. Android(安卓)UI编程进阶——使用SurfaceViewt和Canvas实现动态

随机推荐

  1. Android(安卓)Styles and Themes
  2. 《Pro Android(安卓)Graphics》读书笔记
  3. Android中GridView来显示图片
  4. Get list of photo galleries on Android
  5. android里的ViewGroup
  6. Android(安卓)fragment的数据绑定databin
  7. Android(安卓)程序实时监听网络变化状态
  8. Android(安卓)实现数据的列表显示
  9. Android上实现TCP&UDP的客户端和服务端
  10. android ActionBar的使用