Skia & Impeller #

These two names represent the graphics engine components holding full control over how Flutter translates your Dart code instructions into millions of physical pixels on the device screen. For years, Skia was the main foundation accompanying Flutter’s growth since its launch. However, as UI performance demands grew and modern graphics card architectures evolved, Google developed Impeller — a next-generation graphics engine designed specifically for Flutter. Understanding the fundamental differences, technical architectures, and workings of these two rendering engines is essential for understanding how Flutter’s visual performance is optimized.

Context: What Is a Rendering Engine? #

Before comparing Skia and Impeller directly, you need to understand the specific role of the rendering engine in the Flutter app lifecycle. As discussed in the rendering pipeline topic, after the Dart UI Thread finishes assembling the app’s visual hierarchy, it sends composited data as a binary Layer Tree object through the dart:ui binding.

This is where the rendering engine’s job begins. Running on the Raster Thread (the async C++ thread), the rendering engine acts as the final translator:

flowchart TD
    LayerTree["Layer Tree (UI Thread Output)"] -->|"1. Send via dart:ui"| RenderEngine["Rendering Engine (Raster Thread)"]
    RenderEngine -->|"2. Compute Rasterization & Shaders"| GPU["GPU Hardware (Metal/Vulkan/GL)"]
    GPU -->|"3. Write Pixel Binary"| Framebuffer["Framebuffer (VRAM)"]
    Framebuffer -->|"4. Swap Buffer on VSync"| Display["Device Screen"]
    
    style RenderEngine stroke:#0288d1,stroke-width:2px
    style GPU stroke:#7b1fa2,stroke-width:2px

The rendering engine is responsible for rasterizing every vector shape, line, text, and bitmap image, as well as executing shader effects (like blur, gradients, and shadows) using binary instructions understood directly by the GPU graphics hardware.


Skia — Flutter’s Original Foundation #

Skia is a legendary open-source 2D graphics library actively developed by Google. Unlike Impeller, Skia wasn’t built exclusively for Flutter. Skia is a universal graphics engine widely used in various industry giant products like the Google Chrome web browser, the Android operating system, Mozilla Firefox, and the YouTube video player.

Skia’s Architecture: Immediate Mode Rendering #

Skia operates using the Immediate Mode Rendering (IMR) paradigm. Under this model, every time a drawing instruction is sent by the Framework (e.g., “draw a blue rectangle at coordinate X”), Skia immediately sends that drawing command to the GPU at that moment.

flowchart TD
    subgraph ImmediateMode["Skia Immediate Mode Rendering"]
        direction TB
        FrameN["Frame N: Re-render All Graphics Elements"] -->|"VSync Trigger"| FrameN1["Frame N+1: Re-render All Elements from Scratch"]
        FrameN1 -->|"VSync Trigger"| FrameN2["Frame N+2: Re-render All Elements from Scratch"]
    end

IMR’s main characteristic is its simplicity: Skia doesn’t deeply track the previous frame’s image position status. When a VSync signal requests a new frame, Skia completely clears the screen canvas and redraws all visual elements from scratch. Although simple, this approach heavily burdens the GPU because it requires significant computing power even when only one small component on screen is moving.

Shader JIT Compilation: The Main Cause of Jank #

A shader is a micro-program executed in parallel inside the GPU to determine the color and lighting of every screen pixel. Skia writes these shaders in GLSL (OpenGL Shading Language).

Skia’s biggest weakness when integrated with Flutter’s dynamic declarative UI architecture is its shader compilation method, which uses the JIT (Just-In-Time) approach. Skia doesn’t know what shaders your app will need until the app runs (runtime).

  1. The user triggers a new page transition animation that has shadow effects and custom color gradients.
  2. Skia realizes it doesn’t have a shader program for those effects in the GPU cache memory.
  3. Skia temporarily stops the visual rendering process and starts writing and compiling that shader to the GPU dynamically on the spot.
  4. This runtime GPU compilation takes anywhere from 10 to 50 milliseconds.
  5. Because our frame rendering time budget for 60 FPS is 16.6ms, this compilation pause causes frame drops, producing jarring visual stutter for users (shader compilation jank).

Skia’s Strengths #

  • Battle-Tested & Very Mature: Developed for over a decade and stability-tested on billions of active devices worldwide.
  • Extremely Broad Platform Support: Runs on nearly all legacy graphics drivers, including OpenGL, OpenGL ES, Vulkan, Metal, Direct3D, and even pure CPU software rendering.
  • Strong Web Ecosystem: CanvasKit (Skia compiled to WebAssembly) remains Flutter Web’s most reliable rendering engine for delivering UI identical to mobile.

Skia’s Weaknesses #

  • Permanent Shader Jank: The shader compilation jank problem can’t be totally fixed without completely overhauling Skia’s internal engine structure.
  • API Bridging Overhead: Because it was designed in the OpenGL era, Skia must use an extra bridging layer to communicate with modern APIs like Apple’s Metal, adding CPU workload.
  • Larger Binary Size: Must include the entire runtime shader compiler module (GLSL compilation infrastructure) inside your app’s binary package.

