Skip to main content

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;
25/// The **"cyber-toon" decoration kernel** — domain-agnostic, pure-CPU L0 beauty
26/// helpers (glow halos, AO drop-shadows, animated dash-array strokes, particle
27/// tracers, shockwave rings, CPU bloom, gradient-by-scalar). Zero GPU/vello, so
28/// they light up identically native AND on wasm; all animation is injected-clock
29/// driven so snapshots stay deterministic. See [`decor`].
30pub mod decor;
31pub mod layer;
32pub mod prim;
33
34#[cfg(feature = "wgpu")]
35pub mod gpu;
36
37/// The **L1 vello beauty overlay** (feature `l1-vello`) — gradients, antialiased
38/// curves, gaussian blur / effects composited on top of the L0 SDF base. Desktop
39/// GPU only; never required (L0 stands alone). See [`l1`].
40#[cfg(feature = "l1-vello")]
41pub mod l1;
42
43pub use backend::{decide, Backend, GpuProbe};
44pub use camera::{Camera, InputFeel};
45pub use layer::{Layer, LayerKind, LayerStack};
46pub use prim::{CircleInstance, LineInstance, MarkerInstance, QuadInstance, RingInstance};
47
48/// A rendered frame: straight (un-premultiplied) RGBA8, row-major, `width × height`.
49/// The CPU lane composites into this; the GPU lane reads it back for a parity test.
50/// Mirrors graphview's `Rendered` so a skin can move over without a shape change.
51#[derive(Clone, Debug, PartialEq)]
52pub struct Frame {
53    pub width: u32,
54    pub height: u32,
55    /// `width * height * 4` bytes, `[r, g, b, a]` per pixel, **not** premultiplied.
56    pub rgba: Vec<u8>,
57}
58
59impl Frame {
60    /// Count of pixels whose alpha is non-zero — the "did it actually draw"
61    /// oracle the raster emit checkpoint reports (`raster.lit_px > 0`).
62    pub fn lit_px(&self) -> usize {
63        self.rgba.chunks_exact(4).filter(|p| p[3] != 0).count()
64    }
65}
66
67/// **The drawing surface** — the galileo-style backend-swap seam. A host pushes
68/// SDF instance batches; the concrete canvas (CPU pixmap or wgpu callback) decides
69/// how they become pixels. Domain-agnostic: a map point, a graph node, and a POI
70/// are all just quads; a road and an edge are all lines.
71pub trait Canvas {
72    /// Append a batch of SDF quad instances (circles / rings / markers, already
73    /// lowered via [`QuadInstance`]). Zero-copy slice — the canvas decides whether
74    /// it copies into a vertex buffer (GPU) or rasters in place (CPU).
75    fn push_quads(&mut self, quads: &[QuadInstance]);
76    /// Append a batch of thick-AA line instances.
77    fn push_lines(&mut self, lines: &[LineInstance]);
78    /// The camera this canvas draws under (the shared L0 [`Camera`]).
79    fn camera(&self) -> &Camera;
80}
81
82/// **The renderer** — opens a [`Canvas`] for a frame, then presents it. The
83/// 2-category swap (CPU / CPU+GPU) is expressed entirely as which `Renderer` the
84/// host holds; the draw code is written once against [`Canvas`].
85pub trait Renderer {
86    /// Begin a frame of `width × height` pixels under `camera`, returning the
87    /// [`Canvas`] to push instances into.
88    fn begin(&mut self, width: u32, height: u32, camera: Camera) -> &mut dyn Canvas;
89    /// Finish the frame and produce the rasterized [`Frame`] (straight RGBA8).
90    fn present(&mut self) -> Frame;
91    /// Which [`Backend`] this renderer is.
92    fn backend(&self) -> Backend;
93}
94
95pub use cpu::{CpuCanvas, CpuRenderer};