Rendering Pipeline
Explore the aether-renderer crate's rendering architecture, from frame scheduling and LOD policies to the wgpu GPU backend with PBR shading and shadow mapping.
Overview
The aether-renderer crate implements Aether's rendering pipeline in two layers: a policy and scheduling layer that makes frame-budget decisions independent of any GPU API, and a wgpu GPU backend that executes draw calls via Vulkan, Metal, DX12, or WebGPU. This separation lets the engine run headless (for servers or CI) while using the same scheduling logic as real-time rendering.
Frame Scheduling
The frame scheduler estimates per-frame workload and selects a quality mode that fits the target budget -- critical for VR where dropping below 90 FPS causes discomfort.
use aether_renderer::config::{FramePolicy, FrameBudget, FrameContext};
use aether_renderer::scheduler::{FrameScheduler, FrameModeInput};
let policy = FramePolicy {
budget: FrameBudget {
target_ms: 11.1, // ~90 FPS
gpu_headroom: 0.35,
cpu_headroom: 0.30,
},
..Default::default()
};
let context = FrameContext { fps: 90, draw_calls: 2000, visible_entities: 5000, gpu_ms: 8.5 };
let (mode, reason, workload) = FrameScheduler::decide_mode(&FrameModeInput {
context, base_policy: policy,
});
// mode: Ultra | High | Balanced | Safe
// reason: BudgetAvailable | BudgetPressure | Overloaded | ThermalGuard
The workload is classified into buckets (Comfortable, Elevated, Constrained, Critical) that map to quality modes.
Stereo and Foveated Rendering
VR requires two views per frame. Single-pass multiview rendering draws both eyes in one render pass, cutting draw call overhead roughly in half:
use aether_renderer::config::StereoConfig;
let stereo = StereoConfig { enabled: true, views_per_frame: 2, multiview: true, single_draw_per_eye: true };
Foveated rendering reduces pixel density at view edges where headset lenses produce lower resolution, saving 20-40% fragment shading cost:
use aether_renderer::config::{FoveationConfig, FoveationTier};
let foveation = FoveationConfig {
tier: FoveationTier::Tier2,
center_ratio: 1.0, // Full resolution at center
edge_ratio: 0.45, // 45% at edges
..Default::default()
};
The Adaptive tier uses real-time eye tracking to center the high-resolution region on gaze.
Level of Detail
LOD selection uses distance thresholds with hysteresis to prevent visible popping:
use aether_renderer::config::{LODPolicy, LODLevel, LodCurve};
let policy = LODPolicy { near: 10.0, mid: 35.0, far: 80.0, very_far: 160.0, hysteresis_ratio: 0.08 };
let new_lod = LodCurve::select(&policy, distance, current_lod);
The 8% hysteresis band means an L0 object at 10m stays L0 until it passes 10.8m, preventing rapid switching.
Shadow Mapping
Cascaded shadow maps divide the view frustum into slices at independent resolutions. When memory is constrained, the scheduler clamps resolutions to a byte budget:
use aether_renderer::config::ShadowCascadeConfig;
let shadows = ShadowCascadeConfig {
enabled: true, num_cascades: 4, base_resolution_px: 2048,
far_distance_m: 150.0, cascade_resolutions: [4096, 2048, 1024, 512],
};
let clamped = FrameScheduler::cascade_resolution_budget(&shadows, 4_000_000);
GPU Backend (wgpu)
Initialization
use aether_renderer::gpu::GpuContext;
let gpu = GpuContext::new().await?; // Returns Err if no GPU available
The backend is selectable via AETHER_GPU_BACKEND env var (vulkan, metal, dx12, gl, or auto).
Vertex Format and PBR Materials
All meshes use a 32-byte vertex: 3 floats position, 3 floats normal, 2 floats UV. Materials follow metallic-roughness PBR with albedo, metallic, roughness, and emissive parameters. The WGSL fragment shader combines texture sampling, a directional light, and cascade shadow sampling with PCF.
Render Pass Sequence
Each frame executes: (1) update uniform buffers (camera, model, material, light/shadow), (2) shadow pass with depth-only pipeline per cascade, (3) forward PBR pass with 4x MSAA, (4) MSAA resolve to surface, (5) present.
Instanced Batching
Draw calls are grouped by material via MaterialBatchKey to minimize state changes:
use aether_renderer::batching::batch_instances_by_key;
let batches = batch_instances_by_key(&instances);
for batch in &batches {
// One instanced draw call per material group
}
Progressive Mesh Streaming
Large meshes stream progressively -- a coarse base mesh refines as bandwidth allows. Requests carry priority levels (Low, Medium, High) so nearby objects refine first.
Environment Variables
| Variable | Default | Purpose |
|---|---|---|
AETHER_GPU_BACKEND | "auto" | Force wgpu backend |
AETHER_MSAA_SAMPLES | 4 | MSAA sample count |
AETHER_SHADOW_MAP_SIZE | 2048 | Base shadow map resolution |
AETHER_MAX_TEXTURE_SIZE | 4096 | Maximum texture dimension |
Key Types Reference
| Type | Module | Purpose |
|---|---|---|
FramePolicy | config | Composite rendering policy settings |
FrameScheduler | scheduler | Workload estimation and mode selection |
FrameMode | scheduler | Ultra / High / Balanced / Safe quality |
StereoConfig | config | Multiview stereo settings |
FoveationConfig | config | Foveated rendering tiers and parameters |
LODPolicy | config | Distance thresholds and hysteresis |
ShadowCascadeConfig | config | Cascade count, resolution, distance |
ClusterLightingConfig | config | Tile-based light clustering settings |
GpuContext | gpu | wgpu device, queue, and surface |
MaterialBatchKey | batching | Material-based draw call grouping |