1. 程式人生 > >小遊戲 vs H5 遊戲效能對比和分析

小遊戲 vs H5 遊戲效能對比和分析

這是個人關於微信小遊戲系列文章的第三篇,在這系列文章裡會描述 ——

  • 如何把一些 Canvas/WebGL Demo 移植到小遊戲環境並支援雙端執行;
  • 對小遊戲在 Android 平臺的執行時架構進行分析;
  • 通過對移植的 Canvas/WebGL Demo 在小遊戲和 Chrome for Android 瀏覽器上做 Benchmarking,對 H5 遊戲 vs 小遊戲的渲染效能進行對比和分析;

測試說明

測試軟體

  1. 微信 6.6.3
  2. Chrome for Android 64.0.3282.137

測試使用的 Demo 可以通過 GitHub 下載

測試裝置

小米 Note3,使用驍龍 660,算是這兩年比較主流的中端晶片,雖然效能在高負荷場景下跟旗艦晶片如驍龍 835 還有一定距離,不過大部分場景下也已經夠用。

測試環境

裝置電量在 60% 以上,處於充足狀態,並使用風扇進行輔助散熱,避免因為電量不足或者過熱導致裝置鎖核降頻,從而引起測試資料的波動,無法獲得可以對比的測試結果。

頁面在 Chrome 上執行在全屏的 WebApp 模式下,如果沒有額外說明,Canvas 的渲染解析度使用裝置的螢幕解析度 1080p。

幀率獲取

adb shell "dumpsys SurfaceFlinger --latency 'TARGET_WINDOW_NAME'"

使用上述命令可以獲取目標視窗最近 127 幀的更新時間戳,然後用指令碼計算出這 127 幀渲染過程中的平均幀率。

另外程式碼中也通過計算 JavaScript 中每幀呼叫的次數和時間間隔來計算幀率,然後通過控制檯列印輸出類似下面的資訊,實際驗證兩者的結果基本一致。

WebGL Aqua framerate:1.55fps, program:44, draw call:118

本文的測試資料使用第一種方法

測試資料

WebGL Compute

模擬鳥群的運動,包含大量的物理運動計算,實際上是測試 JavaScript 的計算效能。

可以對 numBoids (guimark3.js)引數進行修改,改變鳥群的數量,鳥群數量越大,JavaScript 計算的耗時就越大。

執行環境 250 鳥群 500 鳥群
小遊戲 58 ~ 59 21 ~ 22
Chrome 58 ~ 60 20 ~ 21

WebGL Aqua

繪製的場景有一定的複雜度,包含了約 30 個模型,使用了 44 個 Program,當引數設定為 500 條魚時,需要呼叫 600 多個 Draw Call(不使用 Instance Rendering 的情況下)。

可以配置的引數如下:

  1. fishSetting 可以修改魚的數量(aquarium-common.js);
  2. antialias 可以選擇開啟或者關閉抗鋸齒(webgl.js);
  3. TRY_USE_WEBGL2 可以選擇是否使用 Instance Rendering(wxhelper.js);
  4. 使用 GetCanvasSizeUseWindowRatio(720/600) 或者 GetWindowSizeInPx 選擇不同的渲染解析度(aquarium.js - setCanvasSize);
