Skip to content

Instantly share code, notes, and snippets.

@jezell
Created March 2, 2025 01:24
Show Gist options
  • Save jezell/41012a13c6789fa7c1c3b0868a8b0942 to your computer and use it in GitHub Desktop.
Save jezell/41012a13c6789fa7c1c3b0868a8b0942 to your computer and use it in GitHub Desktop.

That sounds like an ambitious and exciting project! I'll research how to upgrade the SkWasm renderer to use Skia Graphite and adapt it to run natively on iOS. This will involve understanding the SkWasm renderer, Skia Graphite integration, and modifying the Flutter engine for iOS compatibility. I'll get back to you with a detailed breakdown and potential implementation steps.

Upgrading Flutter’s SkWasm Renderer to Skia Graphite (with iOS Native Integration)

Introduction

Flutter’s SkWasm renderer is a new WebAssembly-based rendering backend that uses Skia for Flutter Web apps. It aims to reduce download size and enable multi-threaded rendering on the web (Web renderers | Flutter). Meanwhile, Skia Graphite is an upcoming Skia rendering architecture designed for modern graphics APIs (Metal, Vulkan, WebGPU) to replace Skia’s traditional GPU backend (Ganesh) (Switch from Ganesh to Graphite · Issue #982 · JetBrains/skiko · GitHub). This report examines how one could upgrade the SkWasm renderer to use Skia Graphite and adapt it to run natively on iOS. We cover:

  • SkWasm architecture and its integration with Flutter’s web engine.
  • Skia Graphite overview, and its advantages over the current Skia model.
  • Steps to modify SkWasm for Graphite.
  • Challenges in getting SkWasm/Graphite working on iOS natively, and potential solutions.
  • How to build and integrate a custom Flutter engine on iOS to support these changes.
  • References to relevant discussions or prior work in the Flutter community.

Understanding SkWasm’s Architecture and Integration

SkWasm (Skia WebAssembly renderer) is essentially a streamlined build of Skia compiled to WebAssembly, used by Flutter Web when the --wasm build mode is enabled (Web renderers | Flutter) (Web renderers | Flutter). In Flutter’s web engine, the rendering stack is abstracted such that CanvasKit and SkWasm are two alternate renderers implementing Flutter’s drawing API in the browser (Web renderers | Flutter). The Flutter framework produces Scene objects (collections of layers and painting commands) and the chosen web renderer “converts UI primitives (stored as Scene objects) into pixels” (Web renderers | Flutter).

Key points about SkWasm’s design and integration:

  • Skia in WASM: SkWasm includes a compact Skia build (about 1.1 MB download, versus 1.5 MB for CanvasKit) (Web renderers | Flutter) (Web renderers | Flutter). This smaller footprint is achieved by trimming unused Skia features for web. The SkWasm module is loaded at runtime and used by Dart/JS code in the Flutter web engine.

  • Threaded Rendering: Unlike the CanvasKit renderer (which runs on the main browser thread), SkWasm can offload rendering to a Web Worker thread. When running in browsers that support SharedArrayBuffer and WASM threads, Flutter will spawn a dedicated worker to perform rendering tasks in parallel (Web renderers | Flutter). This leverages multiple CPU cores and reduces jank by doing heavy paint work off the main thread. (If the browser doesn’t allow this, SkWasm falls back to single-threaded mode.)

  • WasmGC Integration: SkWasm requires modern WebAssembly features, notably Wasmtime GC (Garbage Collection) support (Wasm | Flutter). Dart code is compiled to WASM (via dart2wasm), and new GC capabilities allow passing complex Dart objects to the Skia WASM module efficiently (without the older JS interop overhead). This tight integration means higher performance and more direct calls between Dart and Skia. However, it also limits compatibility – only browsers with Wasm GC support can use SkWasm, otherwise Flutter falls back to CanvasKit (Web renderers | Flutter).

  • Fallback and Selection: At runtime, the Flutter web engine auto-selects the renderer. In WASM build mode, it will use SkWasm if supported, or default to CanvasKit if not (Web renderers | Flutter). Developers can also explicitly choose one via a configuration when bootstrapping the app (Web renderers | Flutter). The HTML DOM renderer is being deprecated in favor of these Skia-based renderers (for performance and fidelity reasons).

In summary, SkWasm is essentially Flutter’s web engine reimplemented on a WebAssembly Skia, allowing Flutter web apps to approach native speeds by using compiled code and multi-threading. It integrates by providing the same canvas APIs to Flutter’s rendering layer but executing them inside a WASM module (potentially off-main-thread).

Overview of Skia Graphite and Its Advantages

Skia Graphite is a new rendering architecture in Skia, intended as the successor to Skia’s current GPU backend (code-named Ganesh). Graphite is designed from the ground up for modern graphics APIs – such as Metal (Apple), Vulkan (Android/Windows), and Dawn/WebGPU (web) (Switch from Ganesh to Graphite · Issue #982 · JetBrains/skiko · GitHub) (Switch from Ganesh to Graphite · Issue #982 · JetBrains/skiko · GitHub). The Skia team’s goal is to eventually deprecate Ganesh and move forward with Graphite for all platforms (Switch from Ganesh to Graphite · Issue #982 · JetBrains/skiko · GitHub). Key advantages and features of Skia Graphite include:

  • Optimized for Modern GPUs: Graphite avoids legacy OpenGL paradigms and instead aligns with modern GPU pipelines. It’s built to work efficiently with Metal, Vulkan, and WebGPU, taking advantage of their advanced features and multithreading capabilities (Switch from Ganesh to Graphite · Issue #982 · JetBrains/skiko · GitHub) (Switch from Ganesh to Graphite · Issue #982 · JetBrains/skiko · GitHub). (Notably, Graphite does not support older APIs like OpenGL – it’s a clean break oriented toward the future.) This is directly relevant for iOS, where Metal is the only graphics API; Graphite can use Metal natively for rendering.

  • Reduced CPU Overhead: Graphite uses a different approach to recording and executing draw commands. It aims to lower the CPU cost of rendering by better batching work and using modern GPU primitives (engine/impeller/docs/faq.md at main · flutter/engine · GitHub). In practice, this means the act of “recording” a frame’s drawing commands should be faster than with Ganesh. Graphite’s architecture allows more work to be prepared up front and offloaded to the GPU efficiently, which is beneficial for high-frame-rate or graphically intensive apps.

  • Shader Pre-compilation and Jank Reduction: One of Graphite’s design goals is to facilitate pre-compiling shaders (GPU programs) at application startup (Switch from Ganesh to Graphite · Issue #982 · JetBrains/skiko · GitHub) (Switch from Ganesh to Graphite · Issue #982 · JetBrains/skiko · GitHub). This directly addresses the notorious “shader jank” issue that Flutter encountered with Skia/Ganesh (where new shader compilations during runtime cause frame drops). By enumerating or knowing most shader variations ahead of time, Graphite can compile them early, avoiding runtime stalls. Impeller, Flutter’s new custom renderer on mobile, was created largely to solve this same problem by prewarming shaders. Notably, the Skia team has stated that Graphite addresses many of the same issues Impeller set out to solve (Switch from Ganesh to Graphite · Issue #982 · JetBrains/skiko · GitHub), including smoother first-run performance. In other words, Graphite converges on the idea of eliminating jank without a separate engine.

  • Unified Backend and Multi-platform: Graphite is part of Skia itself and will run across all platforms (eventually including web, via WebGPU). This is a big advantage: it means a single Skia-based rendering pipeline could potentially cover mobile, desktop, and web. As one Flutter engineer noted, Graphite “runs everywhere including web and desktop,” whereas Flutter’s Impeller (as of now) targets only mobile (Is Impeller engine expected to replace canvaskit and Skwasm And be the only engine for all flutter applications? : r/FlutterDev). In the long term, Graphite could simplify Flutter’s renderer strategy by providing a common high-performance backend for all platforms, maintained by the Skia team.

  • Future-proof and Actively Developed: Major projects are adopting Graphite. For example, Google Chrome and ChromeOS are in the process of switching their rendering from Ganesh to Graphite (Switch from Ganesh to Graphite · Issue #982 · JetBrains/skiko · GitHub). The Skia team’s long-term plan is to replace Ganesh with Graphite entirely, indicating strong support and ongoing improvements. This momentum means Graphite will get attention, optimizations, and bug fixes as it matures, and it’s unlikely to be abandoned (whereas Flutter’s own engine forks must be maintained by Flutter team alone).

Overall, Skia Graphite promises better performance, less runtime latency, and more direct use of modern GPU features compared to the traditional Skia approach. For our purposes, its most important trait is that it can leverage Metal on iOS and WebGPU on Web, making it a prime candidate to unify Flutter’s rendering across web and iOS with high efficiency.

Upgrading SkWasm to Use Skia Graphite

Upgrading the SkWasm renderer to use Graphite involves changes at both the Skia build configuration level and the Flutter web engine integration level. Here’s an overview of the steps required to modify SkWasm for Graphite:

  1. Enable Graphite in Skia’s Build: The Flutter engine includes Skia as a third-party dependency, typically built with Ganesh enabled and Graphite disabled (unless Impeller’s “Skia GPU” is removed). The first step is to compile Skia with Graphite enabled. In Skia’s GN build flags, this means setting skia_enable_graphite = true (and likely keeping skia_enable_ganesh = true for now, unless we want to drop Ganesh entirely). Enabling Graphite also requires enabling one of the backends (Metal, Vulkan, or Dawn) appropriate to the platform. For WebAssembly, the relevant backend would be Dawn (WebGPU), since Graphite can’t target WebGL. We would configure Skia’s build for web to include Dawn – essentially allowing Skia Graphite to run on WebGPU in the browser (Switch from Ganesh to Graphite · Issue #982 · JetBrains/skiko · GitHub). (Note: WebGPU/Dawn support in browsers is still emerging, currently available in Chrome and Safari Technology Preview with flags. This step assumes targeting the future or an environment where WebGPU is accessible for WASM.)

  2. Modify SkWasm Renderer Initialization: In Flutter’s web engine code ( Dart code under flutter/engine/lib/web_ui), the SkWasm renderer currently initializes a WASM module (containing Skia) and uses it to draw into a WebGL context (for CanvasKit) or possibly a bitmap context. Upgrading to Graphite means changing how the rendering context is created. Instead of creating an Skia GPU context for WebGL (Ganesh’s typical path) or using CPU, we need to initialize a Graphite context. Concretely, this might involve:

    • Requesting a WebGPU Canvas or OffscreenCanvas with a WebGPU context from the browser. The code would use the browser’s JavaScript interop to create a WebGPU device/queue (using Dawn internally) and pass that to Skia.
    • Initializing Skia’s Graphite with that device: e.g. calling something like GrDirectContext::MakeGraphite(DawnDevice) or equivalent (the actual API might differ, but conceptually we create a skgpu::graphite::Context).
    • Creating a SkSurface or similar render target that is backed by Graphite. Skia likely provides a way to wrap a WebGPU texture into a Graphite render target. The SkWasm Dart code must call into the WASM module to set up this surface. After this, all SkCanvas drawing commands should be executed against Graphite’s recording/playback system rather than Ganesh.

    In essence, we replace the “Ganesh + WebGL” setup with “Graphite + WebGPU”. Once Graphite’s context is ready, the rest of the rendering calls (drawing paths, text, images) can use Skia’s normal API – Graphite is designed to support the same Skia API (the SkPaint, SkCanvas operations, etc.) just routed through the new backend (engine/impeller/docs/faq.md at main · flutter/engine · GitHub). This means the higher-level Flutter web engine code might not need dramatic changes; it can issue draw commands as before, but those commands will be executed by Graphite under the hood.

  3. Update Threading Model if Necessary: SkWasm already supports threading via Web Workers. Graphite itself is thread-friendly (designed to allow multithreaded resource uploads, etc. (Switch from Ganesh to Graphite · Issue #982 · JetBrains/skiko · GitHub)), so we would continue to leverage the worker setup. We might need to ensure the WebGPU context is created on the worker thread (possible via an OffscreenCanvas with a GPU context transferred to the worker), so that all Graphite drawing happens off the main thread. Modern browsers do allow WebGPU and OffscreenCanvas in workers, similar to WebGL. The communication between threads (main thread for input/events and worker for rendering) would remain, as implemented in SkWasm. So, no major change here aside from using the correct API calls for WebGPU on a worker.

  4. Maintain Fallbacks: Not all browsers currently support WebGPU or the required features for Graphite. In a practical implementation, we’d want to keep a fallback. For example:

    • If the browser supports WasmGC and threads but not WebGPU, perhaps we fall back to the existing SkWasm (Ganesh) or even CanvasKit.
    • Or we could maintain two WASM builds of Skia: one with Graphite (for WebGPU-capable browsers) and one with Ganesh (for WebGL). This increases complexity and size, so an alternative is to allow Graphite to gracefully fall back to a CPU raster surface when GPU isn’t available. (Graphite might not directly support CPU rendering; if not, we rely on CanvasKit fallback as done today.)

    For the scope of this discussion, we assume targeting environments where Graphite can be used. But it’s worth noting this challenge: WebGPU is required for Graphite on web. This means at the time of writing, only Chrome Canary/Edge with flags or Safari Tech Preview would run the Graphite-powered SkWasm, whereas CanvasKit/Ganesh runs on WebGL everywhere. In the future, as WebGPU becomes widely available, Graphite-WASM could be more broadly viable.

  5. Compile and Test: After making these changes, we would compile the Flutter web engine with the modified Skia. We’d test on a WebGPU-enabled browser, verifying that the Flutter app renders correctly using Graphite. Key things to watch for include rendering correctness (Graphite is still maturing, some Skia features might not be fully implemented yet) and performance characteristics (we would expect faster frame times if things are working, especially under heavy graphics load or on high-DPI screens).

  6. Leverage Graphite Features: As a later step, once Graphite is up and running in SkWasm, we could consider tuning or using Graphite-specific features. For example, Graphite might allow explicit shader pre-compilation. Flutter web engine could detect known shader patterns (e.g., for certain primitives or ImageFilter effects) and ask Graphite to create pipelines for them at startup. This is speculative, but it’s a way to capitalize on Graphite’s strengths beyond just switching backends.

Note: Implementing this upgrade is non-trivial. As of August 2024, the Flutter team stated they had no immediate plans to adopt Graphite (engine/impeller/docs/faq.md at main · flutter/engine · GitHub), likely because Graphite was not yet fully production-ready for Flutter’s needs. So this exercise is forward-looking. It assumes Graphite has matured enough to be usable. Any current attempt would need to work closely with Skia’s development, and expect some missing pieces or bugs (especially in text rendering, shaders, etc., that Graphite might still be catching up on).

Challenges in Running SkWasm/Graphite Natively on iOS (and Solutions)

Adapting SkWasm to run natively on iOS essentially means using the same “Skia with Graphite” idea within a Flutter iOS app, instead of in a browser. There are several challenges to address:

  • WASM vs Native: SkWasm is designed for the web environment; on iOS, we don’t actually want to use WebAssembly. Instead, we would compile Skia (with Graphite) directly as a native library and link it into the iOS Flutter engine. This is more straightforward in terms of performance (no WASM overhead or browser sandbox). The challenge is porting any Dart-side logic from the web engine to the native engine. For example, the Flutter web engine’s Dart code orchestrates the SkWasm calls. On iOS, Flutter’s engine is in C++ and uses Dart VM for the framework code. We might need to reimplement the “glue” that was written in Dart (for web) into the iOS engine’s C++ layer. Essentially, instead of a Dart/JS bridge calling WASM functions, we’d call the Skia/Graphite C++ APIs directly from the engine’s raster thread. The Flutter Scene mechanism on iOS could be repurposed to drive Graphite rather than the default Skia.

  • Engine Architecture Differences: On iOS (and other native platforms), Flutter’s engine already has a well-defined renderer setup: by default it used Skia (Ganesh) with OpenGL/Metal, and now it uses Impeller (with Metal) if enabled. We cannot simply “drop in” the web engine code. We have to integrate with Flutter’s existing thread model: typically Flutter has a UI thread (Dart code) and a Raster thread where rendering is executed. The engine’s C++ Rasterizer receives LayerTree (scene) objects and draws them using either Skia or Impeller. To use Skia Graphite natively on iOS, we would likely create a new variant of a rasterizer that uses Skia+Graphite. The steps might include:

    • Initializing a Metal device and command queue for Flutter’s process (since on iOS, Graphite will use Metal).

    • Creating a Skia Graphite Context for Metal (Skia has an API to create a Graphite context from an MTLDevice and MTLCommandQueue, given that Metal is available).

    • When a Flutter frame needs rendering, use Graphite to render the scene: e.g., create a Graphite Recording or get a Graphite-backed SkSurface corresponding to the iOS CAMetalLayer (the layer that Flutter uses for output on iOS), then draw the Flutter layers onto it. We might reuse Flutter’s DisplayList mechanism (which records drawing commands from the framework) and play those into an SkCanvas obtained from a Graphite surface. If Skia’s SkCanvas API works transparently with Graphite (once the surface is Graphite-backed), this could be relatively straightforward. If not, we might need to adapt how painting is done (possibly using Skia’s new Graphite-specific recording APIs).

    • After drawing, flush the Graphite context to present the frame on screen.

    One complication is that Flutter’s Impeller is now the default on iOS. We would likely disable Impeller to use Skia Graphite instead (Impeller is a separate code path). Alternatively, one could imagine integrating Graphite as an option in Flutter similar to how Impeller is toggled, but that would require plumbing a new setting through the engine.

  • Dealing with Flutter-Specific Features: Flutter’s engine provides many things beyond just drawing shapes: text layout (using SkShaper/Harfbuzz), image decoding, compositing layers, platform views, etc. By using Skia (even with Graphite), we retain most of this functionality since Skia handles text rendering, images, etc. But if we were to remove Ganesh and rely on Graphite exclusively, we must ensure that Graphite supports all the operations Flutter needs. As of mid-2024, Graphite did not support all Skia features (for example, certain blend modes or effects might not yet be implemented, and it lacked a GPU-driven text rendering in its early versions). This could cause some Flutter widgets or effects to render incorrectly. Solution: a potential solution is to have a hybrid approach – use Graphite for what it can do, and fall back to CPU for things it can’t. However, that would be complex. A more realistic approach is to contribute missing feature implementations to Graphite, or wait until Graphite fully supports Skia’s API surface (the Skia team is actively working on filling gaps). In the interim, focus on core rendering (shapes, simple shaders) which Graphite handles, and avoid exotic effects.

  • iOS Platform Constraints: Running natively on iOS means we have to abide by iOS platform rules for executables. One notable difference: in browsers, SkWasm uses threading via Web Worker; on iOS, Flutter already has threads available (UI, raster, IO threads). We must ensure Graphite’s usage is confined to the raster thread and potentially additional worker threads for shader compilation if needed. This is fine – iOS has no issue with threads in native code. Another consideration: binary size. If we include both Impeller and Skia Graphite, that could bloat the app. Flutter’s team noted that removing Skia’s GPU code (when Impeller is used exclusively) saved ~17% of engine binary size (Skia and flutter/impeller - Google Groups). Using Graphite means we’d still include Skia (but perhaps could drop Ganesh to save space). It’s a trade-off: including Graphite and Ganesh and Impeller would be large; a custom engine might choose to include only Graphite to keep size reasonable.

  • WASM GC and iOS Browsers: It’s worth mentioning why we can’t rely on the browser on iOS: Flutter compiled to WASM cannot run in any iOS browser due to Apple’s restrictions (all iOS browsers use WebKit which, as of now, doesn’t fully support the needed WASM features) (Wasm | Flutter). This is exactly why running natively is considered – it bypasses the browser entirely. The solution here is the approach of building a native app with a custom engine. By doing so, we don’t depend on Safari’s limitations at all; we ship our own engine. So this challenge is addressed by the approach itself.

  • Coordination with Flutter Framework: If we change the engine to use Graphite on iOS, the Flutter framework (Dart code) mostly doesn’t need to change – it’s agnostic to the rendering backend. However, certain optimization hints (like shader warm-up APIs) might become no-ops or could be tied into Graphite’s mechanisms. We should audit if any Dart code assumes Skia-specific behavior and ensure it still works or is adapted for Graphite. Generally, Flutter uses an abstraction (dart:ui Canvas, Paint, etc.) that would remain the same.

Potential Solutions & Strategies:

  • Implement a Graphite-powered Rasterizer in the Flutter engine behind a flag. For example, one could add a flag similar to --enable-graphite that switches the engine to the new path. This would allow side-by-side testing against the default.

  • Start with Ganesh+Graphite dual build: For development, you might keep Ganesh available in the build and be able to compare rendering output or performance. Once Graphite proves itself, Ganesh could be disabled (to save size and avoid maintaining two code paths).

  • Reuse as much code as possible: Graphite is part of Skia, so a lot of the existing Skia integration in Flutter’s engine (e.g., the code that interfaces with SkSurface, SkCanvas) can be reused or minimally adapted. We would primarily adjust how the surface/context is created. For instance, on iOS today the engine creates an SkSurface from a GrDirectContext (Ganesh) and a Metal layer texture. With Graphite, we instead create a graphite::Context and perhaps an SkSurface via SkSurface::MakeGraphite(...) (if such API exists) tied to the Metal layer. The drawing commands (DisplayList playback) could remain largely the same since SkSurface still provides an SkCanvas to draw into. This minimizes the impact on Flutter’s higher levels.

  • Collaboration with Skia: Because Graphite is under heavy development, working with the Skia team (or studying their progress) is crucial. They may have example code for using Graphite with Metal (possibly in Skia’s docs or examples). Additionally, if any Flutter community members have experimented (for example, the JetBrains team behind Compose Multiplatform considered switching to Graphite (Switch from Ganesh to Graphite · Issue #982 · JetBrains/skiko · GitHub)), learning from those experiments can guide our implementation.

In summary, the main challenges are technical integration (wiring Graphite into Flutter’s engine threads and layer system) and feature parity. The solutions involve careful engine modifications, optionally behind experiment flags, and leveraging Graphite where it shines (Metal on iOS for fast GPU rendering) while managing fallback paths for any missing pieces.

Building and Integrating a Custom Flutter Engine (with Graphite) on iOS

Making these changes means we need to build a custom Flutter engine. Flutter’s engine is open-source, so we can modify and compile it ourselves. The process involves a few steps:

  1. Set up the Engine Development Environment: Follow Flutter’s official instructions for engine development. This includes syncing the Flutter engine repository and installing dependencies (via gclient). The Flutter wiki’s “Setting up the Engine development environment” guide is the starting point (ensuring you have depot_tools, etc.). For iOS development, you’ll need a Mac with Xcode installed.

  2. Apply Changes for Graphite: Modify the engine as discussed – for example, update build configurations in flutter/engine to enable Graphite. You might edit the GN args in flutter/engine/tools/gn or use gn args to flip skia_enable_graphite=true. Also add any code for Graphite context initialization in the iOS embedder (the code under flutter/engine/src/flutter/shell/platform/darwin/ios). Ensure to disable Impeller for now (Impeller can be turned off via a runtime flag in Flutter, or build the engine with --no-impeller for simplicity) so it doesn’t conflict.

  3. Compile the Engine for iOS: From the engine source (src/flutter directory), use Flutter’s build scripts to compile iOS binaries. For example, run the GN tool with iOS config:

    ./flutter/tools/gn --ios --unoptimized

    This generates Xcode project files and GN build files for an iOS debug engine (Compiling the engine · flutter/flutter Wiki · GitHub). (Use --ios --release for a release build, etc., and --simulator if targeting the iOS Simulator.) Once GN is configured, build using Ninja:

    ninja -C out/ios_debug_unopt

    This will compile the Flutter engine and produce frameworks or binaries for iOS (Compiling the engine · flutter/flutter Wiki · GitHub). (It’s also recommended to build the corresponding host tools with ninja -C out/host_debug_unopt as Flutter tools sometimes need host binaries (Compiling the engine · flutter/flutter Wiki · GitHub).) The result should include Flutter.framework (and possibly FlutterEngine.framework) which contain the engine with our modifications.

  4. Integrate the Custom Engine into a Flutter App: There are two common ways to test and use your custom engine:

    • Using Flutter’s Local Engine Feature: Flutter’s CLI can run an app with a local engine binary. Set the ENGINE_SRC_PATH environment variable to the engine src path, and use --local-engine flag with the appropriate build configuration. For example:

      flutter run -d ios --local-engine ios_debug_unopt

      This tells Flutter to use the custom built engine instead of the default. Make sure the Flutter framework version matches the engine commit to avoid version skew (Compiling the engine · flutter/flutter Wiki · GitHub). This method is convenient for development and testing – you can launch on a device or simulator and Flutter will inject your engine.

    • Manual App Embedding: For a more permanent integration, you can take the output frameworks (Flutter.framework, etc.) and embed them into an Xcode project (or modify Flutter’s iOS runner). You’d replace the engine binaries that the Flutter tool would normally embed. If using CocoaPods, you can publish your custom engine as pod frameworks or just manually override them in Runner. Ensure to also include any supporting files (e.g., the flutter_assets and VM data files as usual).

  5. Testing on iOS: Run the Flutter app on an iOS device or simulator. If everything is set up, the app should launch using the custom engine. You would then verify that rendering is happening via Graphite. Since Graphite vs Skia Ganesh is an internal detail, one way to verify is by performance metrics or by intentionally logging which backend is in use (for instance, you might add a log in your engine code when Graphite context is created successfully). You could also test scenarios that Graphite handles differently (like fast animations that previously had shader jank – they should be smooth if Graphite’s pipeline is working and precompiling shaders as expected).

  6. Iterate and Debug: If you encounter crashes or blank screens, use Xcode and lldb to debug the engine. Running a Flutter app with a custom engine in Xcode is possible by opening the generated flutter_engine.xcodeproj (from the out/ios_debug_unopt directory) and attaching to the Flutter app process (Compiling the engine · flutter/flutter Wiki · GitHub) (Compiling the engine · flutter/flutter Wiki · GitHub). Ensure Metal API validation is enabled to catch any misuse of Metal via Graphite. Also, you can use Xcode’s GPU Frame Capture to inspect commands – if Graphite is working, you’ll see Metal draw calls issued by Skia’s Graphite rather than Flutter’s Impeller or GL.

Throughout this process, consult Flutter’s wiki pages and engine docs for any specific flag or step. The Flutter Wiki – Compiling the Engine guide provides detailed steps for iOS builds (Compiling the engine · flutter/flutter Wiki · GitHub) (Compiling the engine · flutter/flutter Wiki · GitHub) and using the engine in Flutter tooling (Compiling the engine · flutter/flutter Wiki · GitHub). Additionally, keep an eye on Flutter’s engine repository issues/discussions – someone may have attempted enabling Graphite (for example, enabling it in Android or for some experiments). Learning from those attempts can save time.

Related Work and Discussions

While there is not yet an official implementation of “SkWasm with Graphite on iOS,” there are several relevant discussions and precedents in the community:

  • Flutter Team Commentary: The Flutter team is aware of Skia Graphite. In fact, they have mentioned that “Graphite is awesome” and that Skia is solving with Graphite “all the problems Impeller was created to address” (Is Impeller engine expected to replace canvaskit and Skwasm And be the only engine for all flutter applications? : r/FlutterDev) (Is Impeller engine expected to replace canvaskit and Skwasm And be the only engine for all flutter applications? : r/FlutterDev). However, they have strategic reasons for continuing with Impeller for now (Is Impeller engine expected to replace canvaskit and Skwasm And be the only engine for all flutter applications? : r/FlutterDev). This indicates that in principle, a Graphite-based solution is viable and attractive, but Flutter has chosen a different short-term path. Our exploration aligns with what the team has considered, even if not pursued yet.

  • Impeller vs Graphite: Impeller is Flutter’s custom rendering engine (currently default on iOS, optional on Android) that precompiles a specific set of shaders (those needed for Flutter’s primitive operations) ahead of time. Some community members asked if Flutter could have just used Skia Graphite instead of creating Impeller. The responses (from both Flutter engineers and others) note that when Impeller started, Graphite wasn’t ready; also Impeller gives Flutter fine-grained control. But importantly, they acknowledge that Graphite is very similar in goals (Switch from Ganesh to Graphite · Issue #982 · JetBrains/skiko · GitHub) (Is Impeller engine expected to replace canvaskit and Skwasm And be the only engine for all flutter applications? : r/FlutterDev). This background is useful because it means if Graphite becomes sufficiently mature, Flutter could pivot to it. In our case, we are essentially experimenting with that idea early.

  • Skia Graphite in other frameworks: The JetBrains Compose Multiplatform project (which uses Skia via Skiko) has an open issue about switching from Ganesh to Graphite (Switch from Ganesh to Graphite · Issue #982 · JetBrains/skiko · GitHub). They list many of the same benefits (modern API support, etc.) and note that Graphite does not work with OpenGL (Switch from Ganesh to Graphite · Issue #982 · JetBrains/skiko · GitHub). That effort is analogous to upgrading SkWasm – it’s about flipping Skia’s backend. Following their progress could provide insight into performance gains or pitfalls encountered. It’s also evidence that there’s community interest in Graphite’s benefits across frameworks.

  • Chrome/Chromium: Since Chrome is adopting Graphite, one can look at Chrome’s bug tracker or Skia’s mailing list for updates on Graphite. Changes in Skia’s API or stability improvements will often be documented there. For example, Skia discussions in August 2023 mention that Graphite was being actively integrated and that “the long-term goal for Skia is to move forward with Graphite only.” (Switch from Ganesh to Graphite · Issue #982 · JetBrains/skiko · GitHub). This trajectory suggests that by the time one implements Graphite in Flutter, the upstream support in Skia will be even more robust.

  • Flutter Web and WebGPU: There is a Flutter issue about adding a WebGPU backend for Flutter Web (WebGPU Backend for Flutter Web · Issue #124334 - GitHub). The suggested approach there was to wait for Skia Graphite to be ready, then “just change some compiler flags” to use it (WebGPU Backend for Flutter Web · Issue #124334 - GitHub). This confirms that using Graphite on Web (via Dawn/WebGPU) is considered the straightforward path to get WebGPU in Flutter, rather than writing a new backend from scratch. Our plan for SkWasm Graphite aligns with this thinking. It also means that when WebGPU is more widely available, the Flutter Web team might naturally move in this direction, potentially providing official solutions we can draw from.

  • iOS Custom Engine Successes: There have been examples of building custom Flutter engines for iOS for various reasons (e.g., embedding Flutter on Apple TV/tvOS, or integrating new native capabilities). These provide general proof that replacing Flutter’s engine on iOS is feasible. For instance, developers have built Flutter engines with tvOS support by modifying and compiling the iOS engine, distributing those binaries to be used in place of the off-the-shelf engine. Our case (enabling Graphite) is different in purpose but similar in execution – it’s a customization of the engine. The community has documented such processes in blogs and GitHub projects, which could be a reference for troubleshooting the build and integration steps.

In conclusion, upgrading SkWasm to use Skia Graphite and running it natively on iOS is an ambitious but technically plausible endeavor. It leverages the latest graphics technology (WebGPU, Metal) to unify Flutter’s rendering pipeline across web and iOS. The architecture of SkWasm provides a blueprint for multi-threaded Skia usage in Dart, and Skia Graphite provides the performance and API unification benefits. While Flutter’s official roadmap hasn’t embraced Graphite (yet) (engine/impeller/docs/faq.md at main · flutter/engine · GitHub), the pieces are in place for an experimental implementation. By carefully modifying the engine, addressing platform-specific challenges, and following the guidance of Skia’s design, one can implement a prototype that renders Flutter via Graphite on both WebAssembly and iOS. This could yield performance improvements and valuable insights, feeding back into the Flutter community’s discussions on the future of its rendering engine.

References:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment