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