1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4#![doc = include_str!("README.md")]
5#![doc(html_logo_url = "https://slint.dev/logo/slint-logo-square-light.svg")]
6
7use std::cell::{Cell, RefCell};
8use std::rc::{Rc, Weak};
9
10use i_slint_core::api::{
11 GraphicsAPI, PhysicalSize as PhysicalWindowSize, RenderingNotifier, RenderingState,
12 SetRenderingNotifierError, Window,
13};
14use i_slint_core::graphics::euclid::{self, Vector2D};
15use i_slint_core::graphics::rendering_metrics_collector::RenderingMetricsCollector;
16use i_slint_core::graphics::{BorderRadius, FontRequest, RequestedGraphicsAPI, SharedPixelBuffer};
17use i_slint_core::item_rendering::{DirtyRegion, ItemCache, ItemRenderer, PartialRenderingState};
18use i_slint_core::lengths::{
19 LogicalLength, LogicalPoint, LogicalRect, LogicalSize, PhysicalPx, ScaleFactor,
20};
21use i_slint_core::platform::PlatformError;
22use i_slint_core::window::{WindowAdapter, WindowInner};
23use i_slint_core::Brush;
24
25type PhysicalLength = euclid::Length<f32, PhysicalPx>;
26type PhysicalRect = euclid::Rect<f32, PhysicalPx>;
27type PhysicalSize = euclid::Size2D<f32, PhysicalPx>;
28type PhysicalPoint = euclid::Point2D<f32, PhysicalPx>;
29type PhysicalBorderRadius = BorderRadius<f32, PhysicalPx>;
30
31mod cached_image;
32mod itemrenderer;
33mod textlayout;
34
35#[cfg(skia_backend_software)]
36pub mod software_surface;
37
38#[cfg(target_vendor = "apple")]
39pub mod metal_surface;
40
41#[cfg(target_family = "windows")]
42pub mod d3d_surface;
43
44#[cfg(skia_backend_vulkan)]
45pub mod vulkan_surface;
46
47#[cfg(not(target_os = "ios"))]
48pub mod opengl_surface;
49
50use i_slint_core::items::TextWrap;
51use itemrenderer::to_skia_rect;
52pub use skia_safe;
53
54cfg_if::cfg_if! {
55 if #[cfg(skia_backend_vulkan)] {
56 type DefaultSurface = vulkan_surface::VulkanSurface;
57 } else if #[cfg(skia_backend_opengl)] {
58 type DefaultSurface = opengl_surface::OpenGLSurface;
59 } else if #[cfg(skia_backend_metal)] {
60 type DefaultSurface = metal_surface::MetalSurface;
61 } else if #[cfg(skia_backend_software)] {
62 type DefaultSurface = software_surface::SoftwareSurface;
63 }
64}
65
66fn create_default_surface(
67 window_handle: Rc<dyn raw_window_handle::HasWindowHandle>,
68 display_handle: Rc<dyn raw_window_handle::HasDisplayHandle>,
69 size: PhysicalWindowSize,
70 requested_graphics_api: Option<RequestedGraphicsAPI>,
71) -> Result<Box<dyn Surface>, PlatformError> {
72 match DefaultSurface::new(
73 window_handle.clone(),
74 display_handle.clone(),
75 size,
76 requested_graphics_api,
77 ) {
78 Ok(gpu_surface: OpenGLSurface) => Ok(Box::new(gpu_surface) as Box<dyn Surface>),
79 #[cfg(skia_backend_software)]
80 Err(err: PlatformError) => {
81 i_slint_core::debug_log!(
82 "Failed to initialize Skia GPU renderer: {} . Falling back to software rendering",
83 err
84 );
85 software_surface::SoftwareSurface::new(window_handle, display_handle, size, None)
86 .map(|r: SoftwareSurface| Box::new(r) as Box<dyn Surface>)
87 }
88 #[cfg(not(skia_backend_software))]
89 Err(err) => Err(err),
90 }
91}
92
93enum DirtyRegionDebugMode {
94 NoDebug,
95 Visualize,
96 Log,
97}
98
99impl Default for DirtyRegionDebugMode {
100 fn default() -> Self {
101 match std::env::var(key:"SLINT_SKIA_PARTIAL_RENDERING").as_deref() {
102 Ok("visualize") => DirtyRegionDebugMode::Visualize,
103 Ok("log") => DirtyRegionDebugMode::Log,
104 _ => DirtyRegionDebugMode::NoDebug,
105 }
106 }
107}
108
109fn create_partial_renderer_state(
110 maybe_surface: Option<&dyn Surface>,
111) -> Option<PartialRenderingState> {
112 maybe_surfacebool
113 .map_or_else(
114 || std::env::var("SLINT_SKIA_PARTIAL_RENDERING").as_deref().is_ok(),
115 |surface: &dyn Surface| surface.use_partial_rendering(),
116 )
117 .then(|| PartialRenderingState::default())
118}
119
120/// Use the SkiaRenderer when implementing a custom Slint platform where you deliver events to
121/// Slint and want the scene to be rendered using Skia as underlying graphics library.
122pub struct SkiaRenderer {
123 maybe_window_adapter: RefCell<Option<Weak<dyn WindowAdapter>>>,
124 rendering_notifier: RefCell<Option<Box<dyn RenderingNotifier>>>,
125 image_cache: ItemCache<Option<skia_safe::Image>>,
126 path_cache: ItemCache<Option<(Vector2D<f32, PhysicalPx>, skia_safe::Path)>>,
127 rendering_metrics_collector: RefCell<Option<Rc<RenderingMetricsCollector>>>,
128 rendering_first_time: Cell<bool>,
129 surface: RefCell<Option<Box<dyn Surface>>>,
130 surface_factory: fn(
131 window_handle: Rc<dyn raw_window_handle::HasWindowHandle>,
132 display_handle: Rc<dyn raw_window_handle::HasDisplayHandle>,
133 size: PhysicalWindowSize,
134 requested_graphics_api: Option<RequestedGraphicsAPI>,
135 ) -> Result<Box<dyn Surface>, PlatformError>,
136 pre_present_callback: RefCell<Option<Box<dyn FnMut()>>>,
137 partial_rendering_state: Option<PartialRenderingState>,
138 dirty_region_debug_mode: DirtyRegionDebugMode,
139 /// Tracking dirty regions indexed by buffer age - 1. More than 3 back buffers aren't supported, but also unlikely to happen.
140 dirty_region_history: RefCell<[DirtyRegion; 3]>,
141}
142
143impl Default for SkiaRenderer {
144 fn default() -> Self {
145 Self {
146 maybe_window_adapter: Default::default(),
147 rendering_notifier: Default::default(),
148 image_cache: Default::default(),
149 path_cache: Default::default(),
150 rendering_metrics_collector: Default::default(),
151 rendering_first_time: Default::default(),
152 surface: Default::default(),
153 surface_factory: create_default_surface,
154 pre_present_callback: Default::default(),
155 partial_rendering_state: create_partial_renderer_state(maybe_surface:None),
156 dirty_region_debug_mode: Default::default(),
157 dirty_region_history: Default::default(),
158 }
159 }
160}
161
162impl SkiaRenderer {
163 #[cfg(skia_backend_software)]
164 /// Creates a new SkiaRenderer that will always use Skia's software renderer.
165 pub fn default_software() -> Self {
166 Self {
167 maybe_window_adapter: Default::default(),
168 rendering_notifier: Default::default(),
169 image_cache: Default::default(),
170 path_cache: Default::default(),
171 rendering_metrics_collector: Default::default(),
172 rendering_first_time: Default::default(),
173 surface: Default::default(),
174 surface_factory: |window_handle, display_handle, size, requested_graphics_api| {
175 software_surface::SoftwareSurface::new(
176 window_handle,
177 display_handle,
178 size,
179 requested_graphics_api,
180 )
181 .map(|r| Box::new(r) as Box<dyn Surface>)
182 },
183 pre_present_callback: Default::default(),
184 partial_rendering_state: PartialRenderingState::default().into(),
185 dirty_region_debug_mode: Default::default(),
186 dirty_region_history: Default::default(),
187 }
188 }
189
190 #[cfg(not(target_os = "ios"))]
191 /// Creates a new SkiaRenderer that will always use Skia's OpenGL renderer.
192 pub fn default_opengl() -> Self {
193 Self {
194 maybe_window_adapter: Default::default(),
195 rendering_notifier: Default::default(),
196 image_cache: Default::default(),
197 path_cache: Default::default(),
198 rendering_metrics_collector: Default::default(),
199 rendering_first_time: Default::default(),
200 surface: Default::default(),
201 surface_factory: |window_handle, display_handle, size, requested_graphics_api| {
202 opengl_surface::OpenGLSurface::new(
203 window_handle,
204 display_handle,
205 size,
206 requested_graphics_api,
207 )
208 .map(|r| Box::new(r) as Box<dyn Surface>)
209 },
210 pre_present_callback: Default::default(),
211 partial_rendering_state: create_partial_renderer_state(None),
212 dirty_region_debug_mode: Default::default(),
213 dirty_region_history: Default::default(),
214 }
215 }
216
217 #[cfg(target_vendor = "apple")]
218 /// Creates a new SkiaRenderer that will always use Skia's Metal renderer.
219 pub fn default_metal() -> Self {
220 Self {
221 maybe_window_adapter: Default::default(),
222 rendering_notifier: Default::default(),
223 image_cache: Default::default(),
224 path_cache: Default::default(),
225 rendering_metrics_collector: Default::default(),
226 rendering_first_time: Default::default(),
227 surface: Default::default(),
228 surface_factory: |window_handle, display_handle, size, requested_graphics_api| {
229 metal_surface::MetalSurface::new(
230 window_handle,
231 display_handle,
232 size,
233 requested_graphics_api,
234 )
235 .map(|r| Box::new(r) as Box<dyn Surface>)
236 },
237 pre_present_callback: Default::default(),
238 partial_rendering_state: create_partial_renderer_state(None),
239 dirty_region_debug_mode: Default::default(),
240 dirty_region_history: Default::default(),
241 }
242 }
243
244 #[cfg(skia_backend_vulkan)]
245 /// Creates a new SkiaRenderer that will always use Skia's Vulkan renderer.
246 pub fn default_vulkan() -> Self {
247 Self {
248 maybe_window_adapter: Default::default(),
249 rendering_notifier: Default::default(),
250 image_cache: Default::default(),
251 path_cache: Default::default(),
252 rendering_metrics_collector: Default::default(),
253 rendering_first_time: Default::default(),
254 surface: Default::default(),
255 surface_factory: |window_handle, display_handle, size, requested_graphics_api| {
256 vulkan_surface::VulkanSurface::new(
257 window_handle,
258 display_handle,
259 size,
260 requested_graphics_api,
261 )
262 .map(|r| Box::new(r) as Box<dyn Surface>)
263 },
264 pre_present_callback: Default::default(),
265 partial_rendering_state: create_partial_renderer_state(None),
266 dirty_region_debug_mode: Default::default(),
267 dirty_region_history: Default::default(),
268 }
269 }
270
271 #[cfg(target_family = "windows")]
272 /// Creates a new SkiaRenderer that will always use Skia's Direct3D renderer.
273 pub fn default_direct3d() -> Self {
274 Self {
275 maybe_window_adapter: Default::default(),
276 rendering_notifier: Default::default(),
277 image_cache: Default::default(),
278 path_cache: Default::default(),
279 rendering_metrics_collector: Default::default(),
280 rendering_first_time: Default::default(),
281 surface: Default::default(),
282 surface_factory: |window_handle, display_handle, size, requested_graphics_api| {
283 d3d_surface::D3DSurface::new(
284 window_handle,
285 display_handle,
286 size,
287 requested_graphics_api,
288 )
289 .map(|r| Box::new(r) as Box<dyn Surface>)
290 },
291 pre_present_callback: Default::default(),
292 partial_rendering_state: create_partial_renderer_state(None),
293 dirty_region_debug_mode: Default::default(),
294 dirty_region_history: Default::default(),
295 }
296 }
297
298 /// Creates a new renderer is associated with the provided window adapter.
299 pub fn new(
300 window_handle: Rc<dyn raw_window_handle::HasWindowHandle>,
301 display_handle: Rc<dyn raw_window_handle::HasDisplayHandle>,
302 size: PhysicalWindowSize,
303 ) -> Result<Self, PlatformError> {
304 Ok(Self::new_with_surface(create_default_surface(
305 window_handle,
306 display_handle,
307 size,
308 None,
309 )?))
310 }
311
312 /// Creates a new renderer with the given surface trait implementation.
313 pub fn new_with_surface(surface: Box<dyn Surface + 'static>) -> Self {
314 let partial_rendering_state = create_partial_renderer_state(Some(surface.as_ref())).into();
315 Self {
316 maybe_window_adapter: Default::default(),
317 rendering_notifier: Default::default(),
318 image_cache: Default::default(),
319 path_cache: Default::default(),
320 rendering_metrics_collector: Default::default(),
321 rendering_first_time: Cell::new(true),
322 surface: RefCell::new(Some(surface)),
323 surface_factory: |_, _, _, _| {
324 Err("Skia renderer constructed with surface does not support dynamic surface re-creation".into())
325 },
326 pre_present_callback: Default::default(),
327 partial_rendering_state,
328 dirty_region_debug_mode: Default::default(),
329 dirty_region_history: Default::default(),
330 }
331 }
332
333 /// Reset the surface to a new surface. (destroy the previously set surface if any)
334 pub fn set_surface(&self, surface: Box<dyn Surface + 'static>) {
335 self.image_cache.clear_all();
336 self.path_cache.clear_all();
337 self.rendering_first_time.set(true);
338 *self.surface.borrow_mut() = Some(surface);
339 }
340
341 fn clear_surface(&self) {
342 let Some(surface) = self.surface.borrow_mut().take() else {
343 return;
344 };
345
346 // If we've rendered a frame before, then we need to invoke the RenderingTearDown notifier.
347 if !self.rendering_first_time.get() {
348 if let Some(callback) = self.rendering_notifier.borrow_mut().as_mut() {
349 surface
350 .with_active_surface(&mut || {
351 surface.with_graphics_api(&mut |api| {
352 callback.notify(RenderingState::RenderingTeardown, &api)
353 })
354 })
355 .ok();
356 }
357 }
358
359 drop(surface);
360 }
361
362 /// Suspends the renderer by freeing all graphics related resources as well as the underlying
363 /// rendering surface. Call [`Self::set_window_handle()`] to re-associate the renderer with a new
364 /// window surface for subsequent rendering.
365 pub fn suspend(&self) -> Result<(), PlatformError> {
366 self.image_cache.clear_all();
367 self.path_cache.clear_all();
368 // Destroy the old surface before allocating the new one, to work around
369 // the vivante drivers using zwp_linux_explicit_synchronization_v1 and
370 // trying to create a second synchronization object and that's not allowed.
371 self.clear_surface();
372 Ok(())
373 }
374
375 /// Reset the surface to the window given the window handle
376 pub fn set_window_handle(
377 &self,
378 window_handle: Rc<dyn raw_window_handle::HasWindowHandle>,
379 display_handle: Rc<dyn raw_window_handle::HasDisplayHandle>,
380 size: PhysicalWindowSize,
381 requested_graphics_api: Option<RequestedGraphicsAPI>,
382 ) -> Result<(), PlatformError> {
383 // just in case
384 self.suspend()?;
385 let surface =
386 (self.surface_factory)(window_handle, display_handle, size, requested_graphics_api)?;
387 self.set_surface(surface);
388 Ok(())
389 }
390
391 /// Render the scene in the previously associated window.
392 pub fn render(&self) -> Result<(), i_slint_core::platform::PlatformError> {
393 let window_adapter = self.window_adapter()?;
394 let size = window_adapter.window().size();
395 self.internal_render_with_post_callback(0., (0., 0.), size, None)
396 }
397
398 fn internal_render_with_post_callback(
399 &self,
400 rotation_angle_degrees: f32,
401 translation: (f32, f32),
402 surface_size: PhysicalWindowSize,
403 post_render_cb: Option<&dyn Fn(&mut dyn ItemRenderer)>,
404 ) -> Result<(), i_slint_core::platform::PlatformError> {
405 let surface = self.surface.borrow();
406 let Some(surface) = surface.as_ref() else { return Ok(()) };
407 if self.rendering_first_time.take() {
408 *self.rendering_metrics_collector.borrow_mut() =
409 RenderingMetricsCollector::new(&format!(
410 "Skia renderer (skia backend {}; surface: {} bpp)",
411 surface.name(),
412 surface.bits_per_pixel()?
413 ));
414
415 if let Some(callback) = self.rendering_notifier.borrow_mut().as_mut() {
416 surface.with_graphics_api(&mut |api| {
417 callback.notify(RenderingState::RenderingSetup, &api)
418 })
419 }
420 }
421
422 let window_adapter = self.window_adapter()?;
423 let window = window_adapter.window();
424
425 surface.render(
426 window,
427 surface_size,
428 &|skia_canvas, gr_context, back_buffer_age| {
429 self.render_to_canvas(
430 skia_canvas,
431 rotation_angle_degrees,
432 translation,
433 gr_context,
434 back_buffer_age,
435 Some(surface.as_ref()),
436 window,
437 post_render_cb,
438 )
439 },
440 &self.pre_present_callback,
441 )
442 }
443
444 fn render_to_canvas(
445 &self,
446 skia_canvas: &skia_safe::Canvas,
447 rotation_angle_degrees: f32,
448 translation: (f32, f32),
449 gr_context: Option<&mut skia_safe::gpu::DirectContext>,
450 back_buffer_age: u8,
451 surface: Option<&dyn Surface>,
452 window: &i_slint_core::api::Window,
453 post_render_cb: Option<&dyn Fn(&mut dyn ItemRenderer)>,
454 ) -> Option<DirtyRegion> {
455 skia_canvas.rotate(rotation_angle_degrees, None);
456 skia_canvas.translate(translation);
457
458 let window_inner = WindowInner::from_pub(window);
459
460 let dirty_region = window_inner
461 .draw_contents(|components| {
462 self.render_components_to_canvas(
463 skia_canvas,
464 gr_context,
465 back_buffer_age,
466 surface,
467 window,
468 post_render_cb,
469 components,
470 )
471 })
472 .unwrap_or_default();
473
474 if let Some(callback) = self.rendering_notifier.borrow_mut().as_mut() {
475 if let Some(surface) = surface {
476 surface.with_graphics_api(&mut |api| {
477 callback.notify(RenderingState::AfterRendering, &api)
478 })
479 }
480 }
481
482 dirty_region
483 }
484
485 fn render_components_to_canvas(
486 &self,
487 skia_canvas: &skia_safe::Canvas,
488 mut gr_context: Option<&mut skia_safe::gpu::DirectContext>,
489 back_buffer_age: u8,
490 surface: Option<&dyn Surface>,
491 window: &i_slint_core::api::Window,
492 post_render_cb: Option<&dyn Fn(&mut dyn ItemRenderer)>,
493 components: &[(&i_slint_core::item_tree::ItemTreeRc, LogicalPoint)],
494 ) -> Option<DirtyRegion> {
495 let window_inner = WindowInner::from_pub(window);
496 let window_adapter = window_inner.window_adapter();
497
498 let mut box_shadow_cache = Default::default();
499
500 self.image_cache.clear_cache_if_scale_factor_changed(window);
501 self.path_cache.clear_cache_if_scale_factor_changed(window);
502
503 let mut skia_item_renderer = itemrenderer::SkiaItemRenderer::new(
504 skia_canvas,
505 window,
506 &self.image_cache,
507 &self.path_cache,
508 &mut box_shadow_cache,
509 );
510
511 let scale_factor = ScaleFactor::new(window_inner.scale_factor());
512 let logical_window_size = i_slint_core::lengths::logical_size_from_api(
513 window.size().to_logical(window_inner.scale_factor()),
514 );
515
516 let mut dirty_region = None;
517
518 {
519 let mut item_renderer: &mut dyn ItemRenderer = &mut skia_item_renderer;
520 let mut partial_renderer;
521 let mut dirty_region_to_visualize = None;
522
523 if let Some(partial_rendering_state) = self.partial_rendering_state() {
524 partial_renderer =
525 partial_rendering_state.create_partial_renderer(skia_item_renderer);
526
527 let mut dirty_region_history = self.dirty_region_history.borrow_mut();
528
529 let buffer_dirty_region = if back_buffer_age > 0
530 && back_buffer_age as usize - 1 < dirty_region_history.len()
531 {
532 // The dirty region is the union of all the previous dirty regions
533 Some(
534 dirty_region_history[0..back_buffer_age as usize - 1]
535 .iter()
536 .fold(DirtyRegion::default(), |acc, region| acc.union(region)),
537 )
538 } else {
539 Some(LogicalRect::from_size(logical_window_size).into())
540 };
541
542 let dirty_region_for_this_frame = partial_rendering_state.apply_dirty_region(
543 &mut partial_renderer,
544 components,
545 logical_window_size,
546 buffer_dirty_region,
547 );
548
549 let mut clip_path = skia_safe::Path::new();
550
551 for dirty_rect in partial_renderer.dirty_region.iter() {
552 let physical_rect = (dirty_rect * scale_factor).to_rect().round_out();
553 clip_path.add_rect(&to_skia_rect(&physical_rect), None);
554 }
555
556 if matches!(self.dirty_region_debug_mode, DirtyRegionDebugMode::Log) {
557 let area_to_repaint: f32 =
558 partial_renderer.dirty_region.iter().map(|b| b.area()).sum();
559 i_slint_core::debug_log!(
560 "repainting {:.2}%",
561 area_to_repaint * 100. / logical_window_size.area()
562 );
563 }
564
565 dirty_region = partial_renderer.dirty_region.clone().into();
566
567 dirty_region_history.rotate_right(1);
568 dirty_region_history[0] = dirty_region_for_this_frame;
569
570 skia_canvas.clip_path(&clip_path, None, false);
571
572 if matches!(self.dirty_region_debug_mode, DirtyRegionDebugMode::Visualize) {
573 dirty_region_to_visualize = Some(clip_path);
574 }
575
576 item_renderer = &mut partial_renderer;
577 }
578
579 if let Some(window_item_rc) = window_inner.window_item_rc() {
580 let window_item =
581 window_item_rc.downcast::<i_slint_core::items::WindowItem>().unwrap();
582 match window_item.as_pin_ref().background() {
583 Brush::SolidColor(clear_color) => {
584 skia_canvas.clear(itemrenderer::to_skia_color(&clear_color));
585 }
586 _ => {
587 // Draws the window background as gradient
588 item_renderer.draw_rectangle(
589 window_item.as_pin_ref(),
590 &window_item_rc,
591 i_slint_core::lengths::logical_size_from_api(
592 window.size().to_logical(window_inner.scale_factor()),
593 ),
594 &window_item.as_pin_ref().cached_rendering_data,
595 );
596 }
597 }
598 }
599
600 if let Some(callback) = self.rendering_notifier.borrow_mut().as_mut() {
601 // For the BeforeRendering rendering notifier callback it's important that this happens *after* clearing
602 // the back buffer, in order to allow the callback to provide its own rendering of the background.
603 // Skia's clear() will merely schedule a clear call, so flush right away to make it immediate.
604 if let Some(ctx) = gr_context.as_mut() {
605 ctx.flush(None);
606 }
607
608 if let Some(surface) = surface {
609 surface.with_graphics_api(&mut |api| {
610 callback.notify(RenderingState::BeforeRendering, &api)
611 })
612 }
613 }
614
615 for (component, origin) in components {
616 i_slint_core::item_rendering::render_component_items(
617 component,
618 item_renderer,
619 *origin,
620 &window_adapter,
621 );
622 }
623
624 if let Some(path) = dirty_region_to_visualize {
625 let mut paint = skia_safe::Paint::new(
626 &skia_safe::Color4f { a: 0.5, r: 1.0, g: 0., b: 0. },
627 None,
628 );
629 paint.set_style(skia_safe::PaintStyle::Stroke);
630 skia_canvas.draw_path(&path, &paint);
631 }
632
633 if let Some(collector) = &self.rendering_metrics_collector.borrow_mut().as_ref() {
634 collector.measure_frame_rendered(item_renderer);
635 if collector.refresh_mode()
636 == i_slint_core::graphics::rendering_metrics_collector::RefreshMode::FullSpeed
637 {
638 if let Some(partial_rendering_state) = self.partial_rendering_state() {
639 partial_rendering_state.force_screen_refresh();
640 }
641 }
642 }
643
644 if let Some(cb) = post_render_cb.as_ref() {
645 cb(item_renderer)
646 }
647 }
648
649 if let Some(ctx) = gr_context.as_mut() {
650 ctx.flush(None);
651 }
652
653 dirty_region
654 }
655
656 fn window_adapter(&self) -> Result<Rc<dyn WindowAdapter>, PlatformError> {
657 self.maybe_window_adapter.borrow().as_ref().and_then(|w| w.upgrade()).ok_or_else(|| {
658 "Renderer must be associated with component before use".to_string().into()
659 })
660 }
661
662 /// Sets the specified callback, that's invoked before presenting the rendered buffer to the windowing system.
663 /// This can be useful to implement frame throttling, i.e. for requesting a frame callback from the wayland compositor.
664 pub fn set_pre_present_callback(&self, callback: Option<Box<dyn FnMut()>>) {
665 *self.pre_present_callback.borrow_mut() = callback;
666 }
667
668 fn partial_rendering_state(&self) -> Option<&PartialRenderingState> {
669 // We don't know where the application might render to, so disable partial rendering.
670 if self.rendering_notifier.borrow().is_some() {
671 None
672 } else {
673 self.partial_rendering_state.as_ref()
674 }
675 }
676}
677
678impl i_slint_core::renderer::RendererSealed for SkiaRenderer {
679 fn text_size(
680 &self,
681 font_request: i_slint_core::graphics::FontRequest,
682 text: &str,
683 max_width: Option<LogicalLength>,
684 scale_factor: ScaleFactor,
685 _text_wrap: TextWrap, //TODO: Add support for char-wrap
686 ) -> LogicalSize {
687 let (layout, _) = textlayout::create_layout(
688 font_request,
689 scale_factor,
690 text,
691 None,
692 max_width.map(|w| w * scale_factor),
693 Default::default(),
694 Default::default(),
695 Default::default(),
696 Default::default(),
697 Default::default(),
698 None,
699 );
700
701 PhysicalSize::new(layout.max_intrinsic_width().ceil(), layout.height().ceil())
702 / scale_factor
703 }
704
705 fn font_metrics(
706 &self,
707 font_request: i_slint_core::graphics::FontRequest,
708 scale_factor: ScaleFactor,
709 ) -> i_slint_core::items::FontMetrics {
710 textlayout::font_metrics(font_request, scale_factor)
711 }
712
713 fn text_input_byte_offset_for_position(
714 &self,
715 text_input: std::pin::Pin<&i_slint_core::items::TextInput>,
716 pos: LogicalPoint,
717 font_request: FontRequest,
718 scale_factor: ScaleFactor,
719 ) -> usize {
720 let max_width = text_input.width() * scale_factor;
721 let max_height = text_input.height() * scale_factor;
722 let pos = pos * scale_factor;
723
724 if max_width.get() <= 0. || max_height.get() <= 0. {
725 return 0;
726 }
727
728 let visual_representation = text_input.visual_representation(None);
729
730 let (layout, layout_top_left) = textlayout::create_layout(
731 font_request,
732 scale_factor,
733 &visual_representation.text,
734 None,
735 Some(max_width),
736 max_height,
737 text_input.horizontal_alignment(),
738 text_input.vertical_alignment(),
739 text_input.wrap(),
740 i_slint_core::items::TextOverflow::Clip,
741 None,
742 );
743
744 let utf16_index =
745 layout.get_glyph_position_at_coordinate((pos.x, pos.y - layout_top_left.y)).position;
746 let mut utf16_count = 0;
747 let byte_offset = visual_representation
748 .text
749 .char_indices()
750 .find(|(_, x)| {
751 let r = utf16_count >= utf16_index;
752 utf16_count += x.len_utf16() as i32;
753 r
754 })
755 .unwrap_or((visual_representation.text.len(), '\0'))
756 .0;
757
758 visual_representation.map_byte_offset_from_byte_offset_in_visual_text(byte_offset)
759 }
760
761 fn text_input_cursor_rect_for_byte_offset(
762 &self,
763 text_input: std::pin::Pin<&i_slint_core::items::TextInput>,
764 byte_offset: usize,
765 font_request: FontRequest,
766 scale_factor: ScaleFactor,
767 ) -> LogicalRect {
768 let max_width = text_input.width() * scale_factor;
769 let max_height = text_input.height() * scale_factor;
770
771 if max_width.get() <= 0. || max_height.get() <= 0. {
772 return Default::default();
773 }
774
775 let string = text_input.text();
776 let string = string.as_str();
777
778 let (layout, layout_top_left) = textlayout::create_layout(
779 font_request,
780 scale_factor,
781 string,
782 None,
783 Some(max_width),
784 max_height,
785 text_input.horizontal_alignment(),
786 text_input.vertical_alignment(),
787 text_input.wrap(),
788 i_slint_core::items::TextOverflow::Clip,
789 None,
790 );
791
792 let physical_cursor_rect = textlayout::cursor_rect(
793 string,
794 byte_offset,
795 layout,
796 text_input.text_cursor_width() * scale_factor,
797 text_input.horizontal_alignment(),
798 );
799
800 physical_cursor_rect.translate(layout_top_left.to_vector()) / scale_factor
801 }
802
803 fn register_font_from_memory(
804 &self,
805 data: &'static [u8],
806 ) -> Result<(), Box<dyn std::error::Error>> {
807 textlayout::register_font_from_memory(data)
808 }
809
810 fn register_font_from_path(
811 &self,
812 path: &std::path::Path,
813 ) -> Result<(), Box<dyn std::error::Error>> {
814 textlayout::register_font_from_path(path)
815 }
816
817 fn set_rendering_notifier(
818 &self,
819 callback: Box<dyn RenderingNotifier>,
820 ) -> std::result::Result<(), SetRenderingNotifierError> {
821 if !self.surface.borrow().as_ref().map_or(DefaultSurface::supports_graphics_api(), |x| {
822 x.supports_graphics_api_with_self()
823 }) {
824 return Err(SetRenderingNotifierError::Unsupported);
825 }
826 let mut notifier = self.rendering_notifier.borrow_mut();
827 if notifier.replace(callback).is_some() {
828 Err(SetRenderingNotifierError::AlreadySet)
829 } else {
830 Ok(())
831 }
832 }
833
834 fn default_font_size(&self) -> LogicalLength {
835 self::textlayout::DEFAULT_FONT_SIZE
836 }
837
838 fn free_graphics_resources(
839 &self,
840 component: i_slint_core::item_tree::ItemTreeRef,
841 items: &mut dyn Iterator<Item = std::pin::Pin<i_slint_core::items::ItemRef<'_>>>,
842 ) -> Result<(), i_slint_core::platform::PlatformError> {
843 self.image_cache.component_destroyed(component);
844 self.path_cache.component_destroyed(component);
845
846 if let Some(partial_rendering_state) = self.partial_rendering_state() {
847 partial_rendering_state.free_graphics_resources(items);
848 }
849
850 Ok(())
851 }
852
853 fn set_window_adapter(&self, window_adapter: &Rc<dyn WindowAdapter>) {
854 *self.maybe_window_adapter.borrow_mut() = Some(Rc::downgrade(window_adapter));
855 self.image_cache.clear_all();
856 self.path_cache.clear_all();
857
858 if let Some(partial_rendering_state) = self.partial_rendering_state() {
859 partial_rendering_state.clear_cache();
860 }
861 }
862
863 fn resize(&self, size: i_slint_core::api::PhysicalSize) -> Result<(), PlatformError> {
864 if let Some(surface) = self.surface.borrow().as_ref() {
865 surface.resize_event(size)
866 } else {
867 Ok(())
868 }
869 }
870
871 /// Returns an image buffer of what was rendered last by reading the previous front buffer (using glReadPixels).
872 fn take_snapshot(
873 &self,
874 ) -> Result<SharedPixelBuffer<i_slint_core::graphics::Rgba8Pixel>, PlatformError> {
875 let window_adapter = self.window_adapter()?;
876 let window = window_adapter.window();
877 let size = window_adapter.window().size();
878 let (width, height) = (size.width, size.height);
879 let mut target_buffer =
880 SharedPixelBuffer::<i_slint_core::graphics::Rgba8Pixel>::new(width, height);
881
882 let mut surface_borrow = skia_safe::surfaces::wrap_pixels(
883 &skia_safe::ImageInfo::new(
884 (width as i32, height as i32),
885 skia_safe::ColorType::RGBA8888,
886 skia_safe::AlphaType::Opaque,
887 None,
888 ),
889 target_buffer.make_mut_bytes(),
890 None,
891 None,
892 )
893 .ok_or_else(|| "Error wrapping target buffer for rendering into with Skia".to_string())?;
894
895 self.render_to_canvas(surface_borrow.canvas(), 0., (0.0, 0.0), None, 0, None, window, None);
896
897 Ok(target_buffer)
898 }
899
900 fn mark_dirty_region(&self, region: i_slint_core::item_rendering::DirtyRegion) {
901 if let Some(partial_rendering_state) = self.partial_rendering_state() {
902 partial_rendering_state.mark_dirty_region(region);
903 }
904 }
905}
906
907impl Drop for SkiaRenderer {
908 fn drop(&mut self) {
909 self.clear_surface()
910 }
911}
912
913/// This trait represents the interface between the Skia renderer and the underlying rendering surface, such as a window
914/// with a metal layer, a wayland window with an OpenGL context, etc.
915pub trait Surface {
916 /// Creates a new surface with the given window, display, and size.
917 fn new(
918 window_handle: Rc<dyn raw_window_handle::HasWindowHandle>,
919 display_handle: Rc<dyn raw_window_handle::HasDisplayHandle>,
920 size: PhysicalWindowSize,
921 requested_graphics_api: Option<RequestedGraphicsAPI>,
922 ) -> Result<Self, PlatformError>
923 where
924 Self: Sized;
925 /// Returns the name of the surface, for diagnostic purposes.
926 fn name(&self) -> &'static str;
927 /// Returns true if the surface supports exposing its platform specific API via the GraphicsAPI struct
928 /// and the `with_graphics_api` function.
929 fn supports_graphics_api() -> bool
930 where
931 Self: Sized,
932 {
933 false
934 }
935
936 fn supports_graphics_api_with_self(&self) -> bool {
937 false
938 }
939
940 /// If supported, this invokes the specified callback with access to the platform graphics API.
941 fn with_graphics_api(&self, _callback: &mut dyn FnMut(GraphicsAPI<'_>)) {}
942 /// Invokes the callback with the surface active. This has only a meaning for OpenGL rendering, where
943 /// the implementation must make the GL context current.
944 fn with_active_surface(
945 &self,
946 callback: &mut dyn FnMut(),
947 ) -> Result<(), i_slint_core::platform::PlatformError> {
948 callback();
949 Ok(())
950 }
951 /// Prepares the surface for rendering and invokes the provided callback with access to a Skia canvas and
952 /// rendering context.
953 fn render(
954 &self,
955 window: &Window,
956 size: PhysicalWindowSize,
957 render_callback: &dyn Fn(
958 &skia_safe::Canvas,
959 Option<&mut skia_safe::gpu::DirectContext>,
960 u8,
961 ) -> Option<DirtyRegion>,
962 pre_present_callback: &RefCell<Option<Box<dyn FnMut()>>>,
963 ) -> Result<(), i_slint_core::platform::PlatformError>;
964 /// Called when the surface should be resized.
965 fn resize_event(
966 &self,
967 size: PhysicalWindowSize,
968 ) -> Result<(), i_slint_core::platform::PlatformError>;
969 fn bits_per_pixel(&self) -> Result<u8, PlatformError>;
970
971 fn use_partial_rendering(&self) -> bool {
972 false
973 }
974
975 /// Implementations should return self to allow upcasting.
976 fn as_any(&self) -> &dyn core::any::Any {
977 &()
978 }
979}
980
981pub trait SkiaRendererExt {
982 fn render_transformed_with_post_callback(
983 &self,
984 rotation_angle_degrees: f32,
985 translation: (f32, f32),
986 surface_size: PhysicalWindowSize,
987 post_render_cb: Option<&dyn Fn(&mut dyn ItemRenderer)>,
988 ) -> Result<(), i_slint_core::platform::PlatformError>;
989}
990
991impl SkiaRendererExt for SkiaRenderer {
992 fn render_transformed_with_post_callback(
993 &self,
994 rotation_angle_degrees: f32,
995 translation: (f32, f32),
996 surface_size: PhysicalWindowSize,
997 post_render_cb: Option<&dyn Fn(&mut dyn ItemRenderer)>,
998 ) -> Result<(), i_slint_core::platform::PlatformError> {
999 self.internal_render_with_post_callback(
1000 rotation_angle_degrees,
1001 translation,
1002 surface_size,
1003 post_render_cb,
1004 )
1005 }
1006}
1007