高性能Android Canvas遊戲開發

Rule #0 爲移動平臺進行優化

爲移動平臺進行優化是十分重要的,由於移動平臺的性能大概只有桌面平臺的1/10左右(*1),它一般意味着:css

  1. 更慢的CPU速度,這意味着不通過優化的JavaScript代碼,性能會十分糟糕;html

  2. 更小的內存,沒有虛擬內存的支持,這意味着加載太多的資源容易致使內存不足,JSVM更容易引起GC,而且GC形成的停頓時間也越長;web

  3. 更慢的GPU速度,沒有獨立的顯存,內存帶寬相比PC要慢的多,這意味着即便使用GPU對Canvas進行加速,您仍然須要當心網頁DOM樹的複雜度,遊戲所使用的分辨率(Canvas的大小),圖片資源的分辨率,遊戲場景的複雜度和儘可能避免Overdraw等等;canvas

註釋:瀏覽器

  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平臺的渲染性能會十分的糟糕。ruby

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上),這意味着:app

  1. 不管是繪製到Canvas自己,仍是Canvas繪製到Canvas都是GPU加速的,普通的位圖要繪製到Canvas上,須要先被加載到一個Texture中;oop

  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. 拼接的圖片應該儘可能避免留下大量空白區域,形成無謂的浪費;

相關文章
相關標籤/搜索