Skip to main content

cranpose_render_pixels/
lib.rs

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