pax-runtime-api 0.39.1

Userland constructs used at the runtime API boundary of Pax Engine
Documentation
//! Rendering backend contracts and helpers for drawing Pax scene content.

use super::*;

/// One vector paint contributing alpha to a mask, in local path coordinates.
/// Color channels do not affect coverage; fill alpha and opacity are multiplied.
#[derive(Clone, Debug)]
pub struct AlphaMaskPaint {
    pub path: kurbo::BezPath,
    pub transform: kurbo::Affine,
    pub fill: Paint,
    pub opacity: f64,
    /// Complete local geometry bounds before stroke expansion or trimming.
    pub paint_bounds: kurbo::Rect,
    /// Primitive boundary: compose its paints before applying source-relative opacity.
    pub composition: Option<(u32, f32)>,
}

/// Replay invalidation for one logical canvas layer.
#[derive(Clone, Debug)]
pub struct ReplayCanvasLayerUpdate {
    pub layer: usize,
    /// Canvas-content regions requiring replay. The runtime selects nodes from its
    /// shared scene geometry; `None` requests full-layer dirtification.
    pub regions: Option<Vec<kurbo::Rect>>,
}

/// One authored opacity boundary, ordered from the outermost ancestor to the painted node.
/// Native surfaces can present independently while a canvas backend composes each scope.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct OpacityScope {
    pub node_id: u32,
    pub opacity: f32,
}

impl crate::Interpolatable for OpacityScope {}

/// The drawing contract shared by Pax runtime backends.
pub trait RenderContext {
    /// Begin an experimental GPU-only subtree effect. Returns whether source draws need replay.
    /// The default rejects programmable effects rather than silently changing their appearance.
    /// Available with the opt-in `shaders` Cargo feature.
    #[cfg(feature = "shaders")]
    fn begin_shader_effect(
        &mut self,
        _layer: usize,
        _id: u32,
        _z: i32,
        _effect: &ShaderEffect,
        _transform: kurbo::Affine,
        _bounds: (f64, f64),
        _scopes: &[OpacityScope],
        _children: &[u32],
    ) -> bool {
        panic!("shader effects require the WGPU renderer; this backend does not support them")
    }
    /// Finish the boundary begun by `begin_shader_effect`.
    #[cfg(feature = "shaders")]
    fn end_shader_effect(&mut self, _layer: usize, _id: u32) {}
    // Drawing
    /// Fills a path at full opacity.
    fn fill(&mut self, layer: usize, path: kurbo::BezPath, fill: &Paint) {
        self.fill_with_opacity(layer, path, fill, 1.0);
    }
    /// Fills a path with an explicit opacity multiplier.
    fn fill_with_opacity(&mut self, layer: usize, path: kurbo::BezPath, fill: &Paint, opacity: f64);
    /// Fills a path with a material and explicit opacity multiplier.
    fn fill_with_material_and_opacity(
        &mut self,
        layer: usize,
        path: kurbo::BezPath,
        fill: &Paint,
        _material: &ResolvedMaterial,
        opacity: f64,
    ) {
        self.fill_with_opacity(layer, path, fill, opacity);
    }
    /// Fills a path after optional geometry smoothing.
    fn fill_with_material_and_opacity_and_smoothing(
        &mut self,
        layer: usize,
        path: kurbo::BezPath,
        fill: &Paint,
        material: &ResolvedMaterial,
        opacity: f64,
        smoothing: PathSmoothing,
    ) {
        let path = crate::drawing::path_smoothing::smooth_bez_path(&path, smoothing);
        self.fill_with_material_and_opacity(layer, path, fill, material, opacity);
    }
    /// Strokes a path at full opacity.
    fn stroke(&mut self, layer: usize, path: kurbo::BezPath, stroke: &ResolvedStroke) {
        self.stroke_with_opacity(layer, path, stroke, 1.0);
    }
    /// Strokes a path with an explicit opacity multiplier.
    fn stroke_with_opacity(
        &mut self,
        layer: usize,
        path: kurbo::BezPath,
        stroke: &ResolvedStroke,
        opacity: f64,
    );
    /// Strokes a path with a material and explicit opacity multiplier.
    fn stroke_with_material_and_opacity(
        &mut self,
        layer: usize,
        path: kurbo::BezPath,
        stroke: &ResolvedStroke,
        _material: &ResolvedMaterial,
        opacity: f64,
    ) {
        self.stroke_with_opacity(layer, path, stroke, opacity);
    }
    /// Strokes a path after optional geometry smoothing.
    fn stroke_with_material_and_opacity_and_smoothing(
        &mut self,
        layer: usize,
        path: kurbo::BezPath,
        stroke: &ResolvedStroke,
        material: &ResolvedMaterial,
        opacity: f64,
        smoothing: PathSmoothing,
    ) {
        let path = crate::drawing::path_smoothing::smooth_bez_path(&path, smoothing);
        self.stroke_with_material_and_opacity(layer, path, stroke, material, opacity);
    }
    /// Strokes a path with a normalized visible range over the path's total length.
    fn stroke_with_draw_range_and_material_and_opacity(
        &mut self,
        layer: usize,
        path: kurbo::BezPath,
        stroke: &ResolvedStroke,
        material: &ResolvedMaterial,
        opacity: f64,
        draw_start: f64,
        draw_end: f64,
    );
    /// Strokes a draw-ranged path after optional geometry smoothing.
    fn stroke_with_draw_range_and_material_and_opacity_and_smoothing(
        &mut self,
        layer: usize,
        path: kurbo::BezPath,
        stroke: &ResolvedStroke,
        material: &ResolvedMaterial,
        opacity: f64,
        draw_start: f64,
        draw_end: f64,
        smoothing: PathSmoothing,
    ) {
        let path = crate::drawing::path_smoothing::smooth_bez_path(&path, smoothing);
        self.stroke_with_draw_range_and_material_and_opacity(
            layer, path, stroke, material, opacity, draw_start, draw_end,
        );
    }

