slint/internal/backends/linuxkms/renderer/skia.rs
Simon Hausmann 302926edfa
Skia: Improve multi-window rendering resource consumption (#8304)
Share Vulkan instance as well as Metal device and command queue across windows.
2025-04-29 18:58:09 +02:00

212 lines
7.1 KiB
Rust

// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
use std::sync::Arc;
use crate::display::RenderingRotation;
use crate::drmoutput::DrmOutput;
use i_slint_core::api::{PhysicalSize as PhysicalWindowSize, Window};
use i_slint_core::item_rendering::{DirtyRegion, ItemRenderer};
use i_slint_core::platform::PlatformError;
use i_slint_renderer_skia::SkiaRendererExt;
use i_slint_renderer_skia::{skia_safe, SkiaRenderer, SkiaSharedContext};
pub struct SkiaRendererAdapter {
renderer: i_slint_renderer_skia::SkiaRenderer,
presenter: Arc<dyn crate::display::Presenter>,
size: PhysicalWindowSize,
}
impl SkiaRendererAdapter {
#[cfg(feature = "renderer-skia-vulkan")]
pub fn new_vulkan(
_device_opener: &crate::DeviceOpener,
) -> Result<Box<dyn crate::fullscreenwindowadapter::FullscreenRenderer>, PlatformError> {
// TODO: figure out how to associate vulkan with an existing drm fd.
let display = crate::display::vulkandisplay::create_vulkan_display()?;
let skia_vk_surface = i_slint_renderer_skia::vulkan_surface::VulkanSurface::from_surface(
display.physical_device,
display.queue_family_index,
display.surface,
display.size,
)?;
let renderer = Box::new(Self {
renderer: SkiaRenderer::new_with_surface(
&SkiaSharedContext::default(),
Box::new(skia_vk_surface),
),
// TODO: For vulkan we don't have a page flip event handling mechanism yet, so drive it with a timer.
presenter: display.presenter,
size: display.size,
});
eprintln!("Using Skia Vulkan renderer");
Ok(renderer)
}
#[cfg(feature = "renderer-skia-opengl")]
pub fn new_opengl(
device_opener: &crate::DeviceOpener,
) -> Result<Box<dyn crate::fullscreenwindowadapter::FullscreenRenderer>, PlatformError> {
let drm_output = DrmOutput::new(device_opener)?;
let display = Arc::new(crate::display::gbmdisplay::GbmDisplay::new(drm_output)?);
let (width, height) = display.drm_output.size();
let size = i_slint_core::api::PhysicalSize::new(width, height);
let skia_gl_surface =
i_slint_renderer_skia::opengl_surface::OpenGLSurface::new_with_config(
display.clone(),
display.clone(),
size,
None,
display.config_template_builder(),
Some(&|config| display.filter_gl_config(config)),
)?;
let renderer = Box::new(Self {
renderer: SkiaRenderer::new_with_surface(
&SkiaSharedContext::default(),
Box::new(skia_gl_surface),
),
presenter: display.clone(),
size,
});
renderer.renderer.set_pre_present_callback(Some(Box::new({
move || {
// Make sure the in-flight font-buffer from the previous swap_buffers call has been
// posted to the screen.
display.drm_output.wait_for_page_flip();
}
})));
eprintln!("Using Skia OpenGL renderer");
Ok(renderer)
}
pub fn new_software(
device_opener: &crate::DeviceOpener,
) -> Result<Box<dyn crate::fullscreenwindowadapter::FullscreenRenderer>, PlatformError> {
let display = crate::display::swdisplay::new(device_opener)?;
let skia_software_surface: i_slint_renderer_skia::software_surface::SoftwareSurface =
DrmDumbBufferAccess { display: display.clone() }.into();
let (width, height) = display.size();
let size = i_slint_core::api::PhysicalSize::new(width, height);
let renderer = Box::new(Self {
renderer: SkiaRenderer::new_with_surface(
&SkiaSharedContext::default(),
Box::new(skia_software_surface),
),
presenter: display.as_presenter(),
size,
});
eprintln!("Using Skia Software renderer");
Ok(renderer)
}
pub fn new_try_vulkan_then_opengl_then_software(
device_opener: &crate::DeviceOpener,
) -> Result<Box<dyn crate::fullscreenwindowadapter::FullscreenRenderer>, PlatformError> {
#[allow(unused_assignments)]
let mut result = Err(format!("No skia renderer available").into());
#[cfg(feature = "renderer-skia-vulkan")]
{
result = Self::new_vulkan(device_opener);
}
#[cfg(feature = "renderer-skia-opengl")]
if result.is_err() {
result = Self::new_opengl(device_opener);
}
if result.is_err() {
result = Self::new_software(device_opener);
}
result
}
}
impl crate::fullscreenwindowadapter::FullscreenRenderer for SkiaRendererAdapter {
fn as_core_renderer(&self) -> &dyn i_slint_core::renderer::Renderer {
&self.renderer
}
fn render_and_present(
&self,
rotation: RenderingRotation,
draw_mouse_cursor_callback: &dyn Fn(&mut dyn ItemRenderer),
) -> Result<(), PlatformError> {
self.renderer.render_transformed_with_post_callback(
rotation.degrees(),
rotation.translation_after_rotation(self.size),
self.size,
Some(&|item_renderer| {
draw_mouse_cursor_callback(item_renderer);
}),
)?;
self.presenter.present()?;
Ok(())
}
fn size(&self) -> i_slint_core::api::PhysicalSize {
self.size
}
}
struct DrmDumbBufferAccess {
display: Arc<dyn crate::display::swdisplay::SoftwareBufferDisplay>,
}
impl i_slint_renderer_skia::software_surface::RenderBuffer for DrmDumbBufferAccess {
fn with_buffer(
&self,
_window: &Window,
size: PhysicalWindowSize,
render_callback: &mut dyn FnMut(
std::num::NonZeroU32,
std::num::NonZeroU32,
skia_safe::ColorType,
u8,
&mut [u8],
) -> Result<
Option<DirtyRegion>,
i_slint_core::platform::PlatformError,
>,
) -> Result<(), i_slint_core::platform::PlatformError> {
let Some((width, height)) = size.width.try_into().ok().zip(size.height.try_into().ok())
else {
// Nothing to render
return Ok(());
};
self.display.map_back_buffer(&mut |pixels, age, format| {
render_callback(
width,
height,
match format {
drm::buffer::DrmFourcc::Xrgb8888 => skia_safe::ColorType::BGRA8888,
drm::buffer::DrmFourcc::Rgb565 => skia_safe::ColorType::RGB565,
_ => {
return Err(format!(
"Unsupported frame buffer format {format} used with skia software renderer"
)
.into())
}
},
age,
pixels.as_mut(),
)?;
Ok(())
})
}
}