facett-core 0.1.7

facett — visual kernel: render a node/edge Scene into egui (wgpu fast path to come)
Documentation
//! **`facett-core::render`** — the L0 shared render kernel both map skins and
//! `facett-graphview` draw through (the CONS-CORE seam).
//!
//! Phase A landed the pure additions ([`camera`], [`layer`], the moved scissor, the
//! GPU scaffold). **Phase B** adds the net-new L0 SDF kernel + the backend-swap
//! seam:
//!
//! - [`prim`] — the SDF instance vocabulary
//!   ([`QuadInstance`](prim::QuadInstance)/[`LineInstance`](prim::LineInstance) +
//!   the `Circle`/`Ring`/`Marker` constructors), `bytemuck`-able for the GPU path.
//! - [`cpu`] — the CPU lane: the SDF coverage math ([`cpu::sdf`]) + [`CpuCanvas`]
//!   rasterizing onto a `vello_cpu` pixmap, plus the moved rect scissor.
//! - [`gpu`] (feature `wgpu`) — the [`GpuSdfRenderer`](gpu::sdf_pipeline::GpuSdfRenderer):
//!   instanced quads with a fragment-shader SDF (circle/ring/marker) + thick AA
//!   instanced lines, on the Phase-A install lifecycle + bytemuck plumbing.
//! - [`backend`] — the [`decide`](backend::decide)`(probe) -> `[`Backend`] policy
//!   (lifted from graphview).
//! - [`Canvas`] / [`Renderer`] — the backend-swap seam traits. Both
//!   [`CpuRenderer`](cpu::CpuRenderer) and (feature `wgpu`)
//!   [`GpuSdfRenderer`](gpu::sdf_pipeline::GpuSdfRenderer) satisfy them.

pub mod backend;
pub mod camera;
pub mod cpu;
pub mod layer;
pub mod prim;

#[cfg(feature = "wgpu")]
pub mod gpu;

pub use backend::{decide, Backend, GpuProbe};
pub use camera::{Camera, InputFeel};
pub use layer::{Layer, LayerKind, LayerStack};
pub use prim::{CircleInstance, LineInstance, MarkerInstance, QuadInstance, RingInstance};

/// A rendered frame: straight (un-premultiplied) RGBA8, row-major, `width × height`.
/// The CPU lane composites into this; the GPU lane reads it back for a parity test.
/// Mirrors graphview's `Rendered` so a skin can move over without a shape change.
#[derive(Clone, Debug, PartialEq)]
pub struct Frame {
    pub width: u32,
    pub height: u32,
    /// `width * height * 4` bytes, `[r, g, b, a]` per pixel, **not** premultiplied.
    pub rgba: Vec<u8>,
}

impl Frame {
    /// Count of pixels whose alpha is non-zero — the "did it actually draw"
    /// oracle the raster emit checkpoint reports (`raster.lit_px > 0`).
    pub fn lit_px(&self) -> usize {
        self.rgba.chunks_exact(4).filter(|p| p[3] != 0).count()
    }
}

/// **The drawing surface** — the galileo-style backend-swap seam. A host pushes
/// SDF instance batches; the concrete canvas (CPU pixmap or wgpu callback) decides
/// how they become pixels. Domain-agnostic: a map point, a graph node, and a POI
/// are all just quads; a road and an edge are all lines.
pub trait Canvas {
    /// Append a batch of SDF quad instances (circles / rings / markers, already
    /// lowered via [`QuadInstance`]). Zero-copy slice — the canvas decides whether
    /// it copies into a vertex buffer (GPU) or rasters in place (CPU).
    fn push_quads(&mut self, quads: &[QuadInstance]);
    /// Append a batch of thick-AA line instances.
    fn push_lines(&mut self, lines: &[LineInstance]);
    /// The camera this canvas draws under (the shared L0 [`Camera`]).
    fn camera(&self) -> &Camera;
}

/// **The renderer** — opens a [`Canvas`] for a frame, then presents it. The
/// 2-category swap (CPU / CPU+GPU) is expressed entirely as which `Renderer` the
/// host holds; the draw code is written once against [`Canvas`].
pub trait Renderer {
    /// Begin a frame of `width × height` pixels under `camera`, returning the
    /// [`Canvas`] to push instances into.
    fn begin(&mut self, width: u32, height: u32, camera: Camera) -> &mut dyn Canvas;
    /// Finish the frame and produce the rasterized [`Frame`] (straight RGBA8).
    fn present(&mut self) -> Frame;
    /// Which [`Backend`] this renderer is.
    fn backend(&self) -> Backend;
}

pub use cpu::{CpuCanvas, CpuRenderer};