Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/facett

facett-map3d/.nornir/render-backend-trait.md

RenderBackend — the swappable 3D render-backend seam (LANDED)

Implements the .nornir/render-engine.md design's "Renderer/Canvas backend-seam trait = the 2-category swap expressed as one trait" (galileo pattern, our own impl). Branch agent/map3d-render-trait. The trait abstracts the 3D draw of the Map3D scene so a different Rust engine drops in without changing the map3d / map3d-flight view code. The trait is the point.

The contract (facett_map3d::backend)

RenderScene<'a>   backend-neutral frame: &Mesh3 + &WaterMesh + OrbitCamera + SunSky
                  + Viewport(rect,center,half_h) + bg + SceneFlags + clock_s
SceneFlags        sky / ssao / soft_shadows / shadow_strength / water_on / planar_reflection
RenderStats       visible_triangles · lit_fraction · ink_outside_rect(==0 oracle)
                  · raw_overshoot_px · depth_monotone · max_extent_px · backend · used_gpu
BackendCaps       name · gpu · shadows · reflective_water · post_fx

trait RenderBackend {
    fn caps(&self) -> BackendCaps;
    fn analyze(&self, &RenderScene) -> RenderStats;   // PURE, device-free — the oracle
    fn paint(&self, &RenderScene, &mut egui::Ui) -> RenderStats;  // default = CPU painter
    fn reset_cache(&self) {}                           // drop device cache on mesh swap
}

analyze is the conformance surface: every backend, handed the same scene, must report the same geometry oracles. It is device-free, so the GPU backend's analyze runs headless alongside the CPU/null ones.

The impls (all behind the one trait)

backend role draws
CpuPainterBackend impl #1a — always available, headless, the correctness reference project+cull+depth-sort+light → egui::Mesh
NullBackend headless / conformance — identical oracle, draws nothing —
WgpuVelloBackend (feat wgpu) impl #1b, our stack all 3D on wgpu (shadow→reflection→PBR mesh→water→blit); vello = 2D HUD only

How the swap works

Map3D holds backend: Box<dyn RenderBackend> (default CpuPainterBackend). with_gpu(true) swaps in WgpuVelloBackend. Every frame Map3D::view builds a RenderScene via build_scene() and calls self.backend.paint(&scene, ui) — the view code never mentions wgpu or vello. A future engine implements RenderBackend and drops in with a one-line self.backend = Box::new(NewEngine::new()).

korp connector seam (facett_map3d::korp)

FeatureLayers { buildings, streets, landuse, water } + load_layers(&[Way]) -> FeatureLayers is the single wiring point. Today it classifies OSM ways by tag (the OSM pipeline is the fallback source); TODO(korp) swaps its body for korp::connect(region) once korp/.nornir exists — the extruder, the GroundLayer z-ordering, and the render backend do NOT change.

Street / feature layering (depth-correct, no z-fight)

extrude::GroundLayer { Cover, Road } assigns each flat feature a disjoint, ordered sub-band of GROUND_LIFT: terrain(0) < Cover(landuse) < Road(streets), all sub-pixel. A road crossing a park is deterministically above the park fill — no z-fight — and buildings rise far above both.

Bench (benches/heavy_render.rs, cargo bench -p facett-map3d)

Per-part-step + rollup over a large synthetic OSM tile (MAP3D_BENCH_TOWN=n, default 64). 48×48 tile = 2402 ways / 36 870 tris: extrude 1.40 ms (1.72 Melem/s), classify 112 µs, shade 9.54 ms (3.87 Mtri/s), scissor 758 µs, backend_analyze 10.26 ms.

Tests