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 growth_trim;
7mod pipeline;
8mod pixel;
9mod rewrite;
10/// The retained scene and hit regions of the pixel renderer.
11pub mod scene;
12mod scene_item;
13pub mod sprites;
14mod style;
15mod surface;
16#[cfg(test)]
17#[path = "tests/text_draws.rs"]
18mod text_draws;
19
20pub use cranpose_render_common::rgb565::Rgb565Order;
21use cranpose_render_common::{
22    RenderScene, Renderer, SceneUpdates,
23    rgb565::{Rgb565Be, Rgb565Le},
24    text_fonts::TextFonts,
25};
26use cranpose_ui::LayoutTree;
27use cranpose_ui_graphics::{MemoryHeadroom, Size};
28pub use draw::draw_scene;
29use pixel::Pixel;
30pub use scene::{HitRegion, Scene};
31
32/// A rectangle of device pixels: columns `x..x + width` of rows
33/// `y..y + height`.
34#[derive(Clone, Copy, Debug, PartialEq, Eq)]
35pub struct PixelRect {
36    /// The first column.
37    pub x: u32,
38    /// The first row.
39    pub y: u32,
40    /// The number of columns.
41    pub width: u32,
42    /// The number of rows.
43    pub height: u32,
44}
45
46fn pixel_rect(bounds: draw::ClipBounds) -> PixelRect {
47    PixelRect {
48        x: bounds.min_x.max(0) as u32,
49        y: bounds.min_y.max(0) as u32,
50        width: (bounds.max_x - bounds.min_x).max(0) as u32,
51        height: (bounds.max_y - bounds.min_y).max(0) as u32,
52    }
53}
54
55/// The picture a host drew last. [`PixelsRenderer::update_picture`] moves it
56/// to the current scene and finds the pixels that change, and
57/// [`PixelsRenderer::draw_picture_area`] draws any part of it.
58#[derive(Default)]
59pub struct Picture {
60    raster: Option<scene::RasterScene>,
61    /// What an update in place pushes each changed layer again with.
62    rewrite: Option<Box<rewrite::Rewrite>>,
63    width: u32,
64    height: u32,
65    scale: f32,
66    changed: Vec<draw::ClipBounds>,
67    // Where each item of `raster` can draw in the frame, in its order: each
68    // strip a host draws passes over the items that miss it.
69    reach: Vec<Option<draw::ClipBounds>>,
70}
71
72fn valid_scale(scale: f32) -> f32 {
73    if scale.is_finite() && scale > 0.0 {
74        scale
75    } else {
76        1.0
77    }
78}
79
80/// A renderer that draws the scene into a caller-owned RGBA pixel buffer on the CPU.
81pub struct PixelsRenderer {
82    scene: Scene,
83    text: draw::TextRaster,
84    /// The app context this renderer gave its text measurer.
85    app_context: Option<std::rc::Weak<cranpose_ui::AppContext>>,
86}
87
88impl Default for PixelsRenderer {
89    fn default() -> Self {
90        Self::new()
91    }
92}
93
94impl PixelsRenderer {
95    /// Creates a renderer that draws text in the embedded default font when the
96    /// `embedded-default-font` feature is on, and draws no text otherwise.
97    pub fn new() -> Self {
98        Self::with_fonts(&[])
99    }
100
101    /// A renderer that draws text in `fonts`. With none that parses, it uses
102    /// the embedded default font when the `embedded-default-font` feature is
103    /// on, and draws no text otherwise.
104    pub fn with_fonts(fonts: &[&'static [u8]]) -> Self {
105        Self {
106            scene: Scene::new(),
107            text: draw::TextRaster::new(TextFonts::from_fonts_or_default(fonts)),
108            app_context: None,
109        }
110    }
111
112    /// Draws the scene into `frame`, an RGBA8 buffer of `width` by `height` pixels, at one device
113    /// pixel per logical pixel.
114    pub fn draw(&self, frame: &mut [u8], width: u32, height: u32) {
115        draw::draw_scene_scaled(frame, width, height, &self.scene, &self.text, 1.0);
116    }
117
118    /// Rasterizes logical scene coordinates at `scale` device pixels per point.
119    /// Hit regions stay in logical coordinates. Invalid scales use 1.0.
120    pub fn draw_scaled(&self, frame: &mut [u8], width: u32, height: u32, scale: f32) {
121        draw::draw_scene_scaled(
122            frame,
123            width,
124            height,
125            &self.scene,
126            &self.text,
127            valid_scale(scale),
128        );
129    }
130
131    /// Fills `raster` with the scene at `scale`, keeping its storage, and
132    /// copying from `previous`, the frame before's, what did not change.
133    fn fill_raster_scene(
134        &self,
135        raster: &mut scene::RasterScene,
136        scale: f32,
137        previous: Option<&scene::RasterScene>,
138    ) {
139        match self.scene.graph.as_ref() {
140            Some(graph) => pipeline::fill_raster_scene(
141                raster,
142                graph,
143                self.scene.diagnostics(),
144                scale,
145                previous,
146                &self.text,
147            ),
148            None => raster.clear(),
149        }
150    }
151
152    /// Moves `picture` to the current scene, drawn `width` by `height` device
153    /// pixels at `scale` device pixels per point, and puts into `changed` the
154    /// areas whose pixels differ from the picture it held: the whole frame the
155    /// first time and after a change of size or scale. Invalid scales use 1.0.
156    pub fn update_picture(
157        &self,
158        picture: &mut Picture,
159        width: u32,
160        height: u32,
161        scale: f32,
162        changed: &mut Vec<PixelRect>,
163    ) {
164        let scale = valid_scale(scale);
165        if let Some(reach) = self.update_picture_in_place(picture, width, height, scale) {
166            changed.clear();
167            changed.extend(picture.changed.iter().copied().map(pixel_rect));
168            match reach {
169                pipeline::UpdatedReach::Held => {}
170                pipeline::UpdatedReach::Lost => picture.measure_reach(),
171            }
172            // With little memory left, the spill an update took for a
173            // subtree that pushed other items than before goes, and so does
174            // the room the scene's lists grew past their items.
175            if cranpose_ui::current_memory_headroom() == MemoryHeadroom::Tight {
176                if let Some(rewrite) = picture.rewrite.as_mut() {
177                    rewrite.release_spill();
178                }
179                growth_trim::trim(picture);
180            }
181            return;
182        }
183        // An update in place that gave up after it changed part of the scene
184        // left the areas it changed: the scene holds those changes already.
185        let updated = (!picture.changed.is_empty()).then(|| std::mem::take(&mut picture.changed));
186        let mut raster = picture
187            .rewrite
188            .as_mut()
189            .map(|rewrite| rewrite.take_storage())
190            .unwrap_or_default();
191        self.fill_raster_scene(&mut raster, scale, picture.raster.as_ref());
192        changed.clear();
193        match picture.raster.as_ref() {
194            Some(old)
195                if (picture.width, picture.height, picture.scale) == (width, height, scale) =>
196            {
197                damage::changed_areas(
198                    old,
199                    &picture.reach,
200                    &raster,
201                    picture.frame(),
202                    &mut picture.changed,
203                );
204                if let Some(mut updated) = updated {
205                    picture.changed.append(&mut updated);
206                    damage::merge_areas(&mut picture.changed);
207                }
208                changed.extend(picture.changed.iter().copied().map(pixel_rect));
209            }
210            _ if width > 0 && height > 0 => changed.push(PixelRect {
211                x: 0,
212                y: 0,
213                width,
214                height,
215            }),
216            _ => {}
217        }
218        picture.raster = Some(raster);
219        picture.width = width;
220        picture.height = height;
221        picture.scale = scale;
222        picture.measure_reach();
223        if cranpose_ui::current_memory_headroom() == MemoryHeadroom::Tight {
224            growth_trim::trim(picture);
225        }
226    }
227
228    // Brings `picture`'s scene up to the current scene in place, and puts
229    // the areas the changed layers changed into `picture.changed`. `None`
230    // when the scene must be built again: the first time, after a change of
231    // size, scale or light, or of the root layer, or when a layer's records
232    // no longer lie in one run, with the areas the update changed before it
233    // stopped in `picture.changed`.
234    fn update_picture_in_place(
235        &self,
236        picture: &mut Picture,
237        width: u32,
238        height: u32,
239        scale: f32,
240    ) -> Option<pipeline::UpdatedReach> {
241        picture.changed.clear();
242        let (Some(raster), Some(graph)) = (picture.raster.as_mut(), self.scene.graph.as_ref())
243        else {
244            return None;
245        };
246        if (picture.width, picture.height, picture.scale) != (width, height, scale) {
247            return None;
248        }
249        let window = draw::ClipBounds {
250            min_x: 0,
251            min_y: 0,
252            max_x: i32::try_from(width).unwrap_or(i32::MAX),
253            max_y: i32::try_from(height).unwrap_or(i32::MAX),
254        };
255        pipeline::InPlaceUpdate {
256            scene: raster,
257            reach: &mut picture.reach,
258            rewrite: &mut picture.rewrite,
259        }
260        .run(
261            graph,
262            self.scene.diagnostics(),
263            scale,
264            &self.text,
265            window,
266            &mut picture.changed,
267        )
268    }
269
270    /// Rasterizes the glyphs of the text in `area` of `picture` into the
271    /// renderer's glyph cache, so that drawing the area later takes them from
272    /// it. A host calls it while it waits anyway, such as for a display that
273    /// is still starting up.
274    pub fn prepare_picture_area(&self, picture: &Picture, area: PixelRect) {
275        let Some(raster) = picture.raster.as_ref() else {
276            return;
277        };
278        let window = draw::ClipBounds {
279            min_x: i32::try_from(area.x).unwrap_or(i32::MAX),
280            min_y: i32::try_from(area.y).unwrap_or(i32::MAX),
281            max_x: i32::try_from(area.x.saturating_add(area.width)).unwrap_or(i32::MAX),
282            max_y: i32::try_from(area.y.saturating_add(area.height)).unwrap_or(i32::MAX),
283        };
284        draw::prepare_raster_window(raster, window, &self.text);
285    }
286
287    /// Draws `area` of `picture` into `pixels` as straight-alpha RGBA8 rows of
288    /// `area.width` pixels, `area.height` of them. Draws nothing before the
289    /// first [`Self::update_picture`] or when `pixels` is shorter than the area.
290    pub fn draw_picture_area(&self, picture: &Picture, area: PixelRect, pixels: &mut [u8]) {
291        self.draw_picture_area_as(picture, area, pixels.as_chunks_mut::<4>().0);
292    }
293
294    /// Draws `area` of `picture` into `pixels` as rows of 16-bit pixels, 5
295    /// bits of red, 6 of green and 5 of blue, stored in `order`, as a display
296    /// takes them: `area.width` pixels a row, `area.height` rows. Each pixel
297    /// keeps the high bits of the bytes [`Self::draw_picture_area`] draws,
298    /// except where a drawing blends over another, which blends over the
299    /// 16-bit pixel.
300    pub fn draw_picture_area_rgb565(
301        &self,
302        picture: &Picture,
303        area: PixelRect,
304        pixels: &mut [u16],
305        order: Rgb565Order,
306    ) {
307        match order {
308            Rgb565Order::LittleEndian => self.draw_picture_area_as::<Rgb565Le>(
309                picture,
310                area,
311                bytemuck::cast_slice_mut(pixels),
312            ),
313            Rgb565Order::BigEndian => self.draw_picture_area_as::<Rgb565Be>(
314                picture,
315                area,
316                bytemuck::cast_slice_mut(pixels),
317            ),
318        }
319    }
320
321    // Out of line, one function a format: a display takes one byte order, and
322    // a build that keeps its drawing in fast memory keeps only that one.
323    #[inline(never)]
324    fn draw_picture_area_as<P: Pixel>(&self, picture: &Picture, area: PixelRect, pixels: &mut [P]) {
325        let Some(raster) = picture.raster.as_ref() else {
326            return;
327        };
328        let len = area.width as usize * area.height as usize;
329        let Some(pixels) = pixels.get_mut(..len) else {
330            return;
331        };
332        let mut target = draw::Target {
333            pixels,
334            left: i32::try_from(area.x).unwrap_or(i32::MAX),
335            top: i32::try_from(area.y).unwrap_or(i32::MAX),
336            width: area.width,
337            height: area.height,
338        };
339        draw::draw_raster_window(
340            &mut target,
341            raster,
342            &picture.reach,
343            self.scene.diagnostics(),
344            &self.text,
345        );
346    }
347
348    /// The scene at `scale` device pixels per point as sprites for a GPU
349    /// compositor, into `frame`, which keeps its textures from the frames
350    /// before. Invalid scales use 1.0.
351    pub fn compose_sprites(
352        &self,
353        frame: &mut sprites::SpriteFrame,
354        width: u32,
355        height: u32,
356        scale: f32,
357    ) {
358        let mut scene = std::mem::take(&mut frame.scene);
359        self.fill_raster_scene(&mut scene, valid_scale(scale), None);
360        frame.compose(&scene, width, height, self.scene.diagnostics(), &self.text);
361        frame.scene = scene;
362    }
363}
364
365impl Picture {
366    fn frame(&self) -> draw::ClipBounds {
367        draw::ClipBounds {
368            min_x: 0,
369            min_y: 0,
370            max_x: i32::try_from(self.width).unwrap_or(i32::MAX),
371            max_y: i32::try_from(self.height).unwrap_or(i32::MAX),
372        }
373    }
374
375    fn measure_reach(&mut self) {
376        let frame = self.frame();
377        match self.raster.as_ref() {
378            Some(raster) => raster.reach_into(frame, &mut self.reach),
379            None => self.reach.clear(),
380        }
381    }
382}
383
384impl Renderer for PixelsRenderer {
385    type Scene = Scene;
386    type Error = std::convert::Infallible;
387
388    fn attach_app_context_services(&mut self, app_context: &cranpose_ui::AppContext) {
389        app_context.set_text_fonts(self.text.fonts.resolver());
390        self.app_context = Some(app_context.downgrade());
391    }
392
393    #[cfg(feature = "installed-fonts")]
394    fn apply_installed_fonts(&mut self, app_context: &cranpose_ui::AppContext) {
395        let Some(fonts) = self.text.fonts.with_installed_faces() else {
396            return;
397        };
398        self.text = draw::TextRaster::new(fonts);
399        let attached = self
400            .app_context
401            .as_ref()
402            .and_then(std::rc::Weak::upgrade)
403            .is_some_and(|attached| std::ptr::eq(&*attached, app_context));
404        if attached {
405            self.attach_app_context_services(app_context);
406        }
407    }
408
409    fn scene(&self) -> &Self::Scene {
410        &self.scene
411    }
412
413    fn scene_mut(&mut self) -> &mut Self::Scene {
414        &mut self.scene
415    }
416
417    fn rebuild_scene(
418        &mut self,
419        layout_tree: &LayoutTree,
420        _viewport: Size,
421    ) -> Result<(), Self::Error> {
422        self.scene.clear();
423        pipeline::render_layout_tree(layout_tree.root(), &mut self.scene);
424        Ok(())
425    }
426
427    fn rebuild_scene_from_applier(
428        &mut self,
429        applier: &mut cranpose_core::MemoryApplier,
430        root: cranpose_core::NodeId,
431        _viewport: Size,
432    ) -> Result<(), Self::Error> {
433        self.scene.rebuild_from_applier(applier, root);
434        Ok(())
435    }
436
437    fn update_scene_from_applier(
438        &mut self,
439        applier: &mut cranpose_core::MemoryApplier,
440        root: cranpose_core::NodeId,
441        _viewport: Size,
442        updates: SceneUpdates<'_>,
443    ) -> Result<(), Self::Error> {
444        self.scene.update_from_applier(applier, root, updates);
445        Ok(())
446    }
447}