跪拜 Guibai
← All articles
Flutter · Dart · Interview

Flutter's Three-Tree Architecture and the Impeller Rendering Pipeline, Explained

By GitLqr ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Understanding that Widgets are cheap blueprints, not views, explains why Flutter achieves 60fps despite rebuilding the Widget tree every frame. The shift from Skia to Impeller means developers no longer need workarounds for shader-compilation jank on first-run animations.

Summary

Flutter's architecture rests on three layers: a platform-specific Embedder that provides a rendering surface and input events, a C++ Engine hosting the Dart runtime and Impeller renderer, and a pure-Dart Framework where developers compose Widgets. In debug mode, a Dart VM with JIT compilation enables Hot Reload by patching incremental kernel files into the running app without discarding the Element tree or State objects. In release mode, AOT compilation produces native ARM or x64 machine code, stripping out the VM, interpreter, and runtime reflection entirely.

The rendering pipeline turns Widgets—temporary, immutable blueprints—into pixels through three cooperating trees. The Widget tree describes the UI, the persistent Element tree manages State and diffs new Widgets against existing ones, and the expensive Render tree handles layout, painting, and hit-testing. When setState() fires, only Widgets rebuild; RenderObjects are reused whenever runtimeType and key match. Impeller eliminates first-frame animation jank by pre-compiling shaders at engine build time rather than at runtime, handing instructions directly to Metal or Vulkan.

Takeaways
Flutter does not use platform UI components; all Widgets, the framework, and app logic are Dart code compiled to native machine code in release mode.
The Embedder layer provides only a rendering surface, vsync signals, and input events—no native UI widgets.
Hot Reload works because the Dart VM patches incremental kernel files into the running app; the Element tree and State objects persist across reloads.
Release builds use AOT compilation with tree-shaking, producing libapp.so (app + framework) and libflutter.so (engine), with no JIT, interpreter, or dart:mirrors support.
Widgets are immutable configuration objects with no size or drawing responsibility; the Element tree manages State, and the Render tree handles layout, painting, and hit-testing.
setState() rebuilds the Widget tree, but RenderObjects are reused when runtimeType and key match, keeping the expensive rendering layer stable.
The layout protocol enforces that constraints flow downward and sizes flow upward; placing a ListView inside a Column causes an unbounded-height error.
Impeller pre-compiles shaders at engine build time, eliminating the runtime shader-compilation jank that Skia caused on first-frame animations.
Conclusions

The three-tree model is Flutter's central architectural insight: decoupling the cheap, disposable Widget tree from the expensive, persistent Render tree makes frequent rebuilds viable without performance collapse.

Flutter's decision to own every pixel—bypassing platform UI entirely—trades larger package size for pixel-identical rendering across iOS and Android, a trade-off that makes sense for branded, custom-designed apps but adds friction when platform-native look-and-feel is required.

The shift from Skia to Impeller mirrors a broader industry move toward pre-compiled shaders and low-level graphics APIs (Metal, Vulkan), trading runtime flexibility for predictable frame times.

Concepts & terms
Impeller
Flutter's default rendering engine that pre-compiles shaders at engine build time rather than at runtime, handing draw commands directly to Metal (iOS) or Vulkan (Android) to eliminate first-frame animation jank.
AOT (Ahead-of-Time) compilation
A compilation mode where Dart code is translated to native ARM or x64 machine code before the app runs, used in Flutter release builds to maximize performance and enable tree-shaking.
JIT (Just-in-Time) compilation
A compilation mode where the Dart VM compiles code at runtime, used in Flutter debug builds to enable Hot Reload by allowing incremental kernel patches to be applied to a running app.
Kernel
An intermediate representation format for Dart code, stored in .dill files, that the Dart VM loads and JIT-compiles in debug mode; incremental kernel files are the mechanism behind Hot Reload.
Tree-shaking
A dead-code elimination process during AOT compilation that removes unused code from the final native binary, reducing Flutter app size in release builds.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