From 5ca8f08ea7530b923d88936bc9201f604b331d5d Mon Sep 17 00:00:00 2001 From: Olivier Goffart Date: Tue, 14 Jun 2022 16:00:05 +0200 Subject: [PATCH] swrenderer: add a renderer that operate dirrectly onh the frame buffer --- internal/backends/mcu/lib.rs | 42 ++++++- internal/backends/mcu/simulator.rs | 8 +- internal/backends/mcu/stm32h735g.rs | 4 + internal/core/swrenderer.rs | 139 ++++++++++++++++++--- internal/core/swrenderer/draw_functions.rs | 19 ++- 5 files changed, 179 insertions(+), 33 deletions(-) diff --git a/internal/backends/mcu/lib.rs b/internal/backends/mcu/lib.rs index 9c9b4ff84a..494736cb4e 100644 --- a/internal/backends/mcu/lib.rs +++ b/internal/backends/mcu/lib.rs @@ -31,6 +31,11 @@ pub type TargetPixel = embedded_graphics::pixelcolor::Rgb565; pub trait Devices { fn screen_size(&self) -> PhysicalSize; + /// If the device supports it, return the target buffer where to draw the frame. Must be width * height large. + /// Also return the dirty area. + fn get_buffer(&mut self) -> Option<(&mut [TargetPixel], PhysicalRect)> { + None + } /// Called before the frame is being drawn, with the dirty region. Return the actual dirty region fn prepare_frame(&mut self, dirty_region: PhysicalRect) -> PhysicalRect { dirty_region @@ -87,7 +92,7 @@ where } thread_local! { static DEVICES: RefCell>> = RefCell::new(None) } -thread_local! { static LINE_RENDERER: RefCell = RefCell::new(Default::default()) } +thread_local! { static RENDERER: RefCell = RefCell::new(Default::default()) } mod the_backend { use super::*; @@ -133,7 +138,7 @@ mod the_backend { _: i_slint_core::component::ComponentRef, items: &mut dyn Iterator>>, ) { - super::LINE_RENDERER.with(|renderer| { + super::RENDERER.with(|renderer| { renderer.borrow().free_graphics_resources(items); }); } @@ -276,9 +281,35 @@ mod the_backend { DEVICES.with(|devices| { let mut devices = devices.borrow_mut(); let devices = devices.as_mut().unwrap(); - let size = devices.screen_size().to_f32() / runtime_window.scale_factor(); + let mut frame_profiler = profiler::Timer::new(&**devices); + let screen_size = devices.screen_size(); + let scale_factor = runtime_window.scale_factor(); + let size = screen_size.to_f32() / scale_factor; runtime_window.set_window_item_geometry(size.width as _, size.height as _); + if let Some((buffer, prev_dirty)) = devices.get_buffer() { + let init_dirty = PhysicalRect::from_untyped( + &window + .initial_dirty_region_for_next_frame + .take() + .to_rect() + .scale(scale_factor, scale_factor) + .cast(), + ); + let new_dirty_region = RENDERER.with(|renderer| { + renderer.borrow().render( + runtime_window, + init_dirty.union(&prev_dirty), + buffer, + screen_size.width_length(), + ) + }); + devices.prepare_frame(new_dirty_region.union(&init_dirty)); + devices.flush_frame(); + frame_profiler.stop_profiling(&mut **devices, "=> frame total"); + return; + } + struct BufferProvider<'a> { screen_fill_profiler: profiler::Timer, span_drawing_profiler: profiler::Timer, @@ -348,13 +379,14 @@ mod the_backend { dirty_region: PhysicalRect::default(), }; - LINE_RENDERER.with(|renderer| { - renderer.borrow().render( + RENDERER.with(|renderer| { + renderer.borrow().render_by_line( runtime_window, window.initial_dirty_region_for_next_frame.take(), buffer_provider, ) }); + frame_profiler.stop_profiling(&mut **devices, "=> frame total"); }); } } diff --git a/internal/backends/mcu/simulator.rs b/internal/backends/mcu/simulator.rs index 7837484704..bffd71864a 100644 --- a/internal/backends/mcu/simulator.rs +++ b/internal/backends/mcu/simulator.rs @@ -142,7 +142,7 @@ impl PlatformWindow for SimulatorWindow { _: i_slint_core::component::ComponentRef, items: &mut dyn Iterator>>, ) { - super::LINE_RENDERER.with(|cache| { + super::RENDERER.with(|cache| { cache.borrow().free_graphics_resources(items); }); } @@ -298,7 +298,7 @@ impl WinitWindow for SimulatorWindow { width: size.width, height: size.height, })); - super::LINE_RENDERER.with(|cache| { + super::RENDERER.with(|cache| { *cache.borrow_mut() = Default::default(); }); buffer @@ -324,8 +324,8 @@ impl WinitWindow for SimulatorWindow { ); } } - super::LINE_RENDERER.with(|renderer| { - renderer.borrow().render( + super::RENDERER.with(|renderer| { + renderer.borrow().render_by_line( runtime_window, self.initial_dirty_region_for_next_frame.take(), BufferProvider { diff --git a/internal/backends/mcu/stm32h735g.rs b/internal/backends/mcu/stm32h735g.rs index c185ac44cf..661c912a03 100644 --- a/internal/backends/mcu/stm32h735g.rs +++ b/internal/backends/mcu/stm32h735g.rs @@ -242,6 +242,10 @@ impl Devices for StmDevices { PhysicalSize::new(DISPLAY_WIDTH as _, DISPLAY_HEIGHT as _) } + fn get_buffer(&mut self) -> Option<(&mut [TargetPixel], PhysicalRect)> { + Some((self.work_fb, self.prev_dirty)) + } + fn prepare_frame(&mut self, dirty_region: PhysicalRect) -> PhysicalRect { dirty_region.union(&core::mem::replace(&mut self.prev_dirty, dirty_region)) } diff --git a/internal/core/swrenderer.rs b/internal/core/swrenderer.rs index 5aecde0177..8a97f8244b 100644 --- a/internal/core/swrenderer.rs +++ b/internal/core/swrenderer.rs @@ -41,11 +41,74 @@ pub trait LineBufferProvider { } #[derive(Default)] -pub struct LineRenderer { +pub struct SoftwareRenderer { partial_cache: RefCell, } -impl LineRenderer { +impl SoftwareRenderer { + /// Render the window to the given frame buffer. + /// + /// returns the dirty region for this frame (not including the initial_dirty_region) + pub fn render( + &self, + window: Rc, + initial_dirty_region: DirtyRegion, + buffer: &mut [impl TargetPixel], + buffer_stride: PhysicalLength, + ) -> DirtyRegion { + let component_rc = window.component(); + let component = crate::component::ComponentRc::borrow_pin(&component_rc); + let factor = ScaleFactor::new(window.scale_factor()); + let size = if let Some(window_item) = crate::items::ItemRef::downcast_pin::< + crate::items::WindowItem, + >(component.as_ref().get_item_ref(0)) + { + (euclid::size2(window_item.width() as f32, window_item.height() as f32) * factor).cast() + } else { + euclid::size2(buffer_stride.get(), (buffer.len() / (buffer_stride.get() as usize)) as _) + }; + let buffer_renderer = SceneBuilder::new( + size, + factor, + window.default_font_properties(), + RenderToBuffer { buffer, stride: buffer_stride }, + ); + let mut renderer = crate::item_rendering::PartialRenderer::new( + &self.partial_cache, + Default::default(), + buffer_renderer, + ); + + let mut dirty_region = PhysicalRect::default(); + window.draw_contents(|components| { + for (component, origin) in components { + renderer.compute_dirty_regions(component, *origin); + } + + dirty_region = (LogicalRect::from_untyped(&renderer.dirty_region.to_rect()).cast() + * factor) + .round_out() + .cast(); + + renderer.combine_clip( + (dirty_region + .union(&initial_dirty_region) + .intersection(&PhysicalRect { origin: euclid::point2(0, 0), size }) + .unwrap_or_default() + .cast() + / factor) + .to_untyped() + .cast(), + 0 as _, + 0 as _, + ); + for (component, origin) in components { + crate::item_rendering::render_component_items(component, &mut renderer, *origin); + } + }); + dirty_region + } + /// Render the window, line by line, into the buffer provided by the `line_buffer` function. /// /// The renderer uses a cache internally and will only render the part of the window @@ -56,7 +119,7 @@ impl LineRenderer { /// TODO: what about async and threading. /// (can we call the line_buffer function from different thread?) /// TODO: should `initial_dirty_region` be set from a different call? - pub fn render( + pub fn render_by_line( &self, window: Rc, initial_dirty_region: crate::item_rendering::DirtyRegion, @@ -69,7 +132,7 @@ impl LineRenderer { ) { let size = euclid::size2(window_item.width() as f32, window_item.height() as f32) * ScaleFactor::new(window.scale_factor()); - render_window_frame( + render_window_frame_by_line( window, window_item.background(), size.cast(), @@ -92,7 +155,7 @@ impl LineRenderer { } } -fn render_window_frame( +fn render_window_frame_by_line( runtime_window: Rc, background: Color, size: PhysicalSize, @@ -124,7 +187,7 @@ fn render_window_frame( SceneCommand::Texture { texture_index } => { let texture = &scene.textures[texture_index as usize]; draw_functions::draw_texture_line( - span, + &PhysicalRect{ origin: span.pos, size: span.size } , scene.current_line, texture, line_buffer, @@ -133,7 +196,7 @@ fn render_window_frame( SceneCommand::RoundedRectangle { rectangle_index } => { let rr = &scene.rounded_rectangles[rectangle_index as usize]; draw_functions::draw_rounded_rectangle_line( - span, + &PhysicalRect{ origin: span.pos, size: span.size } , scene.current_line, rr, line_buffer, @@ -372,8 +435,12 @@ fn prepare_scene( cache: &RefCell, ) -> Scene { let factor = ScaleFactor::new(runtime_window.scale_factor()); - let prepare_scene = - SceneBuilder::::new(size, factor, runtime_window.default_font_properties()); + let prepare_scene = SceneBuilder::new( + size, + factor, + runtime_window.default_font_properties(), + PrepareScene::default(), + ); let mut renderer = crate::item_rendering::PartialRenderer::new(cache, initial_dirty_region, prepare_scene); @@ -412,6 +479,45 @@ trait ProcessScene { fn process_rounded_rectangle(&mut self, geometry: PhysicalRect, data: RoundedRectangle); } +struct RenderToBuffer<'a, TargetPixel> { + buffer: &'a mut [TargetPixel], + stride: PhysicalLength, +} + +impl<'a, T: TargetPixel> ProcessScene for RenderToBuffer<'a, T> { + fn process_texture(&mut self, geometry: PhysicalRect, texture: SceneTexture) { + for line in geometry.min_y()..geometry.max_y() { + draw_functions::draw_texture_line( + &geometry, + PhysicalLength::new(line), + &texture, + &mut self.buffer[line as usize * self.stride.get() as usize..], + ); + } + } + + fn process_rectangle(&mut self, geometry: PhysicalRect, color: Color) { + for line in geometry.min_y()..geometry.max_y() { + let begin = line as usize * self.stride.get() as usize + geometry.origin.x as usize; + TargetPixel::blend_buffer( + &mut self.buffer[begin..begin + geometry.width() as usize], + color, + ); + } + } + + fn process_rounded_rectangle(&mut self, geometry: PhysicalRect, rr: RoundedRectangle) { + for line in geometry.min_y()..geometry.max_y() { + draw_functions::draw_rounded_rectangle_line( + &geometry, + PhysicalLength::new(line), + &rr, + &mut self.buffer[line as usize * self.stride.get() as usize..], + ); + } + } +} + #[derive(Default)] struct PrepareScene { items: Vec, @@ -466,10 +572,15 @@ struct SceneBuilder { default_font: FontRequest, } -impl SceneBuilder { - fn new(size: PhysicalSize, scale_factor: ScaleFactor, default_font: FontRequest) -> Self { +impl SceneBuilder { + fn new( + size: PhysicalSize, + scale_factor: ScaleFactor, + default_font: FontRequest, + processor: T, + ) -> Self { Self { - processor: Default::default(), + processor, state_stack: vec![], current_state: RenderState { alpha: 1., @@ -597,7 +708,7 @@ struct RenderState { clip: LogicalRect, } -impl crate::item_rendering::ItemRenderer for SceneBuilder { +impl crate::item_rendering::ItemRenderer for SceneBuilder { fn draw_rectangle(&mut self, rect: Pin<&crate::items::Rectangle>, _: &ItemRc) { let geom = LogicalRect::new(LogicalPoint::default(), rect.logical_geometry().size_length()); if self.should_draw(&geom) { @@ -891,7 +1002,7 @@ impl crate::item_rendering::ItemRenderer fo } fn as_any(&mut self) -> &mut dyn core::any::Any { - self + unimplemented!() } } diff --git a/internal/core/swrenderer/draw_functions.rs b/internal/core/swrenderer/draw_functions.rs index a9fc07b8aa..1d45b3004e 100644 --- a/internal/core/swrenderer/draw_functions.rs +++ b/internal/core/swrenderer/draw_functions.rs @@ -4,9 +4,8 @@ //! This is the module for the functions that are drawing the pixels //! on the line buffer -use super::{SceneItem, SceneTexture}; use crate::graphics::PixelFormat; -use crate::lengths::{PhysicalLength, PointLengths, SizeLengths}; +use crate::lengths::{PhysicalLength, PhysicalRect, PointLengths, SizeLengths}; use crate::Color; use derive_more::{Add, Mul, Sub}; #[cfg(feature = "embedded-graphics")] @@ -15,19 +14,19 @@ use integer_sqrt::IntegerSquareRoot; /// Draw one line of the texture in the line buffer pub(super) fn draw_texture_line( - span: &SceneItem, + span: &PhysicalRect, line: PhysicalLength, texture: &super::SceneTexture, line_buffer: &mut [impl TargetPixel], ) { - let SceneTexture { data, format, stride, source_size, color } = *texture; + let super::SceneTexture { data, format, stride, source_size, color } = *texture; let source_size = source_size.cast::(); let span_size = span.size.cast::(); let bpp = super::bpp(format) as usize; - let y = (line - span.pos.y_length()).cast::(); + let y = (line - span.origin.y_length()).cast::(); let y_pos = (y.get() * source_size.height / span_size.height) * stride as usize; for (x, pix) in line_buffer - [span.pos.x as usize..(span.pos.x_length() + span.size.width_length()).get() as usize] + [span.origin.x as usize..(span.origin.x_length() + span.size.width_length()).get() as usize] .iter_mut() .enumerate() { @@ -77,7 +76,7 @@ pub(super) fn draw_texture_line( /// draw one line of the rounded rectangle in the line buffer pub(super) fn draw_rounded_rectangle_line( - span: &SceneItem, + span: &PhysicalRect, line: PhysicalLength, rr: &super::RoundedRectangle, line_buffer: &mut [impl TargetPixel], @@ -111,9 +110,9 @@ pub(super) fn draw_rounded_rectangle_line( Self(self.0 * rhs.0) } } - let pos_x = span.pos.x as usize; - let y1 = (line - span.pos.y_length()) + rr.top_clip; - let y2 = (span.pos.y_length() + span.size.height_length() - line) + rr.bottom_clip + let pos_x = span.origin.x as usize; + let y1 = (line - span.origin.y_length()) + rr.top_clip; + let y2 = (span.origin.y_length() + span.size.height_length() - line) + rr.bottom_clip - PhysicalLength::new(1); let y = y1.min(y2); debug_assert!(y.get() >= 0,);