跳至主要内容

着色器编译卡顿

如果您的移动应用上的动画看起来卡顿,但仅在首次运行时出现,这可能是由于着色器编译导致的。Flutter 解决着色器编译卡顿的长期方案是Impeller,它是 iOS 上的默认渲染器。您可以通过将--enable-impeller传递给flutter run来在 Android 上预览 Impeller。

在我们努力使 Impeller 完全投入生产的过程中,您可以通过将预编译的着色器与 iOS 应用捆绑在一起,来缓解着色器编译卡顿。不幸的是,由于预编译的着色器是特定于设备或 GPU 的,因此这种方法在 Android 上效果不佳。Android 硬件生态系统非常庞大,应用捆绑的特定于 GPU 的预编译着色器仅适用于一小部分设备,并且可能会在其他设备上导致卡顿加剧,甚至出现渲染错误。

此外,请注意,我们不打算改进下面描述的创建预编译着色器的开发者体验。相反,我们将精力集中在 Impeller 提供的更强大的解决方案上。

什么是着色器编译卡顿?

#

着色器是在 GPU(图形处理单元)上运行的一段代码。当 Flutter 用于渲染的 Skia 图形后端首次看到一个新的绘制命令序列时,它有时会为该命令序列生成并编译一个自定义的 GPU 着色器。这使得该序列和潜在的类似序列能够尽可能快地渲染。

不幸的是,Skia 的着色器生成和编译与帧工作负载同步进行。编译可能需要花费多达几百毫秒的时间,而为了获得 60 fps(每秒帧数)的显示效果,流畅的帧需要在 16 毫秒内绘制完成。因此,编译可能会导致数十帧丢失,并使 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 毫秒。这导致了本文开头所示的视觉差异。