facett_core/render/mod.rs
1//! **`facett-core::render`** — the L0 shared render kernel both map skins and
2//! `facett-graphview` draw through (the CONS-CORE seam).
3//!
4//! Phase A landed the pure additions ([`camera`], [`layer`], the moved scissor, the
5//! GPU scaffold). **Phase B** adds the net-new L0 SDF kernel + the backend-swap
6//! seam:
7//!
8//! - [`prim`] — the SDF instance vocabulary
9//! ([`QuadInstance`](prim::QuadInstance)/[`LineInstance`](prim::LineInstance) +
10//! the `Circle`/`Ring`/`Marker` constructors), `bytemuck`-able for the GPU path.
11//! - [`cpu`] — the CPU lane: the SDF coverage math ([`cpu::sdf`]) + [`CpuCanvas`]
12//! rasterizing onto a `vello_cpu` pixmap, plus the moved rect scissor.
13//! - [`gpu`] (feature `wgpu`) — the [`GpuSdfRenderer`](gpu::sdf_pipeline::GpuSdfRenderer):
14//! instanced quads with a fragment-shader SDF (circle/ring/marker) + thick AA
15//! instanced lines, on the Phase-A install lifecycle + bytemuck plumbing.
16//! - [`backend`] — the [`decide`](backend::decide)`(probe) -> `[`Backend`] policy
17//! (lifted from graphview).
18//! - [`Canvas`] / [`Renderer`] — the backend-swap seam traits. Both
19//! [`CpuRenderer`](cpu::CpuRenderer) and (feature `wgpu`)
20//! [`GpuSdfRenderer`](gpu::sdf_pipeline::GpuSdfRenderer) satisfy them.
21
22pub mod backend;
23pub mod camera;
24pub mod cpu;
25pub mod layer;
26pub mod prim;
27
28#[cfg(feature = "wgpu")]
29pub mod gpu;
30
31pub use backend::{decide, Backend, GpuProbe};
32pub use camera::{Camera, InputFeel};
33pub use layer::{Layer, LayerKind, LayerStack};
34pub use prim::{CircleInstance, LineInstance, MarkerInstance, QuadInstance, RingInstance};
35
36/// A rendered frame: straight (un-premultiplied) RGBA8, row-major, `width × height`.
37/// The CPU lane composites into this; the GPU lane reads it back for a parity test.
38/// Mirrors graphview's `Rendered` so a skin can move over without a shape change.
39#[derive(Clone, Debug, PartialEq)]
40pub struct Frame {
41 pub width: u32,
42 pub height: u32,
43 /// `width * height * 4` bytes, `[r, g, b, a]` per pixel, **not** premultiplied.
44 pub rgba: Vec<u8>,
45}
46
47impl Frame {
48 /// Count of pixels whose alpha is non-zero — the "did it actually draw"
49 /// oracle the raster emit checkpoint reports (`raster.lit_px > 0`).
50 pub fn lit_px(&self) -> usize {
51 self.rgba.chunks_exact(4).filter(|p| p[3] != 0).count()
52 }
53}
54
55/// **The drawing surface** — the galileo-style backend-swap seam. A host pushes
56/// SDF instance batches; the concrete canvas (CPU pixmap or wgpu callback) decides
57/// how they become pixels. Domain-agnostic: a map point, a graph node, and a POI
58/// are all just quads; a road and an edge are all lines.
59pub trait Canvas {
60 /// Append a batch of SDF quad instances (circles / rings / markers, already
61 /// lowered via [`QuadInstance`]). Zero-copy slice — the canvas decides whether
62 /// it copies into a vertex buffer (GPU) or rasters in place (CPU).
63 fn push_quads(&mut self, quads: &[QuadInstance]);
64 /// Append a batch of thick-AA line instances.
65 fn push_lines(&mut self, lines: &[LineInstance]);
66 /// The camera this canvas draws under (the shared L0 [`Camera`]).
67 fn camera(&self) -> &Camera;
68}
69
70/// **The renderer** — opens a [`Canvas`] for a frame, then presents it. The
71/// 2-category swap (CPU / CPU+GPU) is expressed entirely as which `Renderer` the
72/// host holds; the draw code is written once against [`Canvas`].
73pub trait Renderer {
74 /// Begin a frame of `width × height` pixels under `camera`, returning the
75 /// [`Canvas`] to push instances into.
76 fn begin(&mut self, width: u32, height: u32, camera: Camera) -> &mut dyn Canvas;
77 /// Finish the frame and produce the rasterized [`Frame`] (straight RGBA8).
78 fn present(&mut self) -> Frame;
79 /// Which [`Backend`] this renderer is.
80 fn backend(&self) -> Backend;
81}
82
83pub use cpu::{CpuCanvas, CpuRenderer};