Skip to main content

cranpose_render_pixels/
lib.rs

1#![doc = include_str!("../README.md")]
2
3mod damage;
4mod draw;
5mod pipeline;
6pub mod scene;
7pub mod sprites;
8pub mod style;
9
10use cranpose_render_common::{
11    RenderScene, Renderer, SceneUpdates,
12    text_measure::{CachedFontTextMeasurer, SoftwareTextResources},
13};
14use cranpose_ui::LayoutTree;
15use cranpose_ui_graphics::Size;
16pub use draw::draw_scene;
17pub use scene::{HitRegion, Scene};
18
19/// A rectangle of device pixels: columns `x..x + width` of rows
20/// `y..y + height`.
21#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22pub struct PixelRect {
23    /// The first column.
24    pub x: u32,
25    /// The first row.
26    pub y: u32,
27    /// The number of columns.
28    pub width: u32,
29    /// The number of rows.
30    pub height: u32,
31}
32
33fn pixel_rect(bounds: draw::ClipBounds) -> PixelRect {
34    PixelRect {
35        x: bounds.min_x.max(0) as u32,
36        y: bounds.min_y.max(0) as u32,
37        width: (bounds.max_x - bounds.min_x).max(0) as u32,
38        height: (bounds.max_y - bounds.min_y).max(0) as u32,
39    }
40}
41
42/// The picture a host drew last. [`PixelsRenderer::update_picture`] moves it
43/// to the current scene and finds the pixels that change, and
44/// [`PixelsRenderer::draw_picture_area`] draws any part of it.
45#[derive(Default)]
46pub struct Picture {
47    raster: Option<scene::RasterScene>,
48    width: u32,
49    height: u32,
50    scale: f32,
51    changed: Vec<draw::ClipBounds>,
52}
53
54fn valid_scale(scale: f32) -> f32 {
55    if scale.is_finite() && scale > 0.0 {
56        scale
57    } else {
58        1.0
59    }
60}
61
62#[derive(Debug)]
63pub enum PixelsRendererError {
64    Layout(String),
65}
66
67pub struct PixelsRenderer {
68    scene: Scene,
69    text_resources: SoftwareTextResources,
70    /// The app context this renderer gave its text measurer.
71    app_context: Option<std::rc::Weak<cranpose_ui::AppContext>>,
72}
73
74impl Default for PixelsRenderer {
75    fn default() -> Self {
76        Self::new()
77    }
78}
79
80impl PixelsRenderer {
81    pub fn new() -> Self {
82        Self::with_fonts(&[])
83    }
84
85    /// A renderer that draws text in `fonts`. With none that parses, it uses
86    /// the embedded default font when the `embedded-default-font` feature is
87    /// on, and draws no text otherwise.
88    pub fn with_fonts(fonts: &[&'static [u8]]) -> Self {
89        Self {
90            scene: Scene::new(),
91            text_resources: SoftwareTextResources::from_fonts(fonts),
92            app_context: None,
93        }
94    }
95
96    pub fn draw(&self, frame: &mut [u8], width: u32, height: u32) {
97        draw::draw_scene_with_text_resources(
98            frame,
99            width,
100            height,
101            &self.scene,
102            &self.text_resources,
103        );
104    }
105
106    /// Rasterizes logical scene coordinates at `scale` device pixels per point.
107    /// Hit regions stay in logical coordinates. Invalid scales use 1.0.
108    pub fn draw_scaled(&self, frame: &mut [u8], width: u32, height: u32, scale: f32) {
109        draw::draw_scene_scaled(
110            frame,
111            width,
112            height,
113            &self.scene,
114            &self.text_resources,
115            valid_scale(scale),
116        );
117    }
118
119    fn raster_scene(&self, scale: f32) -> scene::RasterScene {
120        match self.scene.graph.as_ref() {
121            Some(graph) => pipeline::build_raster_scene(graph, self.scene.diagnostics(), scale),
122            None => scene::RasterScene::new(),
123        }
124    }
125
126    /// Moves `picture` to the current scene, drawn `width` by `height` device
127    /// pixels at `scale` device pixels per point, and puts into `changed` the
128    /// areas whose pixels differ from the picture it held: the whole frame the
129    /// first time and after a change of size or scale. Invalid scales use 1.0.
130    pub fn update_picture(
131        &self,
132        picture: &mut Picture,
133        width: u32,
134        height: u32,
135        scale: f32,
136        changed: &mut Vec<PixelRect>,
137    ) {
138        let scale = valid_scale(scale);
139        let raster = self.raster_scene(scale);
140        changed.clear();
141        match picture.raster.as_ref() {
142            Some(old)
143                if (picture.width, picture.height, picture.scale) == (width, height, scale) =>
144            {
145                let window = draw::ClipBounds {
146                    min_x: 0,
147                    min_y: 0,
148                    max_x: i32::try_from(width).unwrap_or(i32::MAX),
149                    max_y: i32::try_from(height).unwrap_or(i32::MAX),
150                };
151                damage::changed_areas(old, &raster, window, &mut picture.changed);
152                changed.extend(picture.changed.iter().copied().map(pixel_rect));
153            }
154            _ if width > 0 && height > 0 => changed.push(PixelRect {
155                x: 0,
156                y: 0,
157                width,
158                height,
159            }),
160            _ => {}
161        }
162        picture.raster = Some(raster);
163        picture.width = width;
164        picture.height = height;
165        picture.scale = scale;
166    }
167
168    /// Draws `area` of `picture` into `pixels` as straight-alpha RGBA8 rows of
169    /// `area.width` pixels, `area.height` of them. Draws nothing before the
170    /// first [`Self::update_picture`] or when `pixels` is shorter than the area.
171    pub fn draw_picture_area(&self, picture: &Picture, area: PixelRect, pixels: &mut [u8]) {
172        let Some(raster) = picture.raster.as_ref() else {
173            return;
174        };
175        let len = area.width as usize * area.height as usize * 4;
176        let Some(pixels) = pixels.get_mut(..len) else {
177            return;
178        };
179        let mut target = draw::Target {
180            pixels,
181            left: i32::try_from(area.x).unwrap_or(i32::MAX),
182            top: i32::try_from(area.y).unwrap_or(i32::MAX),
183            width: area.width,
184            height: area.height,
185        };
186        draw::draw_raster_window(
187            &mut target,
188            raster,
189            self.scene.diagnostics(),
190            &self.text_resources,
191        );
192    }
193}
194
195impl PixelsRenderer {
196    /// The scene at `scale` device pixels per point as sprites for a GPU
197    /// compositor, into `frame`, which keeps its textures from the frames
198    /// before. Invalid scales use 1.0.
199    pub fn compose_sprites(
200        &self,
201        frame: &mut sprites::SpriteFrame,
202        width: u32,
203        height: u32,
204        scale: f32,
205    ) {
206        let scene = self.raster_scene(valid_scale(scale));
207        frame.compose(
208            &scene,
209            width,
210            height,
211            self.scene.diagnostics(),
212            &self.text_resources,
213        );
214    }
215}
216
217impl Renderer for PixelsRenderer {
218    type Scene = Scene;
219    type Error = PixelsRendererError;
220
221    fn attach_app_context_services(&mut self, app_context: &cranpose_ui::AppContext) {
222        app_context.set_text_measurer(CachedFontTextMeasurer::with_text_resources(
223            self.text_resources.clone(),
224            64,
225        ));
226        self.app_context = Some(app_context.downgrade());
227    }
228
229    #[cfg(feature = "installed-fonts")]
230    fn apply_installed_fonts(&mut self, app_context: &cranpose_ui::AppContext) {
231        let Some(resources) = self.text_resources.with_installed_faces() else {
232            return;
233        };
234        self.text_resources = resources;
235        let attached = self
236            .app_context
237            .as_ref()
238            .and_then(std::rc::Weak::upgrade)
239            .is_some_and(|attached| std::ptr::eq(&*attached, app_context));
240        if attached {
241            self.attach_app_context_services(app_context);
242        }
243    }
244
245    fn scene(&self) -> &Self::Scene {
246        &self.scene
247    }
248
249    fn scene_mut(&mut self) -> &mut Self::Scene {
250        &mut self.scene
251    }
252
253    fn rebuild_scene(
254        &mut self,
255        layout_tree: &LayoutTree,
256        _viewport: Size,
257    ) -> Result<(), Self::Error> {
258        self.scene.clear();
259        pipeline::render_layout_tree(layout_tree.root(), &mut self.scene);
260        Ok(())
261    }
262
263    fn rebuild_scene_from_applier(
264        &mut self,
265        applier: &mut cranpose_core::MemoryApplier,
266        root: cranpose_core::NodeId,
267        _viewport: Size,
268    ) -> Result<(), Self::Error> {
269        self.scene.rebuild_from_applier(applier, root);
270        Ok(())
271    }
272
273    fn update_scene_from_applier(
274        &mut self,
275        applier: &mut cranpose_core::MemoryApplier,
276        root: cranpose_core::NodeId,
277        _viewport: Size,
278        updates: SceneUpdates<'_>,
279    ) -> Result<(), Self::Error> {
280        self.scene.update_from_applier(applier, root, updates, true);
281        Ok(())
282    }
283
284    fn update_visual_scene_from_applier(
285        &mut self,
286        applier: &mut cranpose_core::MemoryApplier,
287        root: cranpose_core::NodeId,
288        _viewport: Size,
289        updates: SceneUpdates<'_>,
290    ) -> Result<(), Self::Error> {
291        self.scene
292            .update_from_applier(applier, root, updates, false);
293        Ok(())
294    }
295}