// Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial use std::cell::RefCell; use std::rc::{Rc, Weak}; use i_slint_core::api::{ GraphicsAPI, RenderingNotifier, RenderingState, SetRenderingNotifierError, }; use i_slint_core::graphics::rendering_metrics_collector::RenderingMetricsCollector; use i_slint_core::item_rendering::ItemCache; use i_slint_core::window::{WindowAdapter, WindowInner}; use crate::WindowSystemName; mod cached_image; mod itemrenderer; mod textlayout; cfg_if::cfg_if! { if #[cfg(skia_backend_opengl)] { mod opengl_surface; type DefaultSurface = opengl_surface::OpenGLSurface; } else if #[cfg(skia_backend_metal)] { mod metal_surface; type DefaultSurface = metal_surface::MetalSurface; } else if #[cfg(skia_backend_d3d)] { mod d3d_surface; type DefaultSurface = d3d_surface::D3DSurface; } } pub struct SkiaRenderer { window_adapter_weak: Weak, rendering_notifier: RefCell>>, } impl super::WinitCompatibleRenderer for SkiaRenderer { type Canvas = SkiaCanvas; const NAME: &'static str = "Skia"; fn new(window_adapter_weak: &Weak) -> Self { Self { window_adapter_weak: window_adapter_weak.clone(), rendering_notifier: Default::default(), } } fn create_canvas(&self, window_builder: winit::window::WindowBuilder) -> Self::Canvas { let surface = DefaultSurface::new(window_builder); let rendering_metrics_collector = RenderingMetricsCollector::new( self.window_adapter_weak.clone(), &format!( "Skia renderer (windowing system: {}; skia backend {}; surface: {} bpp)", surface.with_window_handle(|winit_window| winit_window.winsys_name()), surface.name(), surface.bits_per_pixel() ), ); let canvas = SkiaCanvas { image_cache: Default::default(), surface, rendering_metrics_collector }; if let Some(callback) = self.rendering_notifier.borrow_mut().as_mut() { canvas.with_graphics_api(|api| callback.notify(RenderingState::RenderingSetup, &api)) } canvas } fn release_canvas(&self, canvas: Self::Canvas) { canvas.surface.with_active_surface(|| { if let Some(callback) = self.rendering_notifier.borrow_mut().as_mut() { canvas.with_graphics_api(|api| { callback.notify(RenderingState::RenderingTeardown, &api) }) } }); } fn render(&self, canvas: &Self::Canvas, window_adapter: &dyn WindowAdapter) { let window_inner = WindowInner::from_pub(window_adapter.window()); canvas.surface.render(|skia_canvas, gr_context| { window_inner.draw_contents(|components| { if let Some(window_item) = window_inner.window_item() { skia_canvas .clear(itemrenderer::to_skia_color(&window_item.as_pin_ref().background())); } 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. gr_context.flush(None); canvas.with_graphics_api(|api| { callback.notify(RenderingState::BeforeRendering, &api) }) } let mut box_shadow_cache = Default::default(); let mut item_renderer = itemrenderer::SkiaRenderer::new( skia_canvas, window_adapter.window(), &canvas.image_cache, &mut box_shadow_cache, ); for (component, origin) in components { i_slint_core::item_rendering::render_component_items( component, &mut item_renderer, *origin, ); } if let Some(collector) = &canvas.rendering_metrics_collector { collector.measure_frame_rendered(&mut item_renderer); } drop(item_renderer); gr_context.flush(None); }); if let Some(callback) = self.rendering_notifier.borrow_mut().as_mut() { canvas .with_graphics_api(|api| callback.notify(RenderingState::AfterRendering, &api)) } }); } } impl i_slint_core::renderer::Renderer for SkiaRenderer { fn text_size( &self, font_request: i_slint_core::graphics::FontRequest, text: &str, max_width: Option, scale_factor: f32, ) -> i_slint_core::graphics::Size { let layout = textlayout::create_layout( font_request, scale_factor, text, None, max_width.map(|w| w * scale_factor), Default::default(), Default::default(), None, ); [layout.max_intrinsic_width().ceil() / scale_factor, layout.height().ceil() / scale_factor] .into() } fn text_input_byte_offset_for_position( &self, text_input: std::pin::Pin<&i_slint_core::items::TextInput>, pos: i_slint_core::graphics::Point, ) -> usize { let window_adapter = match self.window_adapter_weak.upgrade() { Some(window) => window, None => return 0, }; let window = WindowInner::from_pub(window_adapter.window()); let scale_factor = window.scale_factor(); let max_width = text_input.width() * scale_factor; let max_height = text_input.height() * scale_factor; let pos = pos * scale_factor; if max_width <= 0. || max_height <= 0. { return 0; } let string = text_input.text(); let string = string.as_str(); let font_request = text_input.font_request(&window_adapter); let layout = textlayout::create_layout( font_request, scale_factor, string, None, Some(max_width), text_input.horizontal_alignment(), i_slint_core::items::TextOverflow::Clip, None, ); let layout_top_y = match text_input.vertical_alignment() { i_slint_core::items::TextVerticalAlignment::Top => 0., i_slint_core::items::TextVerticalAlignment::Center => { (max_height - layout.height()) / 2. } i_slint_core::items::TextVerticalAlignment::Bottom => max_height - layout.height(), }; let utf16_index = layout.get_glyph_position_at_coordinate((pos.x, pos.y - layout_top_y)).position; let mut utf16_count = 0; string .char_indices() .find(|(_, x)| { let r = utf16_count >= utf16_index; utf16_count += x.len_utf16() as i32; r }) .unwrap_or((string.len(), '\0')) .0 } fn text_input_cursor_rect_for_byte_offset( &self, text_input: std::pin::Pin<&i_slint_core::items::TextInput>, byte_offset: usize, ) -> i_slint_core::graphics::Rect { let window_adapter = match self.window_adapter_weak.upgrade() { Some(window) => window, None => return Default::default(), }; let window = WindowInner::from_pub(window_adapter.window()); let scale_factor = window.scale_factor(); let max_width = text_input.width() * scale_factor; let max_height = text_input.height() * scale_factor; if max_width <= 0. || max_height <= 0. { return Default::default(); } let string = text_input.text(); let string = string.as_str(); let font_request = text_input.font_request(&window_adapter); let layout = textlayout::create_layout( font_request, scale_factor, string, None, Some(max_width), text_input.horizontal_alignment(), i_slint_core::items::TextOverflow::Clip, None, ); let layout_top_y = match text_input.vertical_alignment() { i_slint_core::items::TextVerticalAlignment::Top => 0., i_slint_core::items::TextVerticalAlignment::Center => { (max_height - layout.height()) / 2. } i_slint_core::items::TextVerticalAlignment::Bottom => max_height - layout.height(), }; let physical_cursor_rect = textlayout::cursor_rect( string, byte_offset, layout, text_input.text_cursor_width() * scale_factor, ); i_slint_core::graphics::Rect::new( [ physical_cursor_rect.x() / scale_factor, (physical_cursor_rect.y() + layout_top_y) / scale_factor, ] .into(), [ physical_cursor_rect.width() / scale_factor, physical_cursor_rect.height() / scale_factor, ] .into(), ) } fn register_font_from_memory( &self, data: &'static [u8], ) -> Result<(), Box> { textlayout::register_font_from_memory(data) } fn register_font_from_path( &self, path: &std::path::Path, ) -> Result<(), Box> { textlayout::register_font_from_path(path) } fn set_rendering_notifier( &self, callback: Box, ) -> std::result::Result<(), SetRenderingNotifierError> { if !DefaultSurface::SUPPORTS_GRAPHICS_API { return Err(SetRenderingNotifierError::Unsupported); } let mut notifier = self.rendering_notifier.borrow_mut(); if notifier.replace(callback).is_some() { Err(SetRenderingNotifierError::AlreadySet) } else { Ok(()) } } } pub trait Surface { const SUPPORTS_GRAPHICS_API: bool; fn new(window_builder: winit::window::WindowBuilder) -> Self; fn name(&self) -> &'static str; fn with_graphics_api(&self, callback: impl FnOnce(GraphicsAPI<'_>)); fn with_window_handle(&self, callback: impl FnOnce(&winit::window::Window) -> T) -> T; fn with_active_surface(&self, callback: impl FnOnce() -> T) -> T { callback() } fn render( &self, callback: impl FnOnce(&mut skia_safe::Canvas, &mut skia_safe::gpu::DirectContext), ); fn resize_event(&self); fn bits_per_pixel(&self) -> u8; } pub struct SkiaCanvas { image_cache: ItemCache>, rendering_metrics_collector: Option>, surface: SurfaceType, } impl super::WinitCompatibleCanvas for SkiaCanvas { fn component_destroyed(&self, component: i_slint_core::component::ComponentRef) { self.image_cache.component_destroyed(component) } fn with_window_handle(&self, callback: impl FnOnce(&winit::window::Window) -> T) -> T { self.surface.with_window_handle(callback) } fn resize_event(&self) { self.surface.resize_event() } } impl SkiaCanvas { fn with_graphics_api(&self, callback: impl FnOnce(GraphicsAPI<'_>)) { self.surface.with_graphics_api(callback) } }