跳至主要內容

著色器編譯卡頓

如果您的行動應用程式動畫在首次執行時出現卡頓,這很可能是由於著色器編譯所致。Flutter 針對著色器編譯卡頓的長期解決方案是 Impeller,它是 iOS 上的預設渲染器。您可以在 Android 上透過傳遞 --enable-impeller 給 `flutter run` 來預覽 Impeller。

在我們努力使 Impeller 完全適用於生產環境的同時,您可以透過將預編譯的著色器與 iOS 應用程式捆綁來減輕著色器編譯卡頓的問題。不幸的是,這種方法在 Android 上效果不佳,因為預編譯的著色器是特定於裝置或 GPU 的。Android 硬體生態系統非常龐大,應用程式捆綁的 GPU 特定預編譯著色器僅能在少數裝置上運行,並且很可能會使其他裝置上的卡頓情況更糟,甚至產生渲染錯誤。

此外,請注意我們不打算改進以下描述的用於建立預編譯著色器的開發人員體驗。相反,我們正將精力集中在 Impeller 提供的更強大的問題解決方案上。

什麼是著色器編譯卡頓?

#

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

不幸的是,Skia 的著色器生成和編譯與幀工作負載同步進行。編譯可能會耗費數百毫秒,而平滑的幀需要在 16 毫秒內繪製完成才能達到 60 fps(每秒幀數)的顯示效果。因此,編譯可能會導致錯過數十幀,並將 fps 從 60 降至 6。這就是編譯卡頓。編譯完成後,動畫應該會變得流暢。

另一方面,Impeller 會在我們建置 Flutter Engine 時生成並編譯所有必要的著色器。因此,在 Impeller 上運行的應用程式已經擁有他們所需的所有著色器,並且可以使用這些著色器而不會在動畫中引入卡頓。

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

A tracing screenshot verifying jank

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

#

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

如何使用 SkSL 預熱

#

Flutter 提供命令列工具,讓應用程式開發人員以 SkSL(Skia 著色器語言)格式收集最終使用者可能需要的著色器。然後,SkSL 著色器可以打包到應用程式中,並在最終使用者首次開啟應用程式時進行預熱(預編譯),從而減少後續動畫中的編譯卡頓。請使用以下說明來收集和打包 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. 操作應用程式以觸發所需的盡可能多的動畫;特別是那些有編譯卡頓的動畫。

  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 毫秒。這導致了本文開頭所說明的視覺差異。