Rendering Pipeline
Nimbus follows an Extract-Prepare-Render pattern, separating CPU geometry work from GPU resource management and draw submission.
Three-Phase Architecture
graph LR
A[Extract] -->|vertices + indices| B[Prepare]
B -->|GPU buffers + bind groups| C[Render]
C -->|draw commands| D[GPU]
Extract
MapRenderer::extract() reads loaded tile data and produces CPU-friendly ExtractedRenderData — vertex arrays and index arrays for each geometry type (fill, line, icon, text).
- Iterates style layers (not MVT layers) for correct z-ordering
- Tessellates polygons using earcutr (results cached in
TessellationCache) - Generates line quads with normals in Mercator space
- Emits icon quads (4 vertices + 6 indices per point, screen-aligned pixel offsets)
- Shapes and wraps text, emitting glyph quads
Geometry emission functions (emit_polygon_geometry, emit_line_geometry) are free functions to avoid &mut self borrow conflicts with the tessellation cache and resolved layers.
Prepare
MapRenderer::prepare() uploads the extracted data to GPU buffers and creates bind groups.
- GPU buffer reuse:
try_reuse_gpu_buffers()checks if existing buffers have enough capacity. If so, data is written viaqueue.write_buffer(no GPU allocation). Otherwise, new buffers are created withmapped_at_creation: trueand 1.5x overallocation. mapped_at_creationon Metal means direct writes into GPU-visible memory (zero staging copies).- Per-label opacity is stored in a small storage buffer (2-4 KB), decoupled from vertex data.
Render
MapRenderer::render() submits draw commands in a single render pass with LoadOp::Clear (background color from the style).
Draw order within the pass:
- Fill geometry (polygons)
- Line geometry
- Raster tiles (if any)
- Icon sprites
- Text glyphs
Module Map
graph LR
subgraph entry ["Entry Points"]
main["main.rs"]
web["web.rs"]
ffi["ffi.rs"]
offscreen["offscreen.rs"]
end
subgraph renderer ["renderer/"]
extract["extract.rs"]
prepare["prepare.rs"]
symbols["symbols.rs"]
subgraph geometry ["geometry/"]
polygon["polygon.rs"]
line["line.rs"]
icons["icons.rs"]
text_geom["text.rs"]
coords["coords.rs"]
end
end
subgraph style_system ["Style System"]
style_eval["style_eval/"]
expressions["expressions/"]
style_mod["style.rs"]
subgraph styler ["styler/"]
styler_mod["mod.rs"]
poi["poi.rs"]
tree["tree.rs"]
end
end
subgraph tiles ["Tile Pipeline"]
tile_loader["tile_loader/"]
mvt["mvt.rs"]
tile_coords["tile_coords.rs"]
raster["raster.rs"]
raster_loader["raster_loader/"]
end
subgraph support ["Support"]
camera["camera_transform.rs"]
collision["collision.rs"]
sprite["sprite.rs"]
glyph["glyph_atlas.rs"]
text_shaper["text_shaper.rs"]
tess_cache["tessellation_cache.rs"]
color["color.rs"]
end
entry --> renderer
renderer --> style_system
renderer --> tiles
renderer --> support
style_eval --> expressions
Dirty Flags and Debouncing
Camera pans/zooms only update the uniform buffer and tile data offsets — they never touch vertex data. Two dirty flags control what gets rebuilt:
| Flag | Set when | Triggers |
|---|---|---|
geometry_dirty |
Tile count changes (new tile arrives or evicted) | Full extract() |
camera_dirty |
Camera moves (pan, zoom, rotation) | Uniform buffer + update_tile_data() |
Extract Debounce
During active user input (zoom/pan), extract is deferred for 100ms to avoid wasted CPU work on rapidly changing tile sets. New tile arrivals always trigger an immediate extract so tiles appear without delay.
Budgeted Incremental Finalize
Large extract operations are split into 4 phases to avoid frame drops:
- Extract — emit geometry into staging buffers
- Begin finalize — start merging into
ExtractedState - Merge — complete the merge (old data stays intact for
update_tile_data()) - Swap — atomic
self.extracted = fs.staging(single field move, no partial state)
ExtractedState groups all 12 extracted data fields into one struct so the finalize swap is a single move — eliminating the "forgot to swap field N" class of bugs.
Shaders
Three WGSL shader files in shaders/:
| File | Lines | Purpose |
|---|---|---|
map.wgsl |
179 | Fill + line vertex/fragment with globe projection |
icon.wgsl |
232 | Icon/glyph SDF + RGBA dual-mode rendering |
raster.wgsl |
127 | Raster tile textured quad rendering |
The vertex shader reconstructs world positions from tile-relative Mercator offsets plus per-tile storage buffer data, avoiding f32 precision issues at high zoom levels. See Coordinate Systems for details.