    // Clip/transform
    /// Saves the current graphics state for a layer.
    fn save(&mut self, layer: usize);
    /// Restores the previous graphics state for a layer.
    fn restore(&mut self, layer: usize);
    /// Applies a clipping path to a layer.
    fn clip(&mut self, layer: usize, path: BezPath);
    /// Clips canvas drawing by source-over vector paint alpha, optionally
    /// feathered with a Gaussian of this standard deviation in logical pixels.
    fn clip_alpha(&mut self, _layer: usize, _paints: &[AlphaMaskPaint], _feather: f64) {
        panic!("this rendering backend does not implement alpha masks");
    }
    /// Whether alpha sources can use ordinary retained vector rendering.
    fn supports_alpha_source_capture(&self) -> bool {
        false
    }
    /// Destination layer while recording a detached alpha source. Source coordinates are world
    /// coordinates; `begin_alpha_source` supplies the mapping to the destination surface.
    fn alpha_source_layer(&self) -> Option<usize> {
        None
    }
    /// Isolates source draws from visible content and inherited clips. The currently open node
    /// owns the result. Scopes before `inherited_scopes` belong to the consumer, not the source.
    fn begin_alpha_source(
        &mut self,
        _layer: usize,
        _world_to_surface: kurbo::Affine,
        _inherited_scopes: usize,
        _feather: f64,
    ) {
        panic!("this rendering backend does not implement alpha-source capture");
    }
    /// Completes the source and installs its captured alpha in the consumer's clip stack.
    fn end_alpha_source(&mut self, _layer: usize) {}
    /// Applies an affine transform to a layer.
    fn transform(&mut self, layer: usize, affine: kurbo::Affine);

    // Images
    /// Loads raw RGBA image bytes under an identifier.
    fn load_image(&mut self, identifier: &str, image: &[u8], width: usize, height: usize);
    /// Draws a previously loaded image into `rect` at full opacity.
    fn draw_image(&mut self, layer: usize, image_path: &str, rect: kurbo::Rect) {
        self.draw_image_with_opacity(layer, image_path, rect, 1.0);
    }
    /// Draws an image with an opacity multiplier, in addition to source pixel alpha.
    /// Backends must apply this at draw time without changing the cached source image.
    fn draw_image_with_opacity(
        &mut self,
        layer: usize,
        image_path: &str,
        rect: kurbo::Rect,
        opacity: f64,
    );
    /// Returns the loaded image size, if known.
    fn get_image_size(&mut self, image_path: &str) -> Option<(usize, usize)>;
    /// Returns true when the image is available for drawing.
    fn image_loaded(&self, image_path: &str) -> bool;

    // Other
    /// Returns the current number of render layers.
    fn layers(&self) -> usize;
    /// Resizes the backend layer set to `layer_count`.
    fn resize_layers_to(&mut self, layer_count: usize, dirty_canvases: Rc<RefCell<Vec<bool>>>);
    /// Clears a layer.
    fn clear(&mut self, layer: usize);
    /// Flushes a layer to its presentation target.
    fn flush(&mut self, layer: usize, dirty_canvases: Rc<RefCell<Vec<bool>>>);
    /// Resizes the render surface.
    fn resize(&mut self, width: usize, height: usize);
    /// Requests refresh of specific layers.
    fn refresh_layers(&mut self, layers: &[usize]);
    /// Installs resolved lighting for a logical canvas layer.
    fn set_scene_lighting(&mut self, _layer: usize, _lighting: &SceneLighting) {}
    /// Cancels any narrowed replay scope for a layer so subsequent work targets all surfaces.
    fn clear_targeted_replay(&mut self, _layer: usize) {}
    /// Returns canvas layers ready to present.
    fn take_ready_canvas_layers(&mut self) -> Vec<usize> {
        vec![]
    }

