Impeller: Flutter's New Rendering Backend and Offline Shader Compilation
Impeller, Flutter’s new rendering backend, eliminates runtime shader‑compilation jank by offline‑compiling GLSL to SPIR‑V and target APIs such as Metal, generating C++ bindings for fast pipeline state creation, offering predictable performance, portability and concurrency, though currently only the Metal backend is functional and feature support remains incomplete.
Flutter's 2022 roadmap highlighted the need to rethink shader usage and announced a rewrite of the image rendering backend. The emerging backend, Impeller, aims to solve shader‑compilation‑induced jank by moving compilation offline.
Background : Flutter uses Skia and SkSL (a GLSL variant). When a shader is first used, Skia compiles SkSL to a platform‑specific shader, which can take hundreds of milliseconds and cause frame drops. Tracing for GrGLProgramBuilder::finalize reveals such jank.
Flutter introduced SkSL pre‑warming in version 1.20 (offline collection of shaders into a JSON file) and iOS Metal shader pre‑compilation in 2.5, reducing launch‑time jank on devices like Moto G4 and iPhone 4s.
However, pre‑warming has drawbacks: larger app size, longer startup, limited shader portability, and a sub‑optimal developer experience.
Impeller Goals :
Predictable performance – offline compile all shaders and build pipeline state objects (PSOs) ahead of time.
Detectability – track all graphics resources.
Portability – shaders written once, converted to target APIs.
Modern API usage – leverage Metal/Vulkan features without being tied to them.
Concurrency – distribute frame work across multiple threads.
Architecture : Impeller consists of several modules – Compiler, Renderer, Entity, Aiks, plus core libraries (Geometry, Base). The Compiler (host tool) includes a Compiler and Reflector that turn GLSL 4.60 shaders into SPIR‑V and then into target languages (e.g., Metal SL). The Reflector generates C++ shader bindings for fast PSO creation.
Example of a GLSL vertex shader:
uniform FrameInfo {\n mat4 mvp;\n vec4 color;\n} frame_info;\n\nin vec2 vertices;\n\nout vec4 color;\n\nvoid main() {\n gl_Position = frame_info.mvp * vec4(vertices, 0.0, 1.0);\n color = frame_info.color;\n}\nAfter offline compilation it becomes Metal code:
using namespace metal;\nstruct FrameInfo {\n float4x4 mvp;\n float4 color;\n};\n\nstruct solid_fill_vertex_main_out {\n float4 color [[user(locn0)]];\n float4 gl_Position [[position]];\n};\n\nstruct solid_fill_vertex_main_in {\n float2 vertices [[attribute(0)]];\n};\n\nvertex solid_fill_vertex_main_out solid_fill_vertex_main(\n solid_fill_vertex_main_in in [[stage_in]],\n constant FrameInfo& frame_info [[buffer(0)]]) {\n solid_fill_vertex_main_out out = {};\n out.gl_Position = frame_info.mvp * float4(in.vertices, 0.0, 1.0);\n out.color = frame_info.color;\n return out;\n}\nThe generated C++ binding header looks like:
struct SolidFillVertexShader {\n static constexpr std::string_view kLabel = "SolidFill";\n static constexpr std::string_view kEntrypointName = "solid_fill_vertex_main";\n static constexpr ShaderStage kShaderStage = ShaderStage::kVertex;\n // ... definitions of PerVertexData, FrameInfo, resource slots, input/output slots ...\n static bool BindFrameInfo(Command& command, BufferView view) {\n return command.BindResource(ShaderStage::kVertex, kResourceFrameInfo, std::move(view));\n }\n};\nRendering Flow : Flutter's display list records drawing commands (73 Ops, 11 Contents). During the Impeller pass, a DisplayListDispatcher converts Ops into EntityPass trees. Each Entity maps to a Content and later to a Command that bundles a pipeline state, vertex/fragment buffers, and shader bindings.
Example of an Op struct:
struct DrawRectOp final : DLOp {\n static const auto kType = DisplayListOpType::kDrawRect;\n explicit DrawRectOp(SkRect rect) : rect(rect) {}\n const SkRect rect;\n void dispatch(Dispatcher& dispatcher) const { dispatcher.drawRect(rect); }\n};\nCommands are encoded into Metal command buffers, setting PSOs and buffers before submission.
Current Status :
Offline‑compiled shader libraries improve first‑frame performance.
Only Metal backend is functional (iOS/macOS).
Supports 73 Ops and 11 Contents.
Codebase ~18.8k lines, still relies on Skia data structures.
Many features (stroke, filters, certain draw calls) are pending.
Impeller demonstrates Flutter’s commitment to eliminating jank by rewriting the rendering stack, and it promises significant performance gains for mobile graphics.
DaTaobao Tech
Official account of DaTaobao Technology
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.