From 40c4b017492461143eaade2ef54da2756b142ef4 Mon Sep 17 00:00:00 2001 From: Simon Hausmann Date: Thu, 20 Jun 2024 16:10:35 +0200 Subject: [PATCH] Add Rust API to grab the contents of a slint::Window into a slint::SharedImageBuffer --- .../linuxkms/fullscreenwindowadapter.rs | 6 + .../backends/linuxkms/renderer/femtovg.rs | 5 + internal/backends/linuxkms/renderer/skia.rs | 5 + internal/backends/linuxkms/renderer/sw.rs | 36 ++- internal/backends/qt/qt_window.rs | 22 ++ internal/backends/winit/lib.rs | 7 +- internal/backends/winit/renderer/femtovg.rs | 7 + internal/backends/winit/renderer/skia.rs | 7 + internal/backends/winit/renderer/sw.rs | 47 +++- internal/backends/winit/winitwindowadapter.rs | 4 + internal/core/api.rs | 9 + internal/core/window.rs | 8 +- internal/renderers/femtovg/lib.rs | 21 +- internal/renderers/skia/lib.rs | 215 +++++++++++------- 14 files changed, 302 insertions(+), 97 deletions(-) diff --git a/internal/backends/linuxkms/fullscreenwindowadapter.rs b/internal/backends/linuxkms/fullscreenwindowadapter.rs index 630f2638647..9b08ae9af17 100644 --- a/internal/backends/linuxkms/fullscreenwindowadapter.rs +++ b/internal/backends/linuxkms/fullscreenwindowadapter.rs @@ -8,6 +8,7 @@ use std::pin::Pin; use std::rc::Rc; use i_slint_core::api::{LogicalPosition, PhysicalSize as PhysicalWindowSize}; +use i_slint_core::graphics::SharedImageBuffer; use i_slint_core::graphics::{euclid, Image}; use i_slint_core::item_rendering::ItemRenderer; use i_slint_core::lengths::LogicalRect; @@ -32,6 +33,7 @@ pub trait FullscreenRenderer { &self, event_loop_handle: crate::calloop_backend::EventLoopHandle, ) -> Result<(), PlatformError>; + fn grab_window(&self) -> Result; } pub struct FullscreenWindowAdapter { @@ -69,6 +71,10 @@ impl WindowAdapter for FullscreenWindowAdapter { } Ok(()) } + + fn grab_window(&self) -> Result { + self.renderer.grab_window() + } } impl FullscreenWindowAdapter { diff --git a/internal/backends/linuxkms/renderer/femtovg.rs b/internal/backends/linuxkms/renderer/femtovg.rs index ec8dbe387e2..cf499cdeec7 100644 --- a/internal/backends/linuxkms/renderer/femtovg.rs +++ b/internal/backends/linuxkms/renderer/femtovg.rs @@ -3,6 +3,7 @@ use std::{num::NonZeroU32, rc::Rc}; +use i_slint_core::graphics::SharedImageBuffer; use i_slint_core::item_rendering::ItemRenderer; use i_slint_core::platform::PlatformError; use i_slint_renderer_femtovg::FemtoVGRendererExt; @@ -201,4 +202,8 @@ impl crate::fullscreenwindowadapter::FullscreenRenderer for FemtoVGRendererAdapt ) -> Result<(), PlatformError> { self.gbm_display.clone().register_page_flip_handler(event_loop_handle) } + + fn grab_window(&self) -> Result { + self.renderer.screenshot() + } } diff --git a/internal/backends/linuxkms/renderer/skia.rs b/internal/backends/linuxkms/renderer/skia.rs index 3b932234692..c48b93a9789 100644 --- a/internal/backends/linuxkms/renderer/skia.rs +++ b/internal/backends/linuxkms/renderer/skia.rs @@ -6,6 +6,7 @@ use std::rc::Rc; use crate::display::RenderingRotation; use crate::drmoutput::DrmOutput; use i_slint_core::api::PhysicalSize as PhysicalWindowSize; +use i_slint_core::graphics::SharedImageBuffer; use i_slint_core::item_rendering::ItemRenderer; use i_slint_core::platform::PlatformError; use i_slint_renderer_skia::skia_safe; @@ -162,6 +163,10 @@ impl crate::fullscreenwindowadapter::FullscreenRenderer for SkiaRendererAdapter ) -> Result<(), PlatformError> { self.presenter.clone().register_page_flip_handler(event_loop_handle) } + + fn grab_window(&self) -> Result { + self.renderer.screenshot() + } } struct DrmDumbBufferAccess { display: Rc, diff --git a/internal/backends/linuxkms/renderer/sw.rs b/internal/backends/linuxkms/renderer/sw.rs index d5e0e38c8be..d6fa9c73501 100644 --- a/internal/backends/linuxkms/renderer/sw.rs +++ b/internal/backends/linuxkms/renderer/sw.rs @@ -4,6 +4,7 @@ //! Delegate the rendering to the [`i_slint_core::software_renderer::SoftwareRenderer`] use i_slint_core::api::PhysicalSize as PhysicalWindowSize; +use i_slint_core::graphics::SharedImageBuffer; use i_slint_core::platform::PlatformError; pub use i_slint_core::software_renderer::SoftwareRenderer; use i_slint_core::software_renderer::{PremultipliedRgbaColor, RepaintBufferType, TargetPixel}; @@ -16,6 +17,7 @@ pub struct SoftwareRendererAdapter { renderer: SoftwareRenderer, display: Rc, size: PhysicalWindowSize, + force_next_frame_new_buffer: Cell, } #[repr(transparent)] @@ -73,7 +75,12 @@ impl SoftwareRendererAdapter { let (width, height) = display.drm_output.size(); let size = i_slint_core::api::PhysicalSize::new(width, height); - let renderer = Box::new(Self { renderer: SoftwareRenderer::new(), display, size }); + let renderer = Box::new(Self { + renderer: SoftwareRenderer::new(), + display, + size, + force_next_frame_new_buffer: Default::default(), + }); eprintln!("Using Software renderer"); @@ -97,10 +104,14 @@ impl crate::fullscreenwindowadapter::FullscreenRenderer for SoftwareRendererAdap ready_for_next_animation_frame: Box, ) -> Result<(), PlatformError> { self.display.map_back_buffer(&mut |mut pixels, age| { - self.renderer.set_repaint_buffer_type(match age { - 1 => RepaintBufferType::ReusedBuffer, - 2 => RepaintBufferType::SwappedBuffers, - _ => RepaintBufferType::NewBuffer, + self.renderer.set_repaint_buffer_type(if self.force_next_frame_new_buffer.take() { + RepaintBufferType::NewBuffer + } else { + match age { + 1 => RepaintBufferType::ReusedBuffer, + 2 => RepaintBufferType::SwappedBuffers, + _ => RepaintBufferType::NewBuffer, + } }); self.renderer.set_rendering_rotation(match rotation { @@ -137,4 +148,19 @@ impl crate::fullscreenwindowadapter::FullscreenRenderer for SoftwareRendererAdap ) -> Result<(), PlatformError> { self.display.drm_output.register_page_flip_handler(event_loop_handle) } + + fn grab_window(&self) -> Result { + let width = self.size.width; + let height = self.size.height; + + let mut target_buffer = + SharedPixelBuffer::::new(width, height); + + self.force_next_frame_new_buffer.set(true); + self.renderer.set_repaint_buffer_type(RepaintBufferType::NewBuffer); + + let _ = self.renderer.render(target_buffer.make_mut_slice(), width as usize); + + Ok(SharedImageBuffer::RGB8(target_buffer)) + } } diff --git a/internal/backends/qt/qt_window.rs b/internal/backends/qt/qt_window.rs index 22b824f6968..45705488c95 100644 --- a/internal/backends/qt/qt_window.rs +++ b/internal/backends/qt/qt_window.rs @@ -1979,6 +1979,28 @@ impl WindowAdapter for QtWindow { fn internal(&self, _: i_slint_core::InternalToken) -> Option<&dyn WindowAdapterInternal> { Some(self) } + + fn grab_window(&self) -> Result { + let widget_ptr = self.widget_ptr(); + + let size = cpp! {unsafe [widget_ptr as "QWidget*"] -> qttypes::QSize as "QSize" { + return widget_ptr->size(); + }}; + + let rgb8_data = cpp! {unsafe [widget_ptr as "QWidget*"] -> qttypes::QByteArray as "QByteArray" { + QPixmap pixmap = widget_ptr->grab(); + QImage image = pixmap.toImage(); + image.convertTo(QImage::Format_RGB888); + return QByteArray(reinterpret_cast(image.constBits()), image.sizeInBytes()); + }}; + + let buffer = i_slint_core::graphics::SharedPixelBuffer::::clone_from_slice( + rgb8_data.to_slice(), + size.width, + size.height, + ); + Ok(i_slint_core::graphics::SharedImageBuffer::RGB8(buffer)) + } } fn into_qsize(logical_size: i_slint_core::api::LogicalSize) -> qttypes::QSize { diff --git a/internal/backends/winit/lib.rs b/internal/backends/winit/lib.rs index d3a1c2f1a10..5f47bb7dbee 100644 --- a/internal/backends/winit/lib.rs +++ b/internal/backends/winit/lib.rs @@ -32,7 +32,7 @@ pub struct SlintUserEvent(CustomEvent); mod renderer { use std::rc::Rc; - use i_slint_core::platform::PlatformError; + use i_slint_core::{graphics::SharedImageBuffer, platform::PlatformError}; pub trait WinitCompatibleRenderer { fn render(&self, window: &i_slint_core::api::Window) -> Result<(), PlatformError>; @@ -46,6 +46,11 @@ mod renderer { fn resumed(&self, _winit_window: Rc) -> Result<(), PlatformError> { Ok(()) } + + fn grab_window( + &self, + window: &i_slint_core::api::Window, + ) -> Result; } #[cfg(feature = "renderer-femtovg")] diff --git a/internal/backends/winit/renderer/femtovg.rs b/internal/backends/winit/renderer/femtovg.rs index dc8351fc510..a0d8a254dd0 100644 --- a/internal/backends/winit/renderer/femtovg.rs +++ b/internal/backends/winit/renderer/femtovg.rs @@ -60,4 +60,11 @@ impl super::WinitCompatibleRenderer for GlutinFemtoVGRenderer { fn as_core_renderer(&self) -> &dyn Renderer { &self.renderer } + + fn grab_window( + &self, + _window: &i_slint_core::api::Window, + ) -> Result { + self.renderer.screenshot() + } } diff --git a/internal/backends/winit/renderer/skia.rs b/internal/backends/winit/renderer/skia.rs index df0913d0457..30104beeeca 100644 --- a/internal/backends/winit/renderer/skia.rs +++ b/internal/backends/winit/renderer/skia.rs @@ -101,4 +101,11 @@ impl super::WinitCompatibleRenderer for WinitSkiaRenderer { winit_window.scale_factor() as f32, ) } + + fn grab_window( + &self, + _window: &i_slint_core::api::Window, + ) -> Result { + self.renderer.screenshot() + } } diff --git a/internal/backends/winit/renderer/sw.rs b/internal/backends/winit/renderer/sw.rs index a904c1742bc..7d981940d9e 100644 --- a/internal/backends/winit/renderer/sw.rs +++ b/internal/backends/winit/renderer/sw.rs @@ -5,11 +5,13 @@ use core::num::NonZeroU32; use core::ops::DerefMut; -use i_slint_core::graphics::Rgb8Pixel; +use i_slint_core::graphics::{Rgb8Pixel, SharedImageBuffer, SharedPixelBuffer}; use i_slint_core::platform::PlatformError; pub use i_slint_core::software_renderer::SoftwareRenderer; use i_slint_core::software_renderer::{PremultipliedRgbaColor, RepaintBufferType, TargetPixel}; -use std::{cell::RefCell, rc::Rc}; +use std::cell::Cell; +use std::cell::RefCell; +use std::rc::Rc; use super::WinitCompatibleRenderer; @@ -18,6 +20,7 @@ pub struct WinitSoftwareRenderer { _context: softbuffer::Context>, surface: RefCell, Rc>>, winit_window: Rc, + force_next_frame_new_buffer: Cell, } #[repr(transparent)] @@ -90,6 +93,7 @@ impl WinitSoftwareRenderer { _context: context, surface: RefCell::new(surface), winit_window: winit_window.clone(), + force_next_frame_new_buffer: Default::default(), }), winit_window, )) @@ -116,11 +120,14 @@ impl super::WinitCompatibleRenderer for WinitSoftwareRenderer { .buffer_mut() .map_err(|e| format!("Error retrieving softbuffer rendering buffer: {e}"))?; - let age = target_buffer.age(); - self.renderer.set_repaint_buffer_type(match age { - 1 => RepaintBufferType::ReusedBuffer, - 2 => RepaintBufferType::SwappedBuffers, - _ => RepaintBufferType::NewBuffer, + self.renderer.set_repaint_buffer_type(if self.force_next_frame_new_buffer.take() { + RepaintBufferType::NewBuffer + } else { + match target_buffer.age() { + 1 => RepaintBufferType::ReusedBuffer, + 2 => RepaintBufferType::SwappedBuffers, + _ => RepaintBufferType::NewBuffer, + } }); let region = if std::env::var_os("SLINT_LINE_BY_LINE").is_none() { @@ -172,6 +179,7 @@ impl super::WinitCompatibleRenderer for WinitSoftwareRenderer { }]) .map_err(|e| format!("Error presenting softbuffer buffer: {e}"))?; } + Ok(()) } @@ -184,4 +192,29 @@ impl super::WinitCompatibleRenderer for WinitSoftwareRenderer { // and the buffer age doesn't respect that, so clean the partial rendering cache self.renderer.set_repaint_buffer_type(RepaintBufferType::NewBuffer); } + + fn grab_window( + &self, + window: &i_slint_core::api::Window, + ) -> Result { + let size = window.size(); + + let Some((width, height)) = size.width.try_into().ok().zip(size.height.try_into().ok()) + else { + // Nothing to render + return Err("grab_window() called on window with invalid size".into()); + }; + + // We could use Surface::fetch() here, but it's only implemented on X11 and Web. + + let mut target_buffer = + SharedPixelBuffer::::new(width, height); + + self.force_next_frame_new_buffer.set(true); + self.renderer.set_repaint_buffer_type(RepaintBufferType::NewBuffer); + + let _ = self.renderer.render(target_buffer.make_mut_slice(), width as usize); + + Ok(SharedImageBuffer::RGB8(target_buffer)) + } } diff --git a/internal/backends/winit/winitwindowadapter.rs b/internal/backends/winit/winitwindowadapter.rs index 197ff161313..16d6db371cc 100644 --- a/internal/backends/winit/winitwindowadapter.rs +++ b/internal/backends/winit/winitwindowadapter.rs @@ -643,6 +643,10 @@ impl WindowAdapter for WinitWindowAdapter { ) -> Result, raw_window_handle::HandleError> { raw_window_handle::HasDisplayHandle::display_handle(&self.winit_window) } + + fn grab_window(&self) -> Result { + self.renderer().grab_window(self.window()) + } } impl WindowAdapterInternal for WinitWindowAdapter { diff --git a/internal/core/api.rs b/internal/core/api.rs index 0afb9ec69b1..cb2b6580b2d 100644 --- a/internal/core/api.rs +++ b/internal/core/api.rs @@ -9,6 +9,7 @@ This module contains types that are public and re-exported in the slint-rs as we #[cfg(target_has_atomic = "ptr")] pub use crate::future::*; +use crate::graphics::SharedImageBuffer; use crate::input::{KeyEventType, MouseEvent}; use crate::item_tree::ItemTreeVTable; use crate::window::{WindowAdapter, WindowInner}; @@ -622,6 +623,14 @@ impl Window { pub fn window_handle(&self) -> WindowHandle { WindowHandle { adapter: self.0.window_adapter() } } + + /// Returns an image buffer with the rendered contents. + /// + /// Note that this function may be slow to call. Reading from the framebuffer previously + /// rendered, too, may take a long time. + pub fn grab_window(&self) -> Result { + self.0.window_adapter().grab_window() + } } pub use crate::SharedString; diff --git a/internal/core/window.rs b/internal/core/window.rs index 28faa7861f0..d2c1d6921d7 100644 --- a/internal/core/window.rs +++ b/internal/core/window.rs @@ -10,7 +10,7 @@ use crate::api::{ CloseRequestResponse, LogicalPosition, PhysicalPosition, PhysicalSize, PlatformError, Window, WindowPosition, WindowSize, }; -use crate::graphics::Point; +use crate::graphics::{Point, SharedImageBuffer}; use crate::input::{ key_codes, ClickState, InternalKeyboardModifierState, KeyEvent, KeyEventType, MouseEvent, MouseInputState, TextCursorBlinker, @@ -153,6 +153,12 @@ pub trait WindowAdapter { ) -> Result, raw_window_handle_06::HandleError> { Err(raw_window_handle_06::HandleError::NotSupported) } + + /// Re-implement this function to support [`Window::grab_window()`], i.e. return + /// the contents of the window in an image buffer. + fn grab_window(&self) -> Result { + Err("WindowAdapter::grab_window is not implemented by the platform".into()) + } } /// Implementation details behind [`WindowAdapter`], but since this diff --git a/internal/renderers/femtovg/lib.rs b/internal/renderers/femtovg/lib.rs index 63f2aa5c4d0..ce6671e8897 100644 --- a/internal/renderers/femtovg/lib.rs +++ b/internal/renderers/femtovg/lib.rs @@ -11,9 +11,9 @@ use std::rc::{Rc, Weak}; use i_slint_common::sharedfontdb; use i_slint_core::api::{RenderingNotifier, RenderingState, SetRenderingNotifierError}; -use i_slint_core::graphics::BorderRadius; -use i_slint_core::graphics::FontRequest; use i_slint_core::graphics::{euclid, rendering_metrics_collector::RenderingMetricsCollector}; +use i_slint_core::graphics::{BorderRadius, SharedImageBuffer}; +use i_slint_core::graphics::{FontRequest, SharedPixelBuffer}; use i_slint_core::item_rendering::ItemRenderer; use i_slint_core::items::TextWrap; use i_slint_core::lengths::{ @@ -172,6 +172,23 @@ impl FemtoVGRenderer { }) } + /// Returns an image buffer of what was rendered last by reading the previous front buffer (using glReadPixels). + pub fn screenshot(&self) -> Result { + self.opengl_context.ensure_current()?; + let screenshot = self + .canvas + .borrow_mut() + .screenshot() + .map_err(|e| format!("FemtoVG error reading current back buffer: {e}"))?; + + use rgb::ComponentBytes; + Ok(SharedImageBuffer::RGBA8(SharedPixelBuffer::clone_from_slice( + screenshot.buf().as_bytes(), + screenshot.width() as u32, + screenshot.height() as u32, + ))) + } + /// Render the scene using OpenGL. pub fn render(&self) -> Result<(), i_slint_core::platform::PlatformError> { self.internal_render_with_post_callback( diff --git a/internal/renderers/skia/lib.rs b/internal/renderers/skia/lib.rs index 1ae0f434d89..f8f6c43c71b 100644 --- a/internal/renderers/skia/lib.rs +++ b/internal/renderers/skia/lib.rs @@ -13,8 +13,8 @@ use i_slint_core::api::{ }; use i_slint_core::graphics::euclid::{self, Vector2D}; use i_slint_core::graphics::rendering_metrics_collector::RenderingMetricsCollector; -use i_slint_core::graphics::BorderRadius; -use i_slint_core::graphics::FontRequest; +use i_slint_core::graphics::{BorderRadius, SharedImageBuffer}; +use i_slint_core::graphics::{FontRequest, SharedPixelBuffer}; use i_slint_core::item_rendering::{ItemCache, ItemRenderer}; use i_slint_core::lengths::{ LogicalLength, LogicalPoint, LogicalRect, LogicalSize, PhysicalPx, ScaleFactor, @@ -209,6 +209,34 @@ impl SkiaRenderer { Ok(()) } + /// Returns an image buffer of what was rendered last by reading the previous front buffer (using glReadPixels). + pub fn screenshot(&self) -> Result { + let window_adapter = self.window_adapter()?; + let window = window_adapter.window(); + let size = window_adapter.window().size(); + let (width, height) = (size.width, size.height); + let mut target_buffer = + SharedPixelBuffer::::new(width, height); + + eprintln!("width {width} height {height}"); + let mut surface_borrow = skia_safe::surfaces::wrap_pixels( + &skia_safe::ImageInfo::new( + (width as i32, height as i32), + skia_safe::ColorType::RGBA8888, + skia_safe::AlphaType::Opaque, + None, + ), + target_buffer.make_mut_bytes(), + None, + None, + ) + .ok_or_else(|| format!("Error wrapping target buffer for rendering into with Skia"))?; + + self.render_to_canvas(surface_borrow.canvas(), 0., (0.0, 0.0), None, None, window, None); + + Ok(SharedImageBuffer::RGBA8(target_buffer)) + } + /// Render the scene in the previously associated window. pub fn render(&self) -> Result<(), i_slint_core::platform::PlatformError> { let window_adapter = self.window_adapter()?; @@ -220,7 +248,7 @@ impl SkiaRenderer { &self, rotation_angle_degrees: f32, translation: (f32, f32), - surace_size: PhysicalWindowSize, + surface_size: PhysicalWindowSize, post_render_cb: Option<&dyn Fn(&mut dyn ItemRenderer)>, ) -> Result<(), i_slint_core::platform::PlatformError> { let surface = self.surface.borrow(); @@ -242,96 +270,121 @@ impl SkiaRenderer { let window_adapter = self.window_adapter()?; let window = window_adapter.window(); - let window_inner = WindowInner::from_pub(window); surface.set_scale_factor(window.scale_factor()); surface.render( - surace_size, - &|skia_canvas, mut gr_context| { - skia_canvas.rotate(rotation_angle_degrees, None); - skia_canvas.translate(translation); - - window_inner.draw_contents(|components| { - let window_background_brush = - window_inner.window_item().map(|w| w.as_pin_ref().background()); - - // Clear with window background if it is a solid color otherwise it will drawn as gradient - if let Some(Brush::SolidColor(clear_color)) = window_background_brush { - skia_canvas.clear(itemrenderer::to_skia_color(&clear_color)); - } - - if let Some(callback) = self.rendering_notifier.borrow_mut().as_mut() { - // For the BeforeRendering rendering notifier callback it's important that this happens *after* clearing - // the back buffer, in order to allow the callback to provide its own rendering of the background. - // Skia's clear() will merely schedule a clear call, so flush right away to make it immediate. - if let Some(ctx) = gr_context.as_mut() { - ctx.flush(None); - } + surface_size, + &|skia_canvas, gr_context| { + self.render_to_canvas( + skia_canvas, + rotation_angle_degrees, + translation, + gr_context, + None, + window, + post_render_cb, + ); + }, + &self.pre_present_callback, + ) + } - surface.with_graphics_api(&mut |api| { - callback.notify(RenderingState::BeforeRendering, &api) - }) - } + fn render_to_canvas( + &self, + skia_canvas: &skia_safe::Canvas, + rotation_angle_degrees: f32, + translation: (f32, f32), + mut gr_context: Option<&mut skia_safe::gpu::DirectContext>, + surface: Option<&dyn Surface>, + window: &i_slint_core::api::Window, + post_render_cb: Option<&dyn Fn(&mut dyn ItemRenderer)>, + ) { + skia_canvas.rotate(rotation_angle_degrees, None); + skia_canvas.translate(translation); - let mut box_shadow_cache = Default::default(); + let window_inner = WindowInner::from_pub(window); - self.image_cache.clear_cache_if_scale_factor_changed(window); - self.path_cache.clear_cache_if_scale_factor_changed(window); + window_inner.draw_contents(|components| { + let window_background_brush = + window_inner.window_item().map(|w| w.as_pin_ref().background()); - let mut item_renderer = itemrenderer::SkiaItemRenderer::new( - skia_canvas, - window, - &self.image_cache, - &self.path_cache, - &mut box_shadow_cache, - ); + // Clear with window background if it is a solid color otherwise it will drawn as gradient + if let Some(Brush::SolidColor(clear_color)) = window_background_brush { + skia_canvas.clear(itemrenderer::to_skia_color(&clear_color)); + } - // Draws the window background as gradient - match window_background_brush { - Some(Brush::SolidColor(..)) | None => {} - Some(brush @ _) => { - item_renderer.draw_rect( - i_slint_core::lengths::logical_size_from_api( - window.size().to_logical(window_inner.scale_factor()), - ), - brush, - ); - } - } - - for (component, origin) in components { - i_slint_core::item_rendering::render_component_items( - component, - &mut item_renderer, - *origin, - ); - } - - if let Some(collector) = &self.rendering_metrics_collector.borrow_mut().as_ref() - { - collector.measure_frame_rendered(&mut item_renderer); - } - - if let Some(cb) = post_render_cb.as_ref() { - cb(&mut item_renderer) - } - - drop(item_renderer); - - if let Some(ctx) = gr_context.as_mut() { - ctx.flush(None); - } - }); - - if let Some(callback) = self.rendering_notifier.borrow_mut().as_mut() { + if let Some(callback) = self.rendering_notifier.borrow_mut().as_mut() { + // For the BeforeRendering rendering notifier callback it's important that this happens *after* clearing + // the back buffer, in order to allow the callback to provide its own rendering of the background. + // Skia's clear() will merely schedule a clear call, so flush right away to make it immediate. + if let Some(ctx) = gr_context.as_mut() { + ctx.flush(None); + } + + if let Some(surface) = surface { surface.with_graphics_api(&mut |api| { - callback.notify(RenderingState::AfterRendering, &api) + callback.notify(RenderingState::BeforeRendering, &api) }) } - }, - &self.pre_present_callback, - ) + } + + let mut box_shadow_cache = Default::default(); + + self.image_cache.clear_cache_if_scale_factor_changed(window); + self.path_cache.clear_cache_if_scale_factor_changed(window); + + let mut item_renderer = itemrenderer::SkiaItemRenderer::new( + skia_canvas, + window, + &self.image_cache, + &self.path_cache, + &mut box_shadow_cache, + ); + + // Draws the window background as gradient + match window_background_brush { + Some(Brush::SolidColor(..)) | None => {} + Some(brush @ _) => { + item_renderer.draw_rect( + i_slint_core::lengths::logical_size_from_api( + window.size().to_logical(window_inner.scale_factor()), + ), + brush, + ); + } + } + + for (component, origin) in components { + i_slint_core::item_rendering::render_component_items( + component, + &mut item_renderer, + *origin, + ); + } + + if let Some(collector) = &self.rendering_metrics_collector.borrow_mut().as_ref() { + collector.measure_frame_rendered(&mut item_renderer); + } + + if let Some(cb) = post_render_cb.as_ref() { + cb(&mut item_renderer) + } + + drop(item_renderer); + + if let Some(ctx) = gr_context.as_mut() { + ctx.flush(None); + } + }); + + if let Some(callback) = self.rendering_notifier.borrow_mut().as_mut() { + if let Some(surface) = surface { + surface.with_graphics_api(&mut |api| { + callback.notify(RenderingState::AfterRendering, &api) + }) + } + } } fn window_adapter(&self) -> Result, PlatformError> {