← /research

GPUI: Zed's 120fps GPU-Accelerated UI

Processing · Reading Notes Created Jan 4, 2025

Source

Leveraging Rust and the GPU to render user interfaces at 120 FPS — Zed Industries (Article)
View source →
Project: web-graphics-research
graphicsrustgpuuiperformance

GPUI is Zed’s in-house GPU-accelerated UI framework. It renders at 120fps using techniques borrowed from game development, not traditional GUI frameworks.

Core Architecture

┌─────────────────────────────────────────────────┐
│                  Application                     │
│         (Elements, Views, Components)            │
└─────────────────────┬───────────────────────────┘

┌─────────────────────┴───────────────────────────┐
│               GPUI Framework                     │
│  • Immediate-mode API with retained optimization│
│  • Entity-based state management                 │
│  • Declarative element composition               │
└─────────────────────┬───────────────────────────┘

┌─────────────────────┴───────────────────────────┐
│            Three-Phase Renderer                  │
│  1. Prepaint — compute layout                    │
│  2. Paint — build scene graph                    │
│  3. Present — render to GPU                      │
└─────────────────────┬───────────────────────────┘

┌─────────────────────┴───────────────────────────┐
│               Graphics Backend                   │
│  • macOS: Metal (MSL shaders)                   │
│  • Linux: Blade (Vulkan)                        │
│  • Windows: Blade (Vulkan/DX12)                 │
└─────────────────────────────────────────────────┘

The Videogame Approach

Zed is rendered “like a videogame” — every frame is a fresh render pass with no widget tree diffing. This enables:

  • Camera manipulation (3D view exploding for debugging)
  • Smooth 120fps animations
  • No accumulated layout debt

Traditional UI frameworks diff a tree of widgets. GPUI just draws everything fresh each frame, like a game.

Signed Distance Functions (SDFs)

GPUI draws primitives using SDFs — mathematical functions that return distance to a shape’s boundary.

// SDF for a rounded rectangle
fn sdf_rounded_rect(p: vec2<f32>, half_size: vec2<f32>, radius: f32) -> f32 {
    let q = abs(p) - half_size + radius;
    return length(max(q, vec2<f32>(0.0))) + min(max(q.x, q.y), 0.0) - radius;
}

// Fragment shader usage
@fragment
fn fs_main(@location(0) local_pos: vec2<f32>) -> @location(0) vec4<f32> {
    let d = sdf_rounded_rect(local_pos, rect_size, corner_radius);

    // Smooth anti-aliasing at the edge
    let alpha = 1.0 - smoothstep(0.0, 1.5, d);

    return vec4<f32>(color.rgb, color.a * alpha);
}

Why SDFs:

  • Anti-aliasing is trivial (smoothstep at boundary)
  • No texture sampling for basic shapes
  • Resolution-independent
  • Composable (union, intersection, difference)

120fps Performance

Hitting 120fps requires <8.33ms frame times. Key techniques:

1. GPU-Side Layout Hints

Heavy layout work stays on GPU. CPU just provides bounding boxes.

2. Metal Transaction Coordination

// Ensure frame doesn't tear with system UI
layer.presents_with_transaction = true;
command_buffer.wait_until_completed();

This prevents partial frame presentation but adds latency. Zed accepts the tradeoff for visual coherence.

3. No Blocking on Main Thread

Any blocking call drops frames:

// BAD: 10ms sleep = frame drop
sleep(Duration::from_millis(10));

// GOOD: Async work offloaded
spawn(async move {
    do_slow_work().await;
});

4. Batched Primitives

All rectangles, glyphs, and shadows batched into single draw calls:

// Scene collects primitives
scene.add_rect(bounds, color);
scene.add_text(position, glyphs);
scene.add_shadow(bounds, radius, color);

// One draw call per primitive type
gpu.draw_rects(scene.rects);
gpu.draw_glyphs(scene.glyphs);

Blade Renderer

Blade is Zed’s low-level graphics abstraction by Dzmitry Malyshau (ex-Mozilla, gfx-rs maintainer):

FeatureImplementation
macOSMetal via metal-rs
LinuxVulkan via ash
WindowsDX12/Vulkan
ShadersWGSL → MSL/SPIR-V

Blade provides a minimal, Vulkan-inspired API that compiles to native backends. ~4000 lines of code enabled Linux support.

Hybrid Rendering Model

GPUI combines immediate and retained modes:

Immediate-mode characteristics:

  • No persistent widget tree
  • Function calls build UI each frame
  • State lives in application, not framework

Retained-mode optimizations:

  • Entity system caches expensive computations
  • Layout cached when inputs unchanged
  • Texture atlases for glyphs
// Immediate-mode API
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Element {
    div()
        .bg(cx.theme().background)
        .child(
            text(self.label.clone())
                .color(cx.theme().text)
        )
}

Performance Comparison

FrameworkTypical FPSApproach
Electron30-60DOM diffing
Qt60Retained widget tree
Dear ImGui60-120Pure immediate mode
GPUI120Hybrid + GPU

GPUI’s advantage: It doesn’t pay the DOM/widget tree tax, but caches enough to avoid redundant work.

Lessons for Web

GPUI runs native, not in browser. But concepts transfer:

  1. SDF rendering — WebGPU compute can do this
  2. Immediate-mode UI — React is already pseudo-immediate
  3. Batched draw calls — Canvas/WebGPU naturally batch
  4. 120fps target — Requires matching display refresh, harder in browser

The challenge: Browsers add layers (compositor, V8, etc.) that GPUI bypasses.

Sources:

Related: vello gpu vector graphics, building figma today, rust wasm graphics