Keyboard shortcuts

Press or to navigate between chapters

Press ? to show this help

Press Esc to hide this help

Rendering Pipeline

Rinch uses a multi-stage rendering pipeline that transforms component code into pixels on the desktop backend. Two rendering backends are available: GPU (Vello/wgpu) and software (tiny-skia/softbuffer). The web backend uses browser-native DOM instead (see note at the end).

Pipeline Stages (Desktop)

┌───────────────────────────────────────────────────────────────┐
│                   1. Component Input                            │
│  #[component] functions + rsx! macro generate DOM construction │
│  code via __scope.create_element(), create_text(),             │
│  create_effect()                                               │
└───────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌───────────────────────────────────────────────────────────────┐
│                   2. DOM Construction                           │
│  DomDocument creates nodes programmatically via RenderScope   │
│  (RinchDocument uses Taffy + Parley on desktop)               │
└───────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌───────────────────────────────────────────────────────────────┐
│                   3. Style Resolution                           │
│  Stylo (Firefox's CSS engine) computes styles for each node   │
└───────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌───────────────────────────────────────────────────────────────┐
│                   4. Layout                                     │
│  Taffy computes the position and size of each element         │
└───────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌───────────────────────────────────────────────────────────────┐
│                   5. Painting (via Painter trait)                │
│  paint_document() walks the DOM tree and emits drawing        │
│  commands through the abstract Painter interface               │
└───────────────────────────────────────────────────────────────┘
                              │
                    ┌─────────┴─────────┐
                    ▼                   ▼
┌──────────────────────────┐  ┌──────────────────────────┐
│  6a. GPU (Vello)         │  │  6b. Software (tiny-skia) │
│  Scene graph → wgpu →    │  │  Rasterize to RGBA pixmap │
│  GPU compositing         │  │  → softbuffer → display   │
└──────────────────────────┘  └──────────────────────────┘
                    │                   │
                    └─────────┬─────────┘
                              ▼
                          Display

Input Stage

User code defines components using the #[component] macro and rsx! macro:

#![allow(unused)]
fn main() {
#[component]
fn counter() -> NodeHandle {
    let count = Signal::new(0);
    rsx! {
        div {
            p { "Count: " {|| count.get().to_string()} }
            button { onclick: move || count.update(|n| *n += 1), "+" }
        }
    }
}
}

The #[component] macro injects a __scope: &mut RenderScope parameter. The rsx! macro generates calls to __scope.create_element(), __scope.create_text(), and __scope.create_effect() to build the DOM tree programmatically. No HTML strings are generated or parsed at runtime.

Key Technologies

rinch-dom

The custom DOM and layout engine built specifically for Rinch:

  • rinch-dom - DOM implementation with Taffy layout, Parley text shaping, and a Painter trait for backend-agnostic rendering
  • VelloPainter - GPU backend: records drawing commands into a Vello scene graph
  • TinySkiaPainter - Software backend: rasterizes directly to an RGBA pixel buffer

Stylo

Mozilla’s CSS engine (from Firefox) provides:

  • Full CSS specification support
  • Efficient style computation
  • Media query handling
  • CSS custom properties

Taffy

A flexbox/grid layout engine that computes:

  • Element positions (x, y)
  • Element sizes (width, height)
  • Flexbox alignment and distribution
  • CSS Grid support

Painter Trait

All rendering goes through the Painter trait, which abstracts over both backends:

#![allow(unused)]
fn main() {
pub trait Painter {
    fn fill(&mut self, fill: Fill, transform: Affine, brush: &Brush, shape: &PaintShape);
    fn stroke(&mut self, stroke: &Stroke, transform: Affine, brush: &Brush, shape: &PaintShape);
    fn draw_glyphs(&mut self, font: &FontData, font_size: f32, ...);
    fn draw_image(&mut self, image: &PaintImage, transform: Affine);
    fn push_clip(&mut self, fill: Fill, transform: Affine, shape: &PaintShape);
    fn push_layer(&mut self, blend: BlendMode, opacity: f32, ...);
    fn pop_layer(&mut self);
    // ...
}
}

Application code never interacts with the Painter directly — the backend is selected at compile time via Cargo features.

Vello (GPU Backend)

A GPU-accelerated 2D graphics library (enabled with features = ["gpu"]):

  • Scene graph-based rendering
  • Efficient batching
  • High-quality anti-aliasing
  • Path rendering (beziers, fills, strokes)
  • Text rendering with proper shaping
  • Requires wgpu (Vulkan, Metal, DX12, or WebGPU)

tiny-skia (Software Backend)

A CPU-based rasterizer (the default when gpu is not enabled):

  • Direct pixel rendering to an RGBA buffer
  • Presented via softbuffer (no GPU required)
  • Dirty region caching — only changed areas are repainted
  • Subtree pruning — nodes outside the dirty region are skipped
  • Works in headless environments, CI, containers, SSH sessions

Rendering Backends

Choosing a Backend

Set it in your Cargo.toml:

# GPU mode (recommended for most apps):
rinch = { workspace = true, features = ["desktop", "gpu"] }

# Software mode (default — no GPU required):
rinch = { workspace = true, features = ["desktop"] }

GPU Rendering Flow

#![allow(unused)]
fn main() {
// Simplified GPU rendering flow
fn paint_gpu(&mut self) {
    let scene = self.app.build_scene(scale, size);
    self.renderer.render_to_surface(&scene, &params, &surface);
}
}

Software Rendering Flow

#![allow(unused)]
fn main() {
// Simplified software rendering flow
fn paint_software(&mut self) {
    let (pixels, w, h) = self.app.build_pixels(scale, size, &layers);
    // pixels are blitted to the window via softbuffer
}
}

The software renderer includes dirty region caching: when only a small part of the UI changes (e.g., cursor blink, hover feedback), only the affected rectangular region is cleared and repainted. Nodes outside the dirty region are skipped entirely during the paint traversal.

Incremental Updates

When content changes, the pipeline can skip unchanged stages:

  1. Style cache - Styles are cached per element selector
  2. Layout cache - Layout is only recomputed for affected subtrees
  3. Scene diffing - Only changed primitives are re-rendered

Performance Characteristics

StageComplexityCaching
DOM BuildO(n)Incremental (surgical updates)
Style ResolveO(n x rules)Selector cache
LayoutO(n)Subtree cache
PaintO(visible)Dirty region caching (software)
GPU RenderO(primitives)GPU buffers (GPU mode)

Web Backend

The pipeline above is desktop-only. The web backend (ui-zoo-web) takes a completely different path:

#[component] + rsx! → DOM construction code → WebDocument (web_sys) → Browser-native DOM

On the web, WebDocument implements DomDocument using web_sys to create real browser DOM elements. The browser handles style resolution, layout, painting, and compositing natively. No Taffy, Parley, Stylo, Vello, or wgpu are needed for the web backend, resulting in a much smaller WASM binary.

Optimizations

Current and planned improvements to the rendering pipeline:

  • Dirty region caching (software) - Only repaint the rectangular area covering changed nodes
  • Subtree pruning (software) - Skip paint traversal for nodes outside the dirty region
  • Sensitivity flags - Hover/active/focus only trigger repaints for nodes with matching CSS selectors
  • Batched redraws - Multiple state changes are batched into a single repaint via AboutToWait
  • Layer compositing - GPU layers for transformed content (planned)
  • Text caching - Glyph atlas for repeated text (planned)
  • Viewport culling - Skip off-screen content (planned)