Why Impeller Was Created #

The shader compilation jank problem in Skia could actually be partially mitigated using shader warm-up techniques (shader warm-up or sksl-bundle). However, this process was very tedious because developers had to manually record animations on physical devices, export .sksl configuration files, and include them during release builds. Worse, those configuration files often weren’t compatible when run on devices with different GPU types.

Beyond the jank problem, the evolution of the operating system ecosystem forced the Flutter team to make a big decision. In 2018, Apple officially deprecated OpenGL on iOS and macOS to fully transition to the Metal API. Because Skia was historically rooted in OpenGL, forced bridging to the Metal API created unnecessary performance overhead.

The Flutter team concluded that the only permanent solution to eliminate shader jank and maximize modern graphics card potential was to build a new rendering engine from scratch, designed exclusively for Flutter’s needs: Impeller.


Impeller — The Next-Generation Rendering Engine #

Impeller began active design in late 2021. Unlike Skia, which must compromise with the needs of the Chrome web browser or the Android OS, Impeller was developed with a single focus: executing Flutter’s declarative UI pipeline as fast and smoothly as possible.

Impeller’s architecture is built on four design pillars fundamentally different from Skia:

1. Retained Mode Rendering Model #

Impeller abandons Skia’s redraw-everything-from-scratch model and adopts the Retained Mode Rendering pattern. Under this model, Impeller actively tracks and maintains the visual status of every layer in GPU memory.

When a new frame is triggered:

  • Impeller performs incremental analysis to detect which visual parts changed position or color.
  • Impeller only re-rasterizes the dirty areas (dirty tiles).
  • For static visual areas (like background images or title text), Impeller directly reuses existing GPU texture caches without re-rendering.

2. Tile-Based Rendering #

To minimize GPU workload on mobile devices with limited battery power, Impeller divides the screen into a grid of small tiles (e.g., $256 \times 256$ pixels each).

flowchart TD
    subgraph Grid["Tile-Based Grid Visualization"]
        direction TB
        T1["Tile 1 (Static)"] --- T2["Tile 2 (Static)"] --- T3["Tile 3 (Static)"]
        T4["Tile 4 (Static)"] --- T5["Tile 5 (Dynamic / Changing)"] --- T6["Tile 6 (Static)"]
        T7["Tile 7 (Static)"] --- T8["Tile 8 (Static)"] --- T9["Tile 9 (Static)"]
    end
          
    style T5 stroke:#f44336,stroke-width:2px

If a touch ink ripple animation only happens in the lower-middle area (e.g., in the Tile 5 area), Impeller instructs the graphics card to only process the pixels inside that Tile 5 box, while other tiles are excluded from new rasterization calculations. This significantly saves GPU work cycles.

3. Ahead-Of-Time (AOT) Shader Compilation #

This is Impeller’s main weapon that permanently solves the shader jank problem. Impeller moves the entire shader compilation process from the app’s runtime phase to the app package build phase (build time).

When you run a build command (like flutter build apk or flutter build ipa), the Flutter system triggers a dedicated shader compiler called impellerc:

flowchart TD
    GLSL["GLSL Shader (Source Code)"] -->|"impellerc (Build Time)"| Compile{"Platform Target?"}
    Compile -->|"iOS / macOS"| MetalLib["Metal Shader Library (.metallib)"]
    Compile -->|"Android (Vulkan)"| SPIRV["SPIR-V Binary (.spirv)"]
    Compile -->|"Legacy OpenGL Fallback"| GLES["OpenGL ES Shader (Compiled)"]
          
    style Compile stroke:#0288d1,stroke-width:2px

impellerc translates all GLSL shaders in the SDK into native binary shader library formats ready for the target GPU to execute. When the user installs and opens your app for the first time, the graphics card instantly loads those shader binaries (0ms delay) with no more dynamic compilation process. Jank disappears completely.

4. Modern Native Graphics API Utilization #

Impeller is built specifically to target next-generation modern graphics APIs that provide more granular hardware control, very low CPU driver overhead, and outstanding multi-threaded parallel execution capability.

  • iOS / macOS: Communicates directly with the native Metal API.
  • Android: Communicates directly with the native Vulkan API (on Android devices with API level 29 and above).
  • Android Legacy Fallback: Uses OpenGL ES when running on older Android devices that don’t yet support Vulkan stably.

Real Performance Comparison Data #

Here’s a visual performance comparison matrix between Skia and Impeller based on official testing results on production apps:

