Rendering engines
When you pick a desktop or dense client stack, you are not only choosing a language. You are choosing a rendering pipeline: how vectors become pixels, how much work happens on the CPU before the GPU sees a frame, and whether a long-lived document can grow without turning every scroll into layout reflow.
This page is a working map for that decision. It complements GUI applications and UI-heavy web applications (browser-heavy products on the Dev-Centr docs playbook). Essay (inward voice): After Skia: where rendering is headed.
Why rendering choice shows up late—and hurts early
Teams often default to Electron or a web shell because the HTML/CSS ecosystem is familiar and shippable. That works until the UI holds a large, streaming document: chat with thousands of messages, a repo map with live syntax tokens, a mail client that never unloads its thread list.
The failure mode is structural, not “JavaScript is slow”:
-
DOM tree explosion — each message, code block, and token becomes live DOM; streaming tokens trigger style recalc and reflow across the tree.
-
Heap + IPC pressure — the main process holds conversation state; the renderer’s V8 heap crosses ~1.5–2 GB; major GC pauses freeze input; OOM kills the tab.
-
Process overhead — Chromium ships multiple processes (main, renderer, GPU, utilities) with duplicated runtime state even at idle.
Native and retained-mode UI stacks avoid the DOM entirely. They still need virtualization and off-heap storage for gigabyte-scale history—but the baseline memory and frame budget are different.
Framework comparison (performance + visual control)
The chart is qualitative: exact numbers vary by build flags, platform, and what you mount on first paint.
Visual expressiveness / customizability
▲
│
Flutter Desktop │ Qt Quick / QML
│
Avalonia UI │
│
│ GPUI (Zed)
│ Slint
│
───────────────────────────────┼───────────────────────────────►
Standard performance │ Extreme low-latency /
│ raw data throughput
Avalonia UI (.NET)
Best for: enterprise richness, deep templating, built-in UI virtualization.
-
Pipeline: Skia → DirectX / Vulkan / Metal / OpenGL; retained visual tree with control templates.
-
Large data:
VirtualizingStackPanel/ItemsControl;Span<T>, memory-mapped I/O without JSON IPC boundaries. -
Idle RAM (typical): ~40–80 MB baseline for a modest desktop shell.
GPUI (Rust, Zed)
Best for: text throughput, sub-millisecond input, zero GC.
-
Pipeline: hybrid immediate/retained model; draw lists and text atlases piped to GPU shaders.
-
Large data: piece tables / ropes; memory-mapped streams without duplicating buffers.
-
Idle RAM (typical): ~20–40 MB.
Qt 6 / QML (C++)
Best for: industrial GPU scene graphs, mature tooling, shader-level effects.
-
Pipeline: Qt Quick scene graph on RHI (Vulkan, D3D12, Metal).
-
Large data:
QAbstractItemModelfeeds QML on demand; C++ backend shares address space with the renderer. -
Idle RAM (typical): ~30–60 MB.
Flutter Desktop (Dart)
Best for: cross-platform visual consistency, fast iteration.
-
Pipeline: Impeller (preferred) or Skia; bypasses native widgets.
-
Large data:
ListView.builder/ isolates for heavy parsing; Dart GC still matters for multi-GB in-memory graphs. -
Idle RAM (typical): ~60–110 MB.
Electron (Chromium)
Best for: web ecosystem reach, fastest path to a shippable cross-platform shell.
-
Pipeline: Blink + compositor + Skia; DOM/CSS layout on every meaningful update.
-
Large data: poor fit for unbounded in-document growth without aggressive virtualization and architectural discipline outside the renderer.
-
Idle RAM (typical): ~200–400+ MB; cold start often 800 ms–2 s+.
Comparison table
| Framework | Rendering backend | Baseline idle RAM | Large data / streaming | Customizability |
|---|---|---|---|---|
GPUI (Rust) |
Custom GPU shaders / Metal / Vulkan / DX12 |
~20–40 MB |
Maximum (ropes, zero-copy streams) |
High (code-first, flex layout) |
Qt 6 / QML |
Scene graph / RHI |
~30–60 MB |
Maximum (C++ models, mmap) |
Extreme (QML + custom shaders) |
Avalonia UI |
Skia (DX / Vulkan / Metal / GL) |
~40–80 MB |
Very high (virtualization, spans) |
Extreme (retemplating) |
Flutter Desktop |
Impeller / Skia |
~60–110 MB |
High (virtual lists, isolates) |
Extreme (canvas-level) |
Electron |
Chromium / Blink / DOM |
~200–400+ MB |
Poor without strict architecture |
Extreme (HTML/CSS ecosystem) |
Architectural requirements for multi-gigabyte client documents
Regardless of framework:
-
Viewport windowing — never mount more items than visible plus a small overdraw buffer (e.g. 20–30 chat rows).
-
Off-heap / mapped storage — append-only log, SQLite, DuckDB, or memory-mapped files; do not keep full history as live heap objects in the UI process.
Electron can survive large histories only when both patterns are enforced and the DOM surface stays small. Native stacks make the same patterns easier but do not remove the obligation.
Is Skia being replaced?
Not overnight. Skia remains the 2D backbone for Chrome, Android, Flutter (legacy paths), and Avalonia. Google’s Graphite backend is a ground-up rewrite of Skia’s GPU layer—modern APIs (Vulkan, D3D12, Metal, Dawn/WebGPU), batched command buffers, pre-baked pipeline state objects instead of dynamic runtime shader generation.
Impeller is Flutter’s precompiled-shader renderer. It is tightly coupled to Flutter’s display-list architecture—not a drop-in Skia replacement for arbitrary apps. Impeller targets mobile first but has desktop paths (Vulkan on Windows/Linux among them).
The more interesting generation shift is compute-first vector rendering:
Generation 1 (CPU raster): Cairo, GDI+, software Skia
Generation 2 (GPU tessellation): Skia (Ganesh/Graphite), Direct2D, Impeller
Generation 3 (GPU compute-first): Vello, Rive Renderer
Vello (Linebender / wgpu)
Path filling, Bézier flattening, and binning run largely in GPU compute shaders via parallel scan algorithms—not classic CPU tessellation + draw-call storms.
Built on wgpu → D3D12 / Vulkan on Windows, Vulkan on Linux, Metal, WebGPU.
Apache-2.0 / MIT (dual).
Skia Graphite
Google’s internal successor layer to legacy Ganesh; landing in Chrome and Android stacks over time. BSD 3-Clause.
Successor comparison
| Engine | Architecture | Pipeline model | Primary platforms |
|---|---|---|---|
Skia (Ganesh) |
CPU tessellation + draw calls |
Legacy GPU (GL / Vulkan / D3D11) |
Universal |
Skia (Graphite) |
Batched command buffers + precompiled PSOs |
Modern GPU (Vulkan / D3D12 / Metal) |
Chrome, Android, desktop embedders |
Impeller |
Precompiled shaders, multi-threaded dispatch |
Modern GPU |
Flutter-centric |
Vello |
Parallel prefix-sum / GPU compute |
Compute shaders ( |
Windows, Linux, macOS, WebGPU |
Rive Renderer |
PLS / overdraw-optimized vector raster |
Modern GPU |
Cross-platform embed |
Blend2D |
JIT SIMD CPU raster |
Software |
CPU-only / headless |
Open source and licenses
All engines listed here are open source under permissive licenses:
| Project | Language | License | Governance |
|---|---|---|---|
Vello |
Rust |
Apache-2.0 / MIT |
Linebender community |
Skia Graphite |
C++ |
BSD 3-Clause |
Google / Skia |
Impeller |
C++ |
BSD 3-Clause |
Flutter engine (Google) |
Rive Renderer |
C++ |
MIT |
Rive Inc. |
Blend2D |
C++ |
Zlib |
Independent OSS |
Practical guidance
-
Ship fast, bounded UI: Electron/Tauri + strict virtualization is valid when time-to-market and web hiring dominate.
-
Dense, long-lived client: prefer retained-mode native stacks (Avalonia, Qt, Flutter, GPUI) and treat rendering engine generation (Skia vs Graphite vs compute-first) as a secondary bet—framework integration matters more near term.
-
Watch Vello and Graphite when you need vector-heavy, animation-rich UIs at high frame rates without CPU tessellation bottlenecks—especially on Windows and Linux where Impeller is Flutter-specific.
-
Pair with Desktop auxiliary template for Help, About, updates, and diagnostics regardless of renderer.