dr2d - 2d Data Renderer
GPU-accelerated 2D data renderer built in Rust on wgpu.
dr2d is a rendering primitive. It handles vertices, viewports, data, and GPU. Meaning belongs to the layer above — dr2d renders triangles, it does not interpret them.
Architecture
┌─────────────────────────────────────────────────────┐
│ dr2d │
│ │
│ ┌───────────┐ ┌───────────┐ ┌──────────────┐ │
│ │ Renderer │ │ Viewport │ │ InputQueue │ │
│ │ ├ SDF │ │ pan/zoom │ │ events │ │
│ │ ├ Tess │ │ transform │ │ drain() │ │
│ │ └ Frame │ └───────────┘ └──────────────┘ │
│ └───────────┘ │
│ │
│ ┌───────────┐ ┌───────────────────────────────┐ │
│ │ Scene │ │ Data │ │
│ │ shapes │ │ ParquetLoader CoordMapper │ │
│ │ viewpoints│ └───────────────────────────────┘ │
│ └───────────┘ │
│ │
│ ┌─────────────────┐ ┌──────────────────────┐ │
│ │ Text (feature) │ │ Headless (feature) │ │
│ │ GlyphAtlas │ │ HeadlessRenderer │ │
│ └─────────────────┘ └──────────────────────┘ │
└─────────────────────────────────────────────────────┘
Rendering Pipeline
begin_frame(viewport)
│
├── draw_sdf(shape, instances) ← SDF pipeline (resolution-independent)
├── draw_instanced(mesh, instances) ← shared mesh + per-instance transforms
├── draw_triangles(vertices) ← flat triangle list
│
└── finish() ← submit + present
The renderer maintains two GPU pipelines:
- SDF pipeline — expands a unit quad per instance, evaluates signed distance functions in the fragment shader for anti-aliased, resolution-independent shapes
- Tessellation pipeline — renders pre-tessellated triangle meshes with optional instancing, used for polygons, rectangles, and text glyphs
Both pipelines share a single uniform bind group containing the viewport transform matrix.
Features
- SDF rendering — resolution-independent shapes (circle, rounded rect, ring, diamond, line cap) with anti-aliased edges
- Instanced rendering —
draw_sdf,draw_instanced,draw_triangles - GPU rendering via wgpu (Vulkan, Metal, DX12, WebGPU)
- Parquet/Arrow data loading with streaming row-group reads
- Viewport with pan, zoom, fit-to-data, window-to-scene coordinate conversion
- Mouse and keyboard input with scene coordinate mapping
- Lyon-based polygon tessellation with caching
- Text rendering (feature:
text) — embedded glyph atlas, ASCII - Headless rendering (feature:
headless) — render to RGBA pixel buffer
Quick Start
use ;
let renderer = new.await?;
let viewport = new;
let mut frame = renderer.begin_frame?;
frame.draw_sdf;
frame.finish;
API Reference
Renderer
Creates and manages the GPU context, pipelines, and frame lifecycle.
// Create renderer attached to a winit window
let renderer = new.await?;
// Resize the rendering surface
renderer.resize;
// Frame lifecycle
let mut frame = renderer.begin_frame?;
// ... draw calls ...
frame.finish;
// or: renderer.end_frame(frame)?;
// Convenience: render all scene shapes in one call
renderer.render?;
// Query window dimensions
let = renderer.window_size;
FrameEncoder
Returned by begin_frame(). Records draw calls into a GPU command encoder.
| Method | Description |
|---|---|
draw_sdf(shape, &[SdfInstance]) |
Draw SDF shapes (resolution-independent, anti-aliased) |
draw_instanced(&[Vertex], &[InstanceData]) |
Draw shared mesh with per-instance transforms |
draw_triangles(&[Vertex]) |
Draw a flat triangle list |
finish() |
Submit commands and present the frame |
All draw calls skip silently when given empty slices.
SDF Shapes
Built-in signed distance function shapes evaluated per-fragment on the GPU:
| Variant | Description | param usage |
|---|---|---|
Circle |
Unit circle | unused |
RoundedRect |
Rounded rectangle | corner radius |
Ring |
Donut / annulus | ring thickness |
Diamond |
Rotated square | unused |
LineCap |
Capsule / line segment with round ends | unused |
SdfInstance
Per-instance data for SDF rendering (48 bytes, GPU-aligned):
| Field | Type | Description |
|---|---|---|
position |
[f32; 2] |
Center in scene coordinates |
size |
[f32; 2] |
Half-extents of bounding quad |
color |
[f32; 4] |
RGBA color |
shape_type |
u32 |
Cast from SdfShape enum |
param |
f32 |
Shape-specific parameter |
Vertex / InstanceData
| Type | Fields | Description |
|---|---|---|
Vertex |
position: [f32; 2], color: [f32; 4] |
GPU vertex for triangle rendering |
InstanceData |
position: [f32; 2], size: [f32; 2], color: [f32; 4] |
Per-instance offset, scale, color |
Viewport
2D translation (pan) and uniform scale (zoom). Transforms scene coordinates to NDC for GPU rendering.
let mut vp = new; // pan=(0,0), zoom=1.0
vp.set_pan;
vp.set_zoom?; // returns Err if zoom <= 0
// Build 3×vec4 transform matrix for GPU uniform buffer
let matrix: = vp.transform_matrix;
// Convert window pixel coordinates to scene coordinates
let = vp.window_to_scene;
Fields: pan_x: f32, pan_y: f32, zoom: f32 (all public).
InputQueue / InputEvent
Buffers input events between frames. Events carry both screen and scene coordinates.
let mut queue = new;
queue.push;
let events: = queue.drain;
InputEvent variants:
| Variant | Key fields |
|---|---|
MouseButton |
button, state, screen_x/y, scene_x/y |
MouseMove |
screen_x/y, scene_x/y |
KeyboardKey |
key: KeyCode, state: ElementState |
Scroll |
delta_x, delta_y |
ModifiersChanged |
alt, ctrl, shift, super_key |
Use convert_window_event() to translate winit WindowEvent into InputEvent.
Scene
Container for shapes and named viewpoints. Shapes are sorted by layer for draw ordering.
let mut scene = new;
let id = scene.add_shape?;
scene.update_shape?;
scene.remove_shape?;
let shape_ref = scene.get_shape;
let sorted: & = scene.shapes_sorted;
Shape geometry types:
| Geometry | Fields |
|---|---|
Rectangle |
x, y, width, height |
Polygon |
vertices: Vec<[f32; 2]> (3–1024 vertices) |
Triangle |
vertices: [[f32; 2]; 3] |
Shape properties: color: [f32; 3], opacity: f32, layer: i32,
border_color: Option<[f32; 3]>, border_width: f32.
Viewpoints:
scene.register_viewpoint;
let vp = scene.activate_viewpoint?;
scene.remove_viewpoint;
Data Module
ParquetLoader
Reads Parquet files into Arrow RecordBatch or extracts typed column pairs. Supports streaming row-group reads. Numeric columns (f32, f64, i32, i64) are cast to f32 automatically.
// Stream columns directly from file
let pair = load_columns?;
// Load full RecordBatch, then extract
let batch = load?;
let pair = extract_columns?;
ColumnPair contains x: Vec<f32> and y: Vec<f32>.
CoordinateMapper
Linear mapping from data value ranges to scene coordinate ranges.
let mapper = from_column_pairs;
let = mapper.map_point;
let points: = mapper.map_all;
Default scene range: 0.0..1000.0 on both axes.
Text (feature: text)
Embedded glyph atlas with geometric outlines for ASCII characters (A-Z, a-z, 0-9, common punctuation). Tessellated into triangle vertices via lyon.
let atlas = new;
let vertices: = atlas.tessellate_string;
Unknown characters are skipped but the cursor still advances.
Headless (feature: headless)
Render to an in-memory RGBA pixel buffer without a window. Creates a wgpu device without a surface.
let mut hr = new.await?;
let pixels: = hr.render_to_image.await?;
// pixels.len() == 800 * 600 * 4
Returns HeadlessError::InvalidDimensions if width or height is zero.
PNG encoding is left to downstream consumers.
Keyboard Shortcuts
Built-in interaction processor translates input events into viewport mutations.
| Key | Action |
|---|---|
| Arrow keys | Pan viewport |
| Scroll wheel | Zoom in/out |
+ / - |
Zoom in / zoom out |
| Left click + drag | Pan viewport |
F |
Fit viewport to data |
Ctrl+Shift+H |
Fit viewport to data |
Home |
Reset to origin (pan=0, zoom=1.0) |
0 |
Reset to initial viewport state |
1–9 |
Activate named viewpoints (sorted alphabetically) |
F11 |
Toggle fullscreen |
Alt+Enter |
Toggle fullscreen |
Ctrl+Super+F |
Toggle fullscreen |
Escape |
Exit fullscreen |
Ctrl+S |
Save request (polled via take_save_request()) |
Using dr2d Downstream
Add dr2d as a dependency in your Cargo.toml:
[]
= "0.0.1-alpha.1"
# Optional features
# dr2d = { version = "0.0.1-alpha.1", features = ["text", "headless"] }
Minimal windowed application:
use Arc;
use ;
async
For scene-based rendering with shapes and tessellation:
use ;
use ;
let mut scene = new;
scene.add_shape.unwrap;
renderer.render.unwrap;
Feature Flags
| Feature | Default | Description |
|---|---|---|
text |
no | GlyphAtlas with embedded ASCII glyph outlines and lyon tessellation |
headless |
no | HeadlessRenderer for offscreen rendering to RGBA pixel buffers |
Examples
CLI
The dr2d-cli crate loads TOML scene files and renders them in a window.
Philosophy
See PHILOSOPHY.md.
License
Apache 2.0 — Copyright 2026 Krishnamoorthy Sankaran. See LICENSE.
All source files carry the following header convention:
// SPDX-License-Identifier: Apache-2.0