Graphics & Rendering Engines
Flutterβs UI framework draws every pixel on screen using a low-level graphics rendering engine. Historically, Flutter used Googleβs Skia engine. In Flutter 3+, Flutter introduced Impeller, a custom-designed rendering engine built specifically for Flutter to eliminate runtime Shader Compilation Jank and leverage modern GPU APIs (Metal & Vulkan).
1. Skia vs. Impeller Architectural Matrix
| Feature | Legacy Skia Engine | Modern Impeller Engine |
|---|---|---|
| Shader Compilation | Just-In-Time (JIT) at runtime | Ahead-Of-Time (AOT) at build time (impellerc) |
| Primary Graphics API | OpenGL ES / Legacy Vulkan | Metal (iOS/macOS) & Vulkan (Android) |
| Shader Jank | β οΈ Frequent frame drops on first animation render | β Zero Shader Compilation Jank |
| Command Buffer Model | Single-threaded stateful model | Multi-threaded concurrent command encoding |
| Memory Allocation | Dynamic runtime GPU memory allocations | Pre-allocated transient memory pools |
2. Under The Hood: Shader Compilation Jank in Skia
What is a Shader?
A Shader is a small program written in GLSL (OpenGL Shading Language) or MSL that runs directly on the GPU to calculate pixel colors, gradients, blurs, clipping masks, and shadows.
Why Skia Suffered from Shader Jank
In Skia, shaders were compiled Just-In-Time (JIT) on the GPU thread the first time a specific visual effect appeared on screen:
[ Flutter Widget Render ] βββΊ First Time Blur/Shadow Appears βββΊ GPU Compiles GLSL Shader (JIT)
β
Takes 30ms - 100ms!
β
βΌ
Exceeds 16.6ms Budget βββΊ DROPPED FRAME! (Jank)- When a user triggered a new screen animation (e.g. page transition with a blurred backdrop), Skia halted frame rendering to compile GLSL shaders.
- Shader compilation took 30ms to 100ms+, blowing past the 16.6ms (60 FPS) frame budget and causing noticeable UI stuttering.
- Subsequent renders reused the compiled shader, meaning the jank occurred only on the first invocation.
3. How Impeller Eliminates Shader Jank
Impeller solves shader jank by shifting shader compilation from runtime to build time (Ahead-Of-Time).
[ Build Time (impellerc) ] [ Runtime Execution ]
ββββββββββββββββββββββββββββ ββββββββββββββββββββββββββββ
β Shaders (.hlsl / .spirv) β β Pre-compiled MSL / SPIR-V β
β β β β Shaders loaded instantly β
β (impellerc AOT) β βββββββββββββββββββββββΊ β β
β βΌ β β ZERO GPU compilation! β
β Pre-compiled MSL / SPIRV β β Deterministic 120 FPS β
ββββββββββββββββββββββββββββ ββββββββββββββββββββββββββββ1. AOT Shader Compilation (impellerc)
During flutter build, the Impeller offline compiler (impellerc) compiles all HLSL/SPIR-V shader source code into target platform binaries (Metal Shading Language on iOS; SPIR-V on Android). Shaders are bundled directly into the application executable.
2. Explicit State Management & Multi-threading
Impeller creates explicit, immutable GPU pipeline state objects (PSOs). Rendering command buffers are encoded concurrently across multiple threads before submission to Metal or Vulkan drivers.
3. Predictable Frame Cadence
Because zero shader compilation occurs at runtime, frame rendering time becomes completely deterministic, maintaining a stable 60 FPS or 120 FPS display rate.
4. Trade-offs & Production Considerations
- Engine Availability: Impeller is enabled by default on iOS (Flutter 3.7+) and Android Vulkan (Flutter 3.16+). On older Android devices running legacy OpenGL ES drivers, Flutter falls back to Skia or Impellerβs OpenGL ES backend.
- Binary Size vs Runtime Speed: Bundling pre-compiled AOT shaders via
impellercadds a minor overhead (~1-2MB) to the release binary size, trading negligible disk space for flawless 120 FPS UI performance.