Skip to main content

pax_runtime_api/
rendering.rs

1//! Rendering backend contracts and helpers for drawing Pax scene content.
2
3use super::*;
4
5/// One vector paint contributing alpha to a mask, in local path coordinates.
6/// Color channels do not affect coverage; fill alpha and opacity are multiplied.
7#[derive(Clone, Debug)]
8pub struct AlphaMaskPaint {
9    pub path: kurbo::BezPath,
10    pub transform: kurbo::Affine,
11    pub fill: Fill,
12    pub opacity: f64,
13}
14
15/// Replay invalidation for one logical canvas layer.
16#[derive(Clone, Debug)]
17pub struct ReplayCanvasLayerUpdate {
18    pub layer: usize,
19    /// `None` means the layer should fall back to region/full-layer dirtification.
20    pub node_ids: Option<Vec<u32>>,
21}
22
23/// The Pax render trait, used as a layer of indirection and contract for backend-agnostic rendering.
24pub trait RenderContext {
25    // Drawing
26    /// Fills a path at full opacity.
27    fn fill(&mut self, layer: usize, path: kurbo::BezPath, fill: &Fill) {
28        self.fill_with_opacity(layer, path, fill, 1.0);
29    }
30    /// Fills a path with an explicit opacity multiplier.
31    fn fill_with_opacity(&mut self, layer: usize, path: kurbo::BezPath, fill: &Fill, opacity: f64);
32    /// Fills a path with a material and explicit opacity multiplier.
33    fn fill_with_material_and_opacity(
34        &mut self,
35        layer: usize,
36        path: kurbo::BezPath,
37        fill: &Fill,
38        _material: &Material,
39        opacity: f64,
40    ) {
41        self.fill_with_opacity(layer, path, fill, opacity);
42    }
43    /// Fills a path after optional geometry smoothing.
44    fn fill_with_material_and_opacity_and_smoothing(
45        &mut self,
46        layer: usize,
47        path: kurbo::BezPath,
48        fill: &Fill,
49        material: &Material,
50        opacity: f64,
51        smoothing: PathSmoothing,
52    ) {
53        let path = crate::drawing::path_smoothing::smooth_bez_path(&path, smoothing);
54        self.fill_with_material_and_opacity(layer, path, fill, material, opacity);
55    }
56    /// Strokes a path at full opacity.
57    fn stroke(&mut self, layer: usize, path: kurbo::BezPath, stroke: &Stroke) {
58        self.stroke_with_opacity(layer, path, stroke, 1.0);
59    }
60    /// Strokes a path with an explicit opacity multiplier.
61    fn stroke_with_opacity(
62        &mut self,
63        layer: usize,
64        path: kurbo::BezPath,
65        stroke: &Stroke,
66        opacity: f64,
67    );
68    /// Strokes a path with a material and explicit opacity multiplier.
69    fn stroke_with_material_and_opacity(
70        &mut self,
71        layer: usize,
72        path: kurbo::BezPath,
73        stroke: &Stroke,
74        _material: &Material,
75        opacity: f64,
76    ) {
77        self.stroke_with_opacity(layer, path, stroke, opacity);
78    }
79    /// Strokes a path after optional geometry smoothing.
80    fn stroke_with_material_and_opacity_and_smoothing(
81        &mut self,
82        layer: usize,
83        path: kurbo::BezPath,
84        stroke: &Stroke,
85        material: &Material,
86        opacity: f64,
87        smoothing: PathSmoothing,
88    ) {
89        let path = crate::drawing::path_smoothing::smooth_bez_path(&path, smoothing);
90        self.stroke_with_material_and_opacity(layer, path, stroke, material, opacity);
91    }
92    /// Strokes a path with a normalized visible range over the path's total length.
93    fn stroke_with_draw_range_and_material_and_opacity(
94        &mut self,
95        layer: usize,
96        path: kurbo::BezPath,
97        stroke: &Stroke,
98        material: &Material,
99        opacity: f64,
100        draw_start: f64,
101        draw_end: f64,
102    );
103    /// Strokes a draw-ranged path after optional geometry smoothing.
104    fn stroke_with_draw_range_and_material_and_opacity_and_smoothing(
105        &mut self,
106        layer: usize,
107        path: kurbo::BezPath,
108        stroke: &Stroke,
109        material: &Material,
110        opacity: f64,
111        draw_start: f64,
112        draw_end: f64,
113        smoothing: PathSmoothing,
114    ) {
115        let path = crate::drawing::path_smoothing::smooth_bez_path(&path, smoothing);
116        self.stroke_with_draw_range_and_material_and_opacity(
117            layer, path, stroke, material, opacity, draw_start, draw_end,
118        );
119    }
120
121    // Clip/transform
122    /// Saves the current graphics state for a layer.
123    fn save(&mut self, layer: usize);
124    /// Restores the previous graphics state for a layer.
125    fn restore(&mut self, layer: usize);
126    /// Applies a clipping path to a layer.
127    fn clip(&mut self, layer: usize, path: BezPath);
128    /// Clips canvas drawing by source-over vector paint alpha, optionally
129    /// feathered with a Gaussian of this standard deviation in logical pixels.
130    fn clip_alpha(&mut self, _layer: usize, _paints: &[AlphaMaskPaint], _feather: f64) {
131        panic!("this rendering backend does not implement alpha masks");
132    }
133    /// Applies an affine transform to a layer.
134    fn transform(&mut self, layer: usize, affine: kurbo::Affine);
135
136    // Images
137    /// Loads raw RGBA image bytes under an identifier.
138    fn load_image(&mut self, identifier: &str, image: &[u8], width: usize, height: usize);
139    /// Draws a previously loaded image into `rect`.
140    fn draw_image(&mut self, layer: usize, image_path: &str, rect: kurbo::Rect);
141    /// Returns the loaded image size, if known.
142    fn get_image_size(&mut self, image_path: &str) -> Option<(usize, usize)>;
143    /// Returns true when the image is available for drawing.
144    fn image_loaded(&self, image_path: &str) -> bool;
145
146    // Other
147    /// Returns the current number of render layers.
148    fn layers(&self) -> usize;
149    /// Resizes the backend layer set to `layer_count`.
150    fn resize_layers_to(&mut self, layer_count: usize, dirty_canvases: Rc<RefCell<Vec<bool>>>);
151    /// Clears a layer.
152    fn clear(&mut self, layer: usize);
153    /// Flushes a layer to its presentation target.
154    fn flush(&mut self, layer: usize, dirty_canvases: Rc<RefCell<Vec<bool>>>);
155    /// Resizes the render surface.
156    fn resize(&mut self, width: usize, height: usize);
157    /// Requests refresh of specific layers.
158    fn refresh_layers(&mut self, layers: &[usize]);
159    /// Installs resolved lighting for a logical canvas layer.
160    fn set_scene_lighting(&mut self, _layer: usize, _lighting: &SceneLighting) {}
161    /// Cancels any narrowed replay scope for a layer so subsequent work targets all surfaces.
162    fn clear_targeted_replay(&mut self, _layer: usize) {}
163    /// Returns canvas layers ready to present.
164    fn take_ready_canvas_layers(&mut self) -> Vec<usize> {
165        vec![]
166    }
167
168    /// Returns canvas layers that should replay prior draw commands.
169    fn take_replay_canvas_layers(&mut self) -> Vec<usize> {
170        vec![]
171    }
172
173    /// Returns canvas layer replay work, optionally narrowed to exact retained node ids.
174    fn take_replay_canvas_layer_updates(&mut self) -> Vec<ReplayCanvasLayerUpdate> {
175        self.take_replay_canvas_layers()
176            .into_iter()
177            .map(|layer| ReplayCanvasLayerUpdate {
178                layer,
179                node_ids: None,
180            })
181            .collect()
182    }
183
184    /// Requests a screenshot for one render layer.
185    fn request_layer_screenshot(&mut self, _layer: usize, _request_id: u32) {}
186
187    /// Returns a previously requested layer screenshot, if ready.
188    fn take_layer_screenshot(&mut self, _layer: usize, _request_id: u32) -> Option<ScreenshotData> {
189        None
190    }
191
192    /// Returns ready screenshots for the physical surfaces backing one logical layer.
193    fn take_layer_surface_screenshots(
194        &mut self,
195        layer: usize,
196        request_id: u32,
197    ) -> Vec<LayerSurfaceScreenshotData> {
198        self.take_layer_screenshot(layer, request_id)
199            .map(|screenshot| {
200                vec![LayerSurfaceScreenshotData {
201                    id: screenshot.id,
202                    key: "single".to_string(),
203                    data: screenshot.data,
204                    width: screenshot.width,
205                    height: screenshot.height,
206                    origin_x: 0.0,
207                    origin_y: 0.0,
208                    logical_width: screenshot.width as f32,
209                    logical_height: screenshot.height as f32,
210                }]
211            })
212            .unwrap_or_default()
213    }
214
215    /// Returns whether the backend retains individual canvas nodes across frames.
216    fn retains_canvas_nodes(&self) -> bool {
217        true
218    }
219
220    /// Begins rendering a node and returns false when the backend can skip it.
221    fn begin_node(
222        &mut self,
223        _layer: usize,
224        _node_id: u32,
225        _z_index: i32,
226        _light_mask: u32,
227    ) -> bool {
228        true
229    }
230
231    /// Begins rendering a node with conservative canvas-space coverage bounds.
232    fn begin_node_with_bounds(
233        &mut self,
234        layer: usize,
235        node_id: u32,
236        z_index: i32,
237        _coverage_bounds: kurbo::Rect,
238        light_mask: u32,
239    ) -> bool {
240        self.begin_node(layer, node_id, z_index, light_mask)
241    }
242
243    /// Returns true when a bounded node skipped by `begin_node_with_bounds` is clean for now.
244    fn take_clean_skipped_node(&mut self, _layer: usize, _node_id: u32) -> bool {
245        false
246    }
247
248    /// Ends rendering a node and returns true when the node was recorded.
249    fn end_node(&mut self, _layer: usize, _node_id: u32) -> bool {
250        true
251    }
252
253    /// Removes a node from backend retained state, if any.
254    fn remove_node(&mut self, _layer: usize, _node_id: u32) -> bool {
255        true
256    }
257}
258
259/// Convert a kurbo BezPath to a SVG-friendly drawing string
260pub fn bez_path_to_svg_path_data(path: &BezPath) -> String {
261    use kurbo::PathEl;
262
263    let mut data = String::new();
264    for element in path.elements() {
265        match element {
266            PathEl::MoveTo(point) => data.push_str(&format!("M{:.3},{:.3}", point.x, point.y)),
267            PathEl::LineTo(point) => data.push_str(&format!("L{:.3},{:.3}", point.x, point.y)),
268            PathEl::QuadTo(ctrl, point) => data.push_str(&format!(
269                "Q{:.3},{:.3} {:.3},{:.3}",
270                ctrl.x, ctrl.y, point.x, point.y
271            )),
272            PathEl::CurveTo(ctrl1, ctrl2, point) => data.push_str(&format!(
273                "C{:.3},{:.3} {:.3},{:.3} {:.3},{:.3}",
274                ctrl1.x, ctrl1.y, ctrl2.x, ctrl2.y, point.x, point.y
275            )),
276            PathEl::ClosePath => data.push('Z'),
277        }
278    }
279    data
280}
281
282/// Render layer selected for a primitive or native surface.
283#[derive(Debug, Clone, Copy, PartialEq)]
284pub enum Layer {
285    /// Platform-native element layer.
286    Native,
287    /// Platform-native layer that does not participate in occlusion.
288    NativeNonOccluding,
289    /// GPU/canvas-rendered layer.
290    Canvas,
291    /// Runtime can choose the appropriate layer.
292    DontCare,
293}