執行環境 100 條魚 200 條魚 500 條魚 1000 條魚 2000 條魚
小遊戲 52 ~ 53 52 ~ 53 58 ~ 59 58 ~ 59 37 ~ 38
Chrome (關閉抗鋸齒) 60 60 60 58 ~ 60 39 ~ 40
Chrome (關閉抗鋸齒, 720p) - - - - 60
Chrome (關閉抗鋸齒, Instance) - - - - 52 ~ 54
Chrome (開啟抗鋸齒) 60 60 42 ~ 44 28 ~ 30 18 ~ 19
Chrome (開啟抗鋸齒, 720p) - - 60 51 ~ 53 31 ~ 32
Chrome (開啟抗鋸齒, 600p) - - - 60 42 ~ 43
Chrome (開啟抗鋸齒, Instance) - - 56 ~ 60 47 ~ 51 35 ~ 36
Chrome (開啟抗鋸齒, Instance, 720p) - - - - 58 ~ 60
  1. 小遊戲不支援抗鋸齒,無論設定 antialias 引數為 true 或者 false,結果都是 false,Chrome 預設是開啟抗鋸齒,需要顯式關閉;
  2. 小遊戲目前並不支援 WebGL2,WebGL2 新增的 API 並沒有實現;
  3. 小遊戲只能使用螢幕畫素大小的渲染解析度;
  4. 小遊戲在 100 ~ 200 條魚時幀率反而更低,反覆驗證過幾次均是如此,懷疑踩到了什麼坑,應該不是效能瓶頸;
  5. 小遊戲最高幀率不會達到 60,最多就是 58 ~ 59,或許是 requestAnimationFrame 的實現有些問題;

Canvas Bitmap

類似雷電的小遊戲,多個小點陣圖的重複繪製,主要測試 Canvas.drawImage 的效能,跟微信開發工具自帶的樣例遊戲類似。

可以對 enemiesCount(guimark3.js),改變敵機的數量,從而增加或者減少 drawImage 的呼叫次數。

執行環境 ~1000 drawImage ~2000 ~4000
小遊戲 58 ~ 59 58 ~ 59 51 ~ 53
Chrome 60 60 30 ~ 31

效能分析

渲染流水線

在 Chrome 渲染流水線裡面,Canvas 元素的更新跟其他 DOM 元素的內容更新一樣,都需要走非合成器動畫的渲染流水線。而非合成器動畫渲染流水線過於複雜和冗長,比較小遊戲簡單和直接的渲染流水線,會有較多的 Overhead。

不過假設網頁的執行條件跟小遊戲一樣,頁面只有一個 Canvas 元素而不存在其他 DOM 元素,這些 Overhead 其實每個環節的耗時都很小,加上 Chrome 多執行緒高併發的流水線設計,實際上大部分開銷都可以忽略不計,對整體效能的影響微乎其微。

WebGL Compute 的測試結果也驗證了這一點,該 Demo 只有一個 Draw Call,所以我們基本可以忽略繪製部分的影響,JavaScript 計算的開銷也可以認為是基本相同,在小遊戲和 Chrome 的效能測試結果基本一致的情況下,我們可以推斷出渲染流水線本身不會造成明顯的效能差異。

關於 Chrome 非合成器動畫的渲染流水線可以參考我的文章 - 瀏覽器渲染流水線解析與網頁動畫效能優化

JavaScript Computing

我們把 JavaScript Computing 定義為除了 Native API(2D Canvas,WebGL,etc…)外的其它純 JavaScript 程式碼執行的耗時。

小遊戲和 Chrome 都使用 v8 虛擬機器執行 JavaScript 程式碼,理論上 JavaScript 計算的效能不會存在較大差異,些微的差別可能來源自 v8 的版本差異,WebGL Compute 的測試結果也驗證了這一點。

WebGL

Chrome 對比小遊戲 WebGL 繪製的差別在於:

  1. 小遊戲的 WebGL 呼叫會直接呼叫對應的 GL API,Chrome 使用了跨程序/執行緒的 CommandBuffer 機制,WebGL 呼叫會先被 Encode Command 到 Buffer 裡面,然後在另外一個獨立的 GPU 執行緒中 Decode Command,再呼叫相應的 GL API;
  2. 小遊戲的主 Canvas 直接繪製在 GLSurfaceView 上,Chrome 會為每個 Canvas 元素分配額外的 Texture 作為 Render Target,然後再把該 Texture 合成到 Window 上,這意味著 Chrome 需要做一次 Render Target 的切換和多一次快取拷貝,會增加一些 GPU 開銷;

