著色器編譯問題

如果行動應用程式的動畫看起來很 janky,但僅限於第一次執行,這可能是由於著色器編譯。Flutter 解決著色器編譯 jank 的長期解決方案是Impeller,它已在 iOS 上穩定釋出,並在 Android 上以旗標預覽。

在我們努力讓 Impeller 完全準備好投入生產時,你可以透過將預編譯的著色器與 iOS 應用程式綑綁在一起,來減輕著色器編譯造成的延遲。遺憾的是,由於預編譯的著色器是特定於裝置或 GPU,因此此方法在 Android 上無法順利運作。Android 硬體生態系統龐大,因此與應用程式綑綁在一起的特定於 GPU 的預編譯著色器只會在少數裝置上運作,而且很可能會讓其他裝置的延遲情況惡化,甚至造成渲染錯誤。

此外,請注意我們不打算改善以下所述預編譯著色器開發人員體驗。相反地,我們將精力集中在 Impeller 提供的更健全解決方案上。

什麼是著色器編譯延遲?

著色器是在 GPU(圖形處理單元)上執行的程式碼。當 Flutter 用於渲染的 Skia 圖形後端第一次看到新的繪製指令序列時,它有時會為該指令序列產生並編譯自訂 GPU 著色器。這允許該序列和潛在的類似序列以最快的速度渲染。

遺憾的是,Skia 的著色器產生和編譯會與畫面工作負載順序執行。編譯可能需要花費數百毫秒,而順暢的畫面需要在 16 毫秒內繪製完成,才能在 60 fps(每秒畫面)顯示器上顯示。因此,編譯可能會導致錯過數十個畫面,並將 fps 從 60 降至 6。這就是編譯延遲。編譯完成後,動畫應會順暢。

另一方面,當我們建置 Flutter Engine 時,Impeller 會產生並編譯所有必要的著色器。因此,在 Impeller 上執行的應用程式已具備所需的所有著色器,而且可以在不讓動畫產生延遲的情況下使用這些著色器。

確定是否存在著色器編譯延遲的明確證據,就是在啟用 --trace-skia 的情況下,在追蹤中設定 GrGLProgramBuilder::finalize。下列螢幕截圖顯示一個時間軸追蹤範例。

A tracing screenshot verifying jank

我們所謂的「首次執行」是什麼意思?

在 iOS 上,「首次執行」表示使用者每次從頭開啟應用程式時,動畫第一次執行時,使用者可能會看到 jank。

如何使用 SkSL 預熱

Flutter 提供指令列工具,供應用程式開發人員收集最終使用者可能需要的著色器,格式為 SkSL (Skia 著色器語言)。然後,SkSL 著色器可以封裝到應用程式中,並在最終使用者首次開啟應用程式時預熱 (預先編譯),從而減少後續動畫中的編譯 jank。使用下列說明收集和封裝 SkSL 著色器

  1. 開啟 --cache-sksl 以執行應用程式,以 SkSL 擷取著色器

    flutter run --profile --cache-sksl
    

    如果先前已在沒有 --cache-sksl 的情況下執行相同的應用程式,則可能需要 --purge-persistent-cache 旗標

    flutter run --profile --cache-sksl --purge-persistent-cache
    

    此旗標會移除較舊的非 SkSL 著色器快取,這些快取可能會干擾 SkSL 著色器擷取。它也會清除 SkSL 著色器,因此在第一次 --cache-sksl 執行時使用。

  2. 使用應用程式觸發儘可能多的動畫;特別是有編譯 jank 的動畫。

  3. flutter run 的指令列中按下 M,將擷取的 SkSL 著色器寫入類似 flutter_01.sksl.json 的檔案中。為獲得最佳結果,請在實際 iOS 裝置上擷取 SkSL 著色器。在模擬器上擷取的著色器不太可能在實際硬體上正常運作。

  4. 使用下列適當方式,使用 SkSL 預熱建置應用程式

    flutter build ios --bundle-sksl-path flutter_01.sksl.json
    

    如果它是針對驅動程式測試建置的,例如 test_driver/app.dart,請務必也指定 --target=test_driver/app.dart (例如,flutter build ios --bundle-sksl-path flutter_01.sksl.json --target=test_driver/app.dart)。

  5. 測試新建立的應用程式。

或者,您可以撰寫一些整合測試,使用單一命令自動執行前三個步驟。例如

flutter drive --profile --cache-sksl --write-sksl-on-exit flutter_01.sksl.json -t test_driver/app.dart

使用此類整合測試,當應用程式程式碼變更或 Flutter 升級時,您可以輕鬆且可靠地取得新的 SkSL。此類測試也可以用於驗證 SkSL 預熱前後的效能變更。更棒的是,您可以將這些測試放入 CI(持續整合)系統,如此一來,SkSL 會在應用程式生命週期中自動產生並測試。

Flutter Gallery的原始版本為例。CI 系統設定為針對每個 Flutter 提交產生 SkSL,並在transitions_perf_test.dart測試中驗證效能。有關更多詳細資訊,請查看flutter_gallery_sksl_warmup__transition_perfflutter_gallery_sksl_warmup__transition_perf_e2e_ios32任務。

最差的畫面光柵化時間是此類整合測試中一個有用的指標,用於指出著色器編譯延遲的嚴重性。例如,以上步驟減少了 Flutter Gallery 的著色器編譯延遲,並將 Moto G4 上最差的畫面光柵化時間從 ~90 毫秒加速到 ~40 毫秒。在 iPhone 4s 上,則從 ~300 毫秒減少到 ~80 毫秒。這會導致視覺差異,如本文開頭所說明。