文章目录
- What is shader compilation jank? -什么是shaders编译卡顿?
- What do we mean by “first run”? - 我们所说的“第一次运行”是什么意思?
- How to use SkSL warmup - 如何使用 SkSL 热身
- Frequently asked questions- 常问的问题
- Why not just compile or warm up all possible shaders? - 为什么不直接编译或预热所有可能的shaders?
- Can SkSLs captured from one device help shader compilation jank on another device? -从一台设备捕获的 SkSL 能否帮助shaders编译在另一台设备上卡顿?
- Why can’t you create a single “ubershader” and just compile that once? -为什么你不能创建一个单一的“ubershader”并且只编译一次呢?
- This process would be easier if the flutter tool could do X! - 这个过程会更轻松,如果flutter工具可以做 X!
- Future work - 未来的工作
If the animations on your mobile app appear to be janky, but only on the first run, you can warm up the shader captured in the Skia Shader Language (SkSL) for a significant improvement.
如果你的手机 App上的动画看起来很卡顿,但只是在第一次运行时,你可以预热在 Skia shaders语言 (SkSL) 中捕获的shaders,以此获得显着的改进。

Side-by-side screenshots of janky mobile app next to non-janky app
卡顿App与非卡顿App并排的屏幕截图
What is shader compilation jank? -什么是shaders编译卡顿?
If an app has janky animations during the first run, and later becomes smooth for the same animation, then it’s very likely due to shader compilation jank.
如果一个App在第一次运行时出现了animation卡顿,后来相同的animation却变得流畅,那么很可能是shaders编译 导致的卡顿。
More technically, a shader is a piece of code that runs on a GPU (graphics processing unit). When a shader is first used, it needs to be compiled on the device. The compilation could cost up to a few hundred milliseconds whereas a smooth frame needs to be drawn within 16 milliseconds for a 60 fps (frame-per-second) display.
从技术上讲,shaders是在 GPU(图形处理单元)上运行的一段代码。 首次使用shaders时,需要在设备上对其进行编译。 编译可能会花费数百毫秒,而流畅的显示帧需要在 16 毫秒内绘制,才能实现 60 fps(每秒帧数)的显示。
Therefore, a compilation could cause tens of frames to be missed, and drop the fps from 60 to 6. This is compilation jank. After the compilation is complete, the animation should be smooth.
因此,一次编译可能会导致数十帧丢失,并将 fps 从 60 降至 6。这就是编译卡顿。 在编译完成后,animation应该是流畅运行的。
Definitive evidence for the presence of shader compilation jank is to see GrGLProgramBuilder::finalize in the tracing with --trace-skia enabled. See the following screenshot for an example timeline tracing.
存在shaders编译卡顿的明确现象是在启用了 --trace-skia 的跟踪中看到 GrGLProgramBuilder::finalize。 有关时间线跟踪的示例,请参见下面的屏幕截图。
A tracing screenshot verifying jank
排查卡顿的跟踪屏幕截图
What do we mean by “first run”? - 我们所说的“第一次运行”是什么意思?
On Android, “first run” means that the user might see jank the first time opening the app after a fresh installation. Subsequent runs should be fine.
在 Android 上,“首次运行”意味着用户在全新安装后第一次打开App时可能会看到卡顿。 后续运行就没问题。
On iOS, “first run” means that the user might see jank when an animation first occurs every time the user opens the app from scratch.
在 iOS 上,“首次运行”意味着用户可能会在每次用户从头开始打开App时第一次出现动画时看到卡顿。
How to use SkSL warmup - 如何使用 SkSL 热身
As of release 1.20, Flutter provides command line tools for app developers to collect shaders that might be needed for end-users in the SkSL (Skia Shader Language) format. The SkSL shaders can then be packaged into the app, and get warmed up (pre-compiled) when an end-user first opens the app, thereby reducing the compilation jank in later animations. Use the following instructions to collect and package the SkSL shaders:
从 1.20 版开始,Flutter 为App开发人员提供命令行工具,以收集最终用户可能需要的 SkSL(Skia shaders语言)格式的shaders。SkSL shaders可以打包到App中,并在最终用户首次打开App时进行预热(预编译),从而减少后续animation的编译卡顿。 使用以下指令收集和打包 SkSL shaders:
1.Run the app with --cache-sksl turned on to capture shaders in SkSL:
1.运行App带上 --cache-sksl 参数,以便在 SkSL 中捕获shaders:
flutter run --profile --cache-sksl
If the same app has been previously run without --cache-sksl, then the --purge-persistent-cache flag may be needed:
如果之前在没有 --cache-sksl 的情况下运行相同的App,则可能需要 --purge-persistent-cache 标志:
flutter run --profile --cache-sksl --purge-persistent-cache
This flag removes older non-SkSL shader caches that could interfere with SkSL shader capturing. It also purges the SkSL shaders so use it only on the first --cache-sksl run.
此标志会消除 ”较旧的非 SkSL shaders缓存“ 对 ”SkSL shaders捕获“的干扰。 它还会清除 SkSL shaders,所以只在第一次运行 --cache-sksl 时使用它。
2.Play with the app to trigger as many animations as needed; particularly those with compilation jank.
2.运行App以根据需要触发尽可能多的animation; 特别是那些有编译卡顿的。
3.Press M at the command line of flutter run to write the captured SkSL shaders into a file named something like flutter_01.sksl.json. For best results, capture SkSL shaders on actual Android and iOS devices separately.
3.在 flutter run 的命令行按 M 将捕获的 SkSL shaders写入名为 flutter_01.sksl.json 的文件中。 为了获取到最佳效果,请分别在真实的 Android 和 iOS 手机上捕获 SkSL shaders。
Build the app with SkSL warm-up using the following, as appropriate:
适当的通过以下方法 让 编译的 App带上 ”SkSL 预热“:
Android:
flutter build apk --bundle-sksl-path flutter_01.sksl.json
or 或者
flutter build appbundle --bundle-sksl-path flutter_01.sksl.json
iOS:
flutter build ios --bundle-sksl-path flutter_01.sksl.json
If it’s built for a driver test like test_driver/app.dart, make sure to also specify --target=test_driver/app.dart (e.g., flutter build ios --bundle-sksl-path flutter_01.sksl.json --target=test_driver/app.dart).
如果它是为像 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.Test the newly built app.
5.测试新建的App。
Alternatively, you can write some integration tests to automate the first three steps using a single command. For example:
或者,你可以编写一些集成测试以使用单个命令自动执行前三个步骤。 例如:
flutter drive --profile --cache-sksl --write-sksl-on-exit flutter_01.sksl.json -t test_driver/app.dart
With such integration tests, you can easily and reliably get the new SkSLs when the app code changes, or when Flutter upgrades. Such tests can also be used to verify the performance change before and after the SkSL warm-up. Even better, you can put those tests into a CI (continuous integration) system so the SkSLs are generated and tested automatically over the lifetime of an app.
通过此类集成测试,你可以在app代码更改或 Flutter 升级时轻松可靠地获取新的 SkSL。 此类测试还可用于验证 SkSL 预热前后的性能变化。 更好的是,你可以将这些测试放入 CI(持续集成)系统中,以便在App的整个生命周期内自动生成和测试 SkSL。
Take the original version of Flutter Gallery as an example. The CI system is set up to generate SkSLs for every Flutter commit, and verifies the performance, in the transitions_perf_test.dart test. For more details, see the flutter_gallery_sksl_warmup__transition_perf and flutter_gallery_sksl_warmup__transition_perf_e2e_ios32 tasks.
以原版 Flutter Gallery 为例。 CI 系统设置为为每次 Flutter 提交生成 SkSL,并在 transitions_perf_test.dart 测试中验证性能。 有关更多详细信息,请参阅 flutter_gallery_sksl_warmup__transition_perf 和flutter_gallery_sksl_warmup__transition_perf_e2e_ios32 任务。
The worst frame rasterization time is a nice metric from such integration tests to indicate the severity of shader compilation jank. For instance, the steps above reduce Flutter gallery’s shader compilation jank and speeds up its worst frame rasterization time on a Moto G4 from ~90 ms to ~40 ms. On iPhone 4s, it’s reduced from ~300 ms to ~80 ms. That leads to the visual difference as illustrated in the beginning of this article.
最差帧raster时间是此类集成测试的一个很好的指标,用于指示shaders编译卡顿的严重性。 例如,上述步骤减少了 Flutter 库的shaders编译卡顿,并将其在 Moto G4 上的最差raster时间从 ~90 ms 加速到 ~40 ms。 在 iPhone 4s 上,它从 ~300 ms 减少到 ~80 ms。 这导致了本文开头所示的视觉差异。
Frequently asked questions- 常问的问题
Why not just compile or warm up all possible shaders? - 为什么不直接编译或预热所有可能的shaders?
If there were only a limited number of possible shaders, then Flutter could compile all of them when an application is built. However, for the best overall performance, the Skia GPU backend used by Flutter dynamically generates shaders based on many parameters at runtime (for example draws, device models, and driver versions). Due to all possible combinations of those parameters, the number of possible shaders multiplies quickly. In short, Flutter uses programs (app, Flutter, and Skia code) to generate some other programs (shaders).
如果shaders数量有上限,那么 Flutter 可以在构建App时编译所有shaders。 然而,为了获得最佳的整体性能,Flutter 使用的 Skia GPU 在背后会在运行时根据许多参数(例如绘图、设备模型和驱动程序版本)动态生成shaders。 由于这些参数的所有可能组合,可能的shaders数量迅速增加。 简而言之,Flutter 使用程序(app、Flutter 和 Skia 代码)来生成其他一些程序(shaders)。
The number of possible shader programs that Flutter can generate is too large to precompute and bundle with an application.
Flutter 可以生成的可能shaders程序的数量太大而无法预先计算并与App捆绑在一起。
Can SkSLs captured from one device help shader compilation jank on another device? -从一台设备捕获的 SkSL 能否帮助shaders编译在另一台设备上卡顿?
Theoretically, there’s no guarantee that the SkSLs from one device would help on another device (but they also won’t cause any troubles if SkSLs aren’t compatible across devices). Practically, as shown in the table on this SkSL-based warmup issue, SkSLs work surprisingly well even if 1) SkSLs are captured from iOS and then applied to Android devices, or 2) SkSLs are captured from emulators and then applied to real mobile devices. As the Flutter team has only a limited number of devices in the lab, we currently don’t have enough data to provide a big picture of cross-device effectiveness. We’d love you to provide us more data points to see how it works in the wild—FrameTiming can be used to compute the worst frame rasterization time in release mode; the worst frame rasterization time is a good indicator on how severe the shader compilation jank is.
从理论上讲,不能保证来自一台设备的 SkSL 会在另一台设备上有所帮助(但如果 SkSL 不能跨设备兼容,它们也不会造成任何问题)。 实际上,如这个基于 SkSL 的预热问题的表格所示,即使 1) 从 iOS 捕获 SkSL,然后将其应用于 Android 设备,或者 2) 从模拟器捕获 SkSL,然后将其应用于真实的移动设备,SkSL 的工作效果也出奇地好 . 由于 Flutter 团队在实验室中只有有限数量的设备,我们目前没有足够的数据来提供跨设备有效性的大图。 我们希望你提供更多数据点,看看它在实验室之外是如何工作的——FrameTiming 可用于计算release模式下最差的raster化时间; 最差帧raster时间可以很好地指示shaders编译卡顿的严重程度。
Why can’t you create a single “ubershader” and just compile that once? -为什么你不能创建一个单一的“ubershader”并且只编译一次呢?
One idea that people sometimes suggest is to create a single large shader that implements all of Skia’s features, and use that shader while the more optimized bespoke shaders are being compiled.
人们有时建议的一个想法是创建一个实现 Skia 所有功能的大型shaders,并在编译更优化的定制shaders时使用该shaders。
This is similar to a solution used by the Dolphin Emulator.
这类似于 Dolphin Emulator 使用的解决方案。
In practice we believe implementing this for Flutter (or more specifically for Skia) would be impractical. Such a shader would be fantastically large, essentially reimplementing all of Skia on the GPU. This would itself take a long time to compile, thus introducing more jank; it would not necessarily be fast enough to avoid jank even when compiled; and it would likely introduce fidelity issues (e.g. flickering) since there would likely be differences in precise rendering between the optimized shaders and the “ubershader”.
在实践中,我们认为为 Flutter(或更具体地为 Skia)实现这一点是不切实际的。 这样的shaders会非常大,本质上是在 GPU 上重新实现所有 Skia。 这本身会花费很长时间来编译,从而引入更多的卡顿问题; 即使在编译时,它也不一定足够快以避免卡顿; 并且它可能会引入保真度问题(例如闪烁),因为优化shaders和“ubershader”之间的精确渲染可能存在差异。
That said, Flutter and Skia are open source and we are eager to see proofs-of-concept along these lines if this is something that interests you. To get started, please see our contribution guidelines.
也就是说,Flutter 和 Skia 是开源的,如果你对此感兴趣,我们渴望看到这些方面的概念验证。 要开始使用,请参阅我们的贡献指南。
This process would be easier if the flutter tool could do X! - 这个过程会更轻松,如果flutter工具可以做 X!
There are a number of possible ways that the tooling around shader warm-up could be improved. Some are already listed as ideas under our Early-onset jank project on GitHub. Please let us know what is important to you by giving a thumbs-up to your feature request, or by filing a new one if one doesn’t already exist.
有许多可能的方法可以改进围绕shaders预热的工具。 有些已经被列为我们在 GitHub 上的 Early-onset jank 项目下的想法。 对你关注的feature request给予一个thumbs-up,或者创建新的feature request(如果该feature还不存在),以此来让我们知道什么对你很重要。
Future work - 未来的工作
On both Android and iOS, shader warm-up has a few drawbacks:
在 Android 和 iOS 上,shaders预热都有一些缺点:
- The size of the deployed app is larger because it contains the bundled shaders.
发布的App的包体积会更大,因为它包含捆绑的shaders。 - App startup latency is longer because the bundled shaders need to be precompiled.
App启动时间会较长,因为捆绑的shaders需要预编译。 - Most importantly, we are not happy with the developer experience of shader warm-up. In particular we view the process of performing training runs, and reasoning about the trade-offs imposed by (1) and (2) to be too onerous.
最重要的是,我们对shaders预热的开发者体验并不满意。 特别是,我们认为执行训练运行的过程以及对 (1) 和 (2) 强加的权衡进行推理过于繁琐。
Therefore, we are continuing to investigate approaches to shader compilation jank, and jank more generally, that do not rely on shader warm-up. In particular, we are both working with Skia to reduce the number of shaders it generates in response to Flutter’s requests, as well as investigating how much of Flutter could be implemented with a small set of statically defined shaders that we could bundle with the Flutter Engine. Stay tuned for more progress!
因此,我们将继续研究不依赖shaders预热的shaders编译卡顿和更普遍的卡顿的解决方法。 特别是,我们都在与 Skia 合作,以减少它为响应 Flutter 的请求而生成的shaders数量,并研究可以使用一组静态定义的shaders来实现多少 Flutter功能,我们可以将这些shaders与 Flutter 引擎捆绑在一起 . 请继续关注以获得更多进展!
If you have questions on SkSL shader warm-up, please comment on Issue 60313 and Issue 53607. If you have general shader warm-up questions, please refer to Issue 32170.
如果你对 SkSL shaders预热有任何疑问,请在Issue 60313 和Issue 53607 中发表评论。如果你有一般的shaders预热问题,请参阅Issue 32170。