Evaluation MetricSkia EngineImpeller Engine
Shader Compilation JankOften occurs on the first frames of new animations.Completely jank-free because shaders are AOT compiled.
Complex Clipping Composition LoadSlow (~450ms in deep clipping scenarios).Very fast (~11ms) because optimized on the GPU.
Dropped Frames RateStandard baseline.Reduced by >70% in fast animation transitions.
RAM Memory ConsumptionStandard baseline.Saves up to ~100MB in heavy rendering scenarios.
Engine Binary Overhead SizeLarger because it carries a runtime JIT compiler.Smaller (~100kb compressed overhead) because the compiler is discarded.

Current Status (2025) #

The rendering engine transition from Skia to Impeller is happening gradually to maintain application stability at production level. Here’s the default Impeller adoption status on various target platforms:

Target Operating SystemDefault Engine StatusActive Graphics BackendImportant Notes
iOSImpeller (Default)Metal APIEnabled since Flutter 3.10. Starting with Flutter 3.29+, Skia support is permanently removed from iOS binaries.
Android (API 29+)Impeller (Default)Vulkan APIEnabled by default since Flutter 3.27.
Android (API < 29)Impeller (Default)OpenGL ESAutomatically falls back transparently without manual configuration.
macOSAvailableMetal APICan be enabled manually via a runtime flag.
WebNot Yet ActiveCanvasKit (Skia Wasm)Skwasm/WebGPU-based Impeller is still in active development.
Windows / LinuxNot Yet ActiveSkia EngineDesktop Vulkan Impeller is still being explored internally.

If you encounter specific visual rendering issues on Android devices during development, you can temporarily disable Impeller to compare results with Skia using the following command:

# Running an Android app while forcing the use of the Skia engine
flutter run --no-enable-impeller

What Still Uses Skia / Skia Components #

Although Impeller takes over all vector shape rasterization tasks and visual shader processing, the Flutter system doesn’t discard Skia entirely from the engine binary. This is because there are two sub-systems that are still highly optimal when handled by Skia’s built-in libraries:

  1. SkParagraph (Text Layout): The text layout analysis process (text shaping) is very complex because it involves unicode rules and cross-language typography. Skia’s SkParagraph sub-system is very mature and accuracy-tested. Therefore, Flutter still uses SkParagraph for letter position calculations, while Impeller handles drawing those letter glyphs onto the visual canvas.
  2. Skia Codecs (Image Decoding): Skia’s built-in image decoder library is still used to decode compressed image binary files (like JPEG, PNG, and WebP formats) into raw GPU textures before handing them to Impeller.

This task separation runs transparently and is fully managed at the Engine Layer level without affecting the Dart code you write.


Impeller and the Future: Flutter GPU #

Impeller’s modern architecture design opens new gates for the Flutter ecosystem that were previously impossible to reach when using Skia. One of them is the experimental introduction of the Flutter GPU API.

Flutter GPU is a low-level binding library (low-level graphics API) written in Dart. This library lets you as a developer:

  • Access the graphics card rendering pipeline directly from Dart code (writing custom render passes).
  • Load 3D model files in glTF format using the helper library Flutter Scene.
  • Render high-performance 3D objects, dynamic lighting, and interactive particles directly inside Flutter apps alongside ordinary 2D widgets without needing a third-party game engine.
import 'dart:ui' as ui;

// Conceptual visualization of rendering a 3D model using the Flutter GPU API
void renderCustom3DMesh(gpu.RenderPass renderPass, gpu.Buffer vertexBuffer) {
  renderPass
    ..bindPipeline(shader3DPipeline) // GLSL shader AOT compiled by impellerc
    ..bindVertexBuffer(vertexBuffer)
    ..draw(vertexCount: 36); // Drawing a 3D cube object
}

This innovation positions Flutter not just as an ordinary UI SDK, but transforms it into a high-performance interactive visual platform ready for the future of 3D graphics engineering and Augmented Reality (AR).

Summary #

  • Two Rendering Eras — Skia is Google’s mature universal 2D rendering engine, while Impeller is a next-generation rendering engine designed exclusively for Flutter.
  • Shader Jank in Skia — Caused by the JIT (Just-In-Time) compilation approach where new GPU shaders are written and compiled at runtime when new animation transitions appear.
  • Impeller’s AOT Solution — Statically compiles all GLSL shaders using the impellerc compiler at build-time (AOT), so shaders are instantly ready at runtime without triggering jank.
  • Retained Mode Optimization — Impeller actively tracks GPU memory status and only rasterizes changed screen tiles (tile-based rendering) to save GPU power.
  • Modern APIs Without Bridging — Impeller communicates directly with modern native graphics APIs Metal (iOS) and Vulkan (Android) to minimize CPU overhead.
  • iOS & Android Support — Impeller is active by default for all iOS apps (since 3.10) and Android devices with API level 29+ (since 3.27).
  • Division of Labor with Skia — Retains Skia’s SkParagraph component to handle text layout and complex glyph determination for rendering stability.
  • Flutter GPU Expansion — Leverages Impeller’s shader foundation to provide native 3D graphics APIs (Flutter Scene) directly from Dart.

← Previous: Rendering Pipeline   Next: Native Comparison →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact