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: Paint,
12    pub opacity: f64,
13    /// Complete local geometry bounds before stroke expansion or trimming.
14    pub paint_bounds: kurbo::Rect,
15    /// Primitive boundary: compose its paints before applying source-relative opacity.
16    pub composition: Option<(u32, f32)>,
17}
18
19/// Replay invalidation for one logical canvas layer.
20#[derive(Clone, Debug)]
21pub struct ReplayCanvasLayerUpdate {
22    pub layer: usize,
23    /// Canvas-content regions requiring replay. The runtime selects nodes from its
24    /// shared scene geometry; `None` requests full-layer dirtification.
25    pub regions: Option<Vec<kurbo::Rect>>,
26}
27
28/// One authored opacity boundary, ordered from the outermost ancestor to the painted node.
29/// Native surfaces can present independently while a canvas backend composes each scope.
30#[derive(Clone, Copy, Debug, PartialEq)]
31pub struct OpacityScope {
32    pub node_id: u32,
33    pub opacity: f32,
34}
35
36impl crate::Interpolatable for OpacityScope {}
37
38/// The drawing contract shared by Pax runtime backends.
39pub trait RenderContext {
40    /// Begin an experimental GPU-only subtree effect. Returns whether source draws need replay.
41    /// The default rejects programmable effects rather than silently changing their appearance.
42    /// Available with the opt-in `shaders` Cargo feature.
43    #[cfg(feature = "shaders")]
44    fn begin_shader_effect(
45        &mut self,
46        _layer: usize,
47        _id: u32,
48        _z: i32,
49        _effect: &ShaderEffect,
50        _transform: kurbo::Affine,
51        _bounds: (f64, f64),
52        _scopes: &[OpacityScope],
53        _children: &[u32],
54    ) -> bool {
55        panic!("shader effects require the WGPU renderer; this backend does not support them")
56    }
57    /// Finish the boundary begun by `begin_shader_effect`.
58    #[cfg(feature = "shaders")]
59    fn end_shader_effect(&mut self, _layer: usize, _id: u32) {}
60    // Drawing
61    /// Fills a path at full opacity.
62    fn fill(&mut self, layer: usize, path: kurbo::BezPath, fill: &Paint) {
63        self.fill_with_opacity(layer, path, fill, 1.0);
64    }
65    /// Fills a path with an explicit opacity multiplier.
66    fn fill_with_opacity(&mut self, layer: usize, path: kurbo::BezPath, fill: &Paint, opacity: f64);
67    /// Fills a path with a material and explicit opacity multiplier.
68    fn fill_with_material_and_opacity(
69        &mut self,
70        layer: usize,
71        path: kurbo::BezPath,
72        fill: &Paint,
73        _material: &ResolvedMaterial,
74        opacity: f64,
75    ) {
76        self.fill_with_opacity(layer, path, fill, opacity);
77    }
78    /// Fills a path after optional geometry smoothing.
79    fn fill_with_material_and_opacity_and_smoothing(
80        &mut self,
81        layer: usize,
82        path: kurbo::BezPath,
83        fill: &Paint,
84        material: &ResolvedMaterial,
85        opacity: f64,
86        smoothing: PathSmoothing,
87    ) {
88        let path = crate::drawing::path_smoothing::smooth_bez_path(&path, smoothing);
89        self.fill_with_material_and_opacity(layer, path, fill, material, opacity);
90    }
91    /// Strokes a path at full opacity.
92    fn stroke(&mut self, layer: usize, path: kurbo::BezPath, stroke: &ResolvedStroke) {
93        self.stroke_with_opacity(layer, path, stroke, 1.0);
94    }
95    /// Strokes a path with an explicit opacity multiplier.
96    fn stroke_with_opacity(
97        &mut self,
98        layer: usize,
99        path: kurbo::BezPath,
100        stroke: &ResolvedStroke,
101        opacity: f64,
102    );
103    /// Strokes a path with a material and explicit opacity multiplier.
104    fn stroke_with_material_and_opacity(
105        &mut self,
106        layer: usize,
107        path: kurbo::BezPath,
108        stroke: &ResolvedStroke,
109        _material: &ResolvedMaterial,
110        opacity: f64,
111    ) {
112        self.stroke_with_opacity(layer, path, stroke, opacity);
113    }
114    /// Strokes a path after optional geometry smoothing.
115    fn stroke_with_material_and_opacity_and_smoothing(
116        &mut self,
117        layer: usize,
118        path: kurbo::BezPath,
119        stroke: &ResolvedStroke,
120        material: &ResolvedMaterial,
121        opacity: f64,
122        smoothing: PathSmoothing,
123    ) {
124        let path = crate::drawing::path_smoothing::smooth_bez_path(&path, smoothing);
125        self.stroke_with_material_and_opacity(layer, path, stroke, material, opacity);
126    }
127    /// Strokes a path with a normalized visible range over the path's total length.
128    fn stroke_with_draw_range_and_material_and_opacity(
129        &mut self,
130        layer: usize,
131        path: kurbo::BezPath,
132        stroke: &ResolvedStroke,
133        material: &ResolvedMaterial,
134        opacity: f64,
135        draw_start: f64,
136        draw_end: f64,
137    );
138    /// Strokes a draw-ranged path after optional geometry smoothing.
139    fn stroke_with_draw_range_and_material_and_opacity_and_smoothing(
140        &mut self,
141        layer: usize,
142        path: kurbo::BezPath,
143        stroke: &ResolvedStroke,
144        material: &ResolvedMaterial,
145        opacity: f64,
146        draw_start: f64,
147        draw_end: f64,
148        smoothing: PathSmoothing,
149    ) {
150        let path = crate::drawing::path_smoothing::smooth_bez_path(&path, smoothing);
151        self.stroke_with_draw_range_and_material_and_opacity(
152            layer, path, stroke, material, opacity, draw_start, draw_end,
153        );
154    }
155
156    // Clip/transform
157    /// Saves the current graphics state for a layer.
158    fn save(&mut self, layer: usize);
159    /// Restores the previous graphics state for a layer.
160    fn restore(&mut self, layer: usize);
161    /// Applies a clipping path to a layer.
162    fn clip(&mut self, layer: usize, path: BezPath);
163    /// Clips canvas drawing by source-over vector paint alpha, optionally
164    /// feathered with a Gaussian of this standard deviation in logical pixels.
165    fn clip_alpha(&mut self, _layer: usize, _paints: &[AlphaMaskPaint], _feather: f64) {
166        panic!("this rendering backend does not implement alpha masks");
167    }
168    /// Whether alpha sources can use ordinary retained vector rendering.
169    fn supports_alpha_source_capture(&self) -> bool {
170        false
171    }
172    /// Destination layer while recording a detached alpha source. Source coordinates are world
173    /// coordinates; `begin_alpha_source` supplies the mapping to the destination surface.
174    fn alpha_source_layer(&self) -> Option<usize> {
175        None
176    }
177    /// Isolates source draws from visible content and inherited clips. The currently open node
178    /// owns the result. Scopes before `inherited_scopes` belong to the consumer, not the source.
179    fn begin_alpha_source(
180        &mut self,
181        _layer: usize,
182        _world_to_surface: kurbo::Affine,
183        _inherited_scopes: usize,
184        _feather: f64,
185    ) {
186        panic!("this rendering backend does not implement alpha-source capture");
187    }
188    /// Completes the source and installs its captured alpha in the consumer's clip stack.
189    fn end_alpha_source(&mut self, _layer: usize) {}
190    /// Applies an affine transform to a layer.
191    fn transform(&mut self, layer: usize, affine: kurbo::Affine);
192
193    // Images
194    /// Loads raw RGBA image bytes under an identifier.
195    fn load_image(&mut self, identifier: &str, image: &[u8], width: usize, height: usize);
196    /// Draws a previously loaded image into `rect` at full opacity.
197    fn draw_image(&mut self, layer: usize, image_path: &str, rect: kurbo::Rect) {
198        self.draw_image_with_opacity(layer, image_path, rect, 1.0);
199    }
200    /// Draws an image with an opacity multiplier, in addition to source pixel alpha.
201    /// Backends must apply this at draw time without changing the cached source image.
202    fn draw_image_with_opacity(
203        &mut self,
204        layer: usize,
205        image_path: &str,
206        rect: kurbo::Rect,
207        opacity: f64,
208    );
209    /// Returns the loaded image size, if known.
210    fn get_image_size(&mut self, image_path: &str) -> Option<(usize, usize)>;
211    /// Returns true when the image is available for drawing.
212    fn image_loaded(&self, image_path: &str) -> bool;
213
214    // Other
215    /// Returns the current number of render layers.
216    fn layers(&self) -> usize;
217    /// Resizes the backend layer set to `layer_count`.
218    fn resize_layers_to(&mut self, layer_count: usize, dirty_canvases: Rc<RefCell<Vec<bool>>>);
219    /// Clears a layer.
220    fn clear(&mut self, layer: usize);
221    /// Flushes a layer to its presentation target.
222    fn flush(&mut self, layer: usize, dirty_canvases: Rc<RefCell<Vec<bool>>>);
223    /// Resizes the render surface.
224    fn resize(&mut self, width: usize, height: usize);
225    /// Requests refresh of specific layers.
226    fn refresh_layers(&mut self, layers: &[usize]);
227    /// Installs resolved lighting for a logical canvas layer.
228    fn set_scene_lighting(&mut self, _layer: usize, _lighting: &SceneLighting) {}
229    /// Cancels any narrowed replay scope for a layer so subsequent work targets all surfaces.
230    fn clear_targeted_replay(&mut self, _layer: usize) {}
231    /// Returns canvas layers ready to present.
232    fn take_ready_canvas_layers(&mut self) -> Vec<usize> {
233        vec![]
234    }
235
236    /// Returns canvas layers that should replay prior draw commands.
237    fn take_replay_canvas_layers(&mut self) -> Vec<usize> {
238        vec![]
239    }
240
241    /// Returns canvas layer replay work, optionally narrowed to canvas-content regions.
242    fn take_replay_canvas_layer_updates(&mut self) -> Vec<ReplayCanvasLayerUpdate> {
243        self.take_replay_canvas_layers()
244            .into_iter()
245            .map(|layer| ReplayCanvasLayerUpdate {
246                layer,
247                regions: None,
248            })
249            .collect()
250    }
251
252    /// Requests a screenshot for one render layer.
253    fn request_layer_screenshot(&mut self, _layer: usize, _request_id: u32) {}
254
255    /// Returns a previously requested layer screenshot, if ready.
256    fn take_layer_screenshot(&mut self, _layer: usize, _request_id: u32) -> Option<ScreenshotData> {
257        None
258    }
259
260    /// Returns ready screenshots for the physical surfaces backing one logical layer.
261    fn take_layer_surface_screenshots(
262        &mut self,
263        layer: usize,
264        request_id: u32,
265    ) -> Vec<LayerSurfaceScreenshotData> {
266        self.take_layer_screenshot(layer, request_id)
267            .map(|screenshot| {
268                vec![LayerSurfaceScreenshotData {
269                    id: screenshot.id,
270                    key: "single".to_string(),
271                    data: screenshot.data,
272                    width: screenshot.width,
273                    height: screenshot.height,
274                    origin_x: 0.0,
275                    origin_y: 0.0,
276                    logical_width: screenshot.width as f32,
277                    logical_height: screenshot.height as f32,
278                }]
279            })
280            .unwrap_or_default()
281    }
282
283    /// Returns whether the backend retains individual canvas nodes across frames.
284    fn retains_canvas_nodes(&self) -> bool {
285        true
286    }
287
288    /// Begins rendering a node and returns false when the backend can skip it.
289    fn begin_node(
290        &mut self,
291        _layer: usize,
292        _node_id: u32,
293        _z_index: i32,
294        _light_mask: u32,
295    ) -> bool {
296        true
297    }
298
299    /// Begins rendering a node with conservative canvas-space coverage bounds.
300    fn begin_node_with_bounds(
301        &mut self,
302        layer: usize,
303        node_id: u32,
304        z_index: i32,
305        _coverage_bounds: kurbo::Rect,
306        light_mask: u32,
307    ) -> bool {
308        self.begin_node(layer, node_id, z_index, light_mask)
309    }
310
311    /// Returns true when a bounded node skipped by `begin_node_with_bounds` is clean for now.
312    fn take_clean_skipped_node(&mut self, _layer: usize, _node_id: u32) -> bool {
313        false
314    }
315
316    /// Whether canvas opacity is applied after composing each subtree's canvas
317    /// content. This does not imply that the backend caches content across frames.
318    fn supports_subtree_opacity(&self) -> bool {
319        false
320    }
321
322    /// Assigns stable opacity ancestry to the currently recorded canvas node.
323    fn set_node_opacity_scopes(&mut self, _layer: usize, _node_id: u32, _scopes: &[OpacityScope]) {}
324
325    /// Ends rendering a node and returns true when the node was recorded.
326    fn end_node(&mut self, _layer: usize, _node_id: u32) -> bool {
327        true
328    }
329
330    /// Removes a node from backend retained state, if any.
331    fn remove_node(&mut self, _layer: usize, _node_id: u32) -> bool {
332        true
333    }
334}
335
336/// Convert a kurbo BezPath to a SVG-friendly drawing string
337pub fn bez_path_to_svg_path_data(path: &BezPath) -> String {
338    use kurbo::PathEl;
339
340    let mut data = String::new();
341    for element in path.elements() {
342        match element {
343            PathEl::MoveTo(point) => data.push_str(&format!("M{:.3},{:.3}", point.x, point.y)),
344            PathEl::LineTo(point) => data.push_str(&format!("L{:.3},{:.3}", point.x, point.y)),
345            PathEl::QuadTo(ctrl, point) => data.push_str(&format!(
346                "Q{:.3},{:.3} {:.3},{:.3}",
347                ctrl.x, ctrl.y, point.x, point.y
348            )),
349            PathEl::CurveTo(ctrl1, ctrl2, point) => data.push_str(&format!(
350                "C{:.3},{:.3} {:.3},{:.3} {:.3},{:.3}",
351                ctrl1.x, ctrl1.y, ctrl2.x, ctrl2.y, point.x, point.y
352            )),
353            PathEl::ClosePath => data.push('Z'),
354        }
355    }
356    data
357}
358
359/// Render layer selected for a primitive or native surface.
360#[derive(Debug, Clone, Copy, PartialEq)]
361pub enum Layer {
362    /// Platform-native element layer.
363    Native,
364    /// Platform-native layer that does not participate in occlusion.
365    NativeNonOccluding,
366    /// GPU/canvas-rendered layer.
367    Canvas,
368    /// Runtime can choose the appropriate layer.
369    DontCare,
370}