在 WebGL Aqua 這個 Demo 中,計算部分的開銷很小,大部分是繪製的開銷,Chrome 的多執行緒模型並沒有帶來多少併發的優勢,只是抵消了 CommandBuffer 機制帶來的一些 Overhead,不過額外的 Render Target 切換和快取拷貝的影響看起來也很小,Chrome 和小遊戲的效能基本持平,甚至 Chrome 還稍微好一些。如果是計算和繪製開銷比較平均的場景,Chrome 可能會有更大的效能優勢。

2D Canvas

Chrome 對比小遊戲 2D Canvas 繪製的差別在於:

  1. Chrome 每一個 2D Canvas Draw Call,特別是 drawImage 的呼叫,因為瀏覽器執行環境複雜性的原因,有較大的 Overhead,包括 Image 物件的型別檢查和轉型,Dirty Rect 的計算,DOM 物件的互動,Image URL 的 Origin Check 安全檢查,等等,而這些在小遊戲執行環境下都是可以省略或者優化的;
  2. Chrome 使用 Skia 作為 2D 繪圖引擎,Skia 作為一個通用,完備,可外部配置的 2D 繪圖引擎,支援不同的光柵化後端實現(CPU,GL,Vulkan),支援複雜的記憶體管理和資源快取機制,但是也帶來了較高的 per drawImage 的開銷,小遊戲高度定製的 2D 繪圖引擎可以容易地規避掉這些開銷;

因為 Chrome 執行環境中較高的 per drawImage 的開銷,我們可以在 Canvas Bitmap 測試中看到當 drawImage 的呼叫次數非常高時,Chrome 的 Renderer 執行緒會被嚴重阻塞而導致幀率下降幅度大大高於小遊戲的下降幅度。

WebGL 理論上 Chrome 也會多一些 Overhead,只是 WebGL API 相比 2D Canvas API 的實現要簡單很多,所以這些 Oveahead 實際影響非常小,並沒有 2D Canvas 那麼明顯。

總結和優化建議

總結

  1. Chrome 和小遊戲因為渲染流水線不同帶來的影響很小,基本上可以忽略(在沒有其他 DOM 元素的條件下);
  2. Chrome 和小遊戲都使用 v8 作為 JavaScript 虛擬機器,JavaScript 的計算效能基本一致;
  3. Chrome 和小遊戲的 WebGL 渲染效能在都關閉抗鋸齒的條件下基本一致,Chrome 還略微超出,另外 Chrome 可以通過降低渲染解析度和使用 Instance Rendering 等方式進一步提升效能;
  4. Chrome 和小遊戲在 2D Canvas 繪製場景中,如果 drawImage 的每幀呼叫次數在正常範圍內,效能差別不大,如果 drawImage 呼叫次數非常高,Chrome 的效能下降幅度更大;

H5 遊戲的優化建議

  1. 如果渲染場景比較複雜,WebGL 建議關閉抗鋸齒,開啟/關閉抗鋸齒在 5.x 寸屏上對渲染效果的影響微乎其微,並且小遊戲和原生手遊也沒有使用抗鋸齒,關閉抗鋸齒對於複雜的渲染場景可以帶來非常明顯的效能提升;
  2. 如果渲染場景非常複雜,可以考慮使用較低的渲染解析度,降低解析度可以帶來明顯的效能提升,600 ~ 720p 在 5.x 寸屏上比起原生解析度雖然畫面會略微模糊一些,但是整體渲染效果還是可以接受的;
  3. 如果渲染場景存在同一個 Mesh 的大量重複繪製,建議使用 Instance Rendering,Instance Rendering 可以大幅降低 GL API 呼叫次數,減少 GL API 呼叫的 CPU 開銷,帶來較大程度的效能提升;
  4. 如果 2D 遊戲場景中的每幀 Image 繪製次數達到幾千的量級,建議使用 WebGL 替代 2D Canvas(大部分 2D 遊戲應該也就幾百的量級,就不需要考慮這個);

如果覺得本文的內容對你有幫助,請麻煩點下贊