lib.rs raw

//! Vello (GPU) render backend for guiduck scenes.
//!
//! This is the only guiduck crate that touches wgpu. The window/surface
//! plumbing (swapchain, blit, present) belongs to the platform shell; this
//! crate's job is fragment tree → `vello::Scene` → texture.

pub mod encode;

pub use encode::encode_fragment_tree;
pub use vello;

use guiduck_scene::geom::Affine;
use guiduck_scene::paint::Color;
use guiduck_scene::{FragmentId, FragmentStore, RenderBackend, RenderError};

/// GPU render backend: encodes fragment trees with vello and rasterizes them
/// into a configured target texture view.
pub struct VelloBackend {
    renderer: vello::Renderer,
    device: wgpu::Device,
    queue: wgpu::Queue,
    scene: vello::Scene,
    /// What `scene` currently holds: (store id, mutation counter, root,
    /// scale bits). While unchanged, the whole encode is skipped and the
    /// scene re-rendered as is.
    encoded: Option<(u64, u64, FragmentId, u64)>,
    target: Option<wgpu::TextureView>,
    base_color: Color,
}

impl VelloBackend {
    pub fn new(device: wgpu::Device, queue: wgpu::Queue) -> Result<Self, RenderError> {
        let renderer = vello::Renderer::new(&device, vello::RendererOptions::default())
            .map_err(|e| RenderError::Backend(Box::new(e)))?;
        Ok(Self {
            renderer,
            device,
            queue,
            scene: vello::Scene::new(),
            encoded: None,
            target: None,
            base_color: Color::WHITE,
        })
    }

    /// Set the texture view rendered into by [`RenderBackend::render`]. The
    /// shell calls this whenever the surface's target texture is (re)created.
    pub fn set_target(&mut self, view: wgpu::TextureView) {
        self.target = Some(view);
    }

    /// Set the background color the target is cleared to before drawing.
    pub fn set_base_color(&mut self, color: Color) {
        self.base_color = color;
    }
}

impl RenderBackend for VelloBackend {
    fn render(
        &mut self,
        root: FragmentId,
        store: &FragmentStore,
        width_px: u32,
        height_px: u32,
        scale: f64,
    ) -> Result<(), RenderError> {
        let target = self
            .target
            .as_ref()
            .ok_or_else(|| RenderError::Backend("no target texture view configured".into()))?;
        let signature = (
            store.store_id(),
            store.mutation_counter(),
            root,
            scale.to_bits(),
        );
        if self.encoded != Some(signature) {
            self.encoded = None;
            self.scene.reset();
            encode_fragment_tree(&mut self.scene, root, store, Affine::scale(scale))?;
            self.encoded = Some(signature);
        }
        self.renderer
            .render_to_texture(
                &self.device,
                &self.queue,
                &self.scene,
                target,
                &vello::RenderParams {
                    base_color: self.base_color,
                    width: width_px,
                    height: height_px,
                    antialiasing_method: vello::AaConfig::Area,
                },
            )
            .map_err(|e| RenderError::Backend(Box::new(e)))
    }
}