    /// Returns canvas layers that should replay prior draw commands.
    fn take_replay_canvas_layers(&mut self) -> Vec<usize> {
        vec![]
    }

    /// Returns canvas layer replay work, optionally narrowed to canvas-content regions.
    fn take_replay_canvas_layer_updates(&mut self) -> Vec<ReplayCanvasLayerUpdate> {
        self.take_replay_canvas_layers()
            .into_iter()
            .map(|layer| ReplayCanvasLayerUpdate {
                layer,
                regions: None,
            })
            .collect()
    }

    /// Requests a screenshot for one render layer.
    fn request_layer_screenshot(&mut self, _layer: usize, _request_id: u32) {}

    /// Returns a previously requested layer screenshot, if ready.
    fn take_layer_screenshot(&mut self, _layer: usize, _request_id: u32) -> Option<ScreenshotData> {
        None
    }

    /// Returns ready screenshots for the physical surfaces backing one logical layer.
    fn take_layer_surface_screenshots(
        &mut self,
        layer: usize,
        request_id: u32,
    ) -> Vec<LayerSurfaceScreenshotData> {
        self.take_layer_screenshot(layer, request_id)
            .map(|screenshot| {
                vec![LayerSurfaceScreenshotData {
                    id: screenshot.id,
                    key: "single".to_string(),
                    data: screenshot.data,
                    width: screenshot.width,
                    height: screenshot.height,
                    origin_x: 0.0,
                    origin_y: 0.0,
                    logical_width: screenshot.width as f32,
                    logical_height: screenshot.height as f32,
                }]
            })
            .unwrap_or_default()
    }

    /// Returns whether the backend retains individual canvas nodes across frames.
    fn retains_canvas_nodes(&self) -> bool {
        true
    }

    /// Begins rendering a node and returns false when the backend can skip it.
    fn begin_node(
        &mut self,
        _layer: usize,
        _node_id: u32,
        _z_index: i32,
        _light_mask: u32,
    ) -> bool {
        true
    }

    /// Begins rendering a node with conservative canvas-space coverage bounds.
    fn begin_node_with_bounds(
        &mut self,
        layer: usize,
        node_id: u32,
        z_index: i32,
        _coverage_bounds: kurbo::Rect,
        light_mask: u32,
    ) -> bool {
        self.begin_node(layer, node_id, z_index, light_mask)
    }

    /// Returns true when a bounded node skipped by `begin_node_with_bounds` is clean for now.
    fn take_clean_skipped_node(&mut self, _layer: usize, _node_id: u32) -> bool {
        false
    }

    /// Whether canvas opacity is applied after composing each subtree's canvas
    /// content. This does not imply that the backend caches content across frames.
    fn supports_subtree_opacity(&self) -> bool {
        false
    }

    /// Assigns stable opacity ancestry to the currently recorded canvas node.
    fn set_node_opacity_scopes(&mut self, _layer: usize, _node_id: u32, _scopes: &[OpacityScope]) {}

    /// Ends rendering a node and returns true when the node was recorded.
    fn end_node(&mut self, _layer: usize, _node_id: u32) -> bool {
        true
    }

    /// Removes a node from backend retained state, if any.
    fn remove_node(&mut self, _layer: usize, _node_id: u32) -> bool {
        true
    }
}

/// Convert a kurbo BezPath to a SVG-friendly drawing string
pub fn bez_path_to_svg_path_data(path: &BezPath) -> String {
    use kurbo::PathEl;

    let mut data = String::new();
    for element in path.elements() {
        match element {
            PathEl::MoveTo(point) => data.push_str(&format!("M{:.3},{:.3}", point.x, point.y)),
            PathEl::LineTo(point) => data.push_str(&format!("L{:.3},{:.3}", point.x, point.y)),
            PathEl::QuadTo(ctrl, point) => data.push_str(&format!(
                "Q{:.3},{:.3} {:.3},{:.3}",
                ctrl.x, ctrl.y, point.x, point.y
            )),
            PathEl::CurveTo(ctrl1, ctrl2, point) => data.push_str(&format!(
                "C{:.3},{:.3} {:.3},{:.3} {:.3},{:.3}",
                ctrl1.x, ctrl1.y, ctrl2.x, ctrl2.y, point.x, point.y
            )),
            PathEl::ClosePath => data.push('Z'),
        }
    }
    data
}

/// Render layer selected for a primitive or native surface.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Layer {
    /// Platform-native element layer.
    Native,
    /// Platform-native layer that does not participate in occlusion.
    NativeNonOccluding,
    /// GPU/canvas-rendered layer.
    Canvas,
    /// Runtime can choose the appropriate layer.
    DontCare,
}