mirror of
https://github.com/slint-ui/slint.git
synced 2025-08-31 07:37:24 +00:00
Initial EGL implementation using gbm surfaces
This commit is contained in:
parent
5c9eed62f1
commit
236d6ec183
4 changed files with 235 additions and 3 deletions
|
@ -19,6 +19,10 @@ path = "lib.rs"
|
|||
i-slint-core = { version = "=1.2.0", path = "../../../internal/core" }
|
||||
i-slint-renderer-skia = { version = "=1.2.0", path = "../../renderers/skia", features = ["vulkan"] }
|
||||
vulkano = { version = "0.33.0", default-features = false }
|
||||
drm = { version = "0.9.0" }
|
||||
gbm = { version = "0.12.0", default-features = false, features = ["drm-support"] }
|
||||
glutin = { version = "0.30.8", default-features = false, features = ["libloading", "egl"] }
|
||||
raw-window-handle = "0.5.2"
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
input = { version = "0.8.2" }
|
||||
|
|
|
@ -10,13 +10,17 @@ use i_slint_core::api::PhysicalSize as PhysicalWindowSize;
|
|||
use i_slint_core::{platform::PlatformError, window::WindowAdapter};
|
||||
use i_slint_renderer_skia::SkiaRenderer;
|
||||
|
||||
mod egldisplay;
|
||||
mod vulkandisplay;
|
||||
|
||||
type PresentFn = Box<dyn Fn() -> Result<(), PlatformError>>;
|
||||
|
||||
pub struct SkiaWindowAdapter {
|
||||
window: i_slint_core::api::Window,
|
||||
renderer: SkiaRenderer,
|
||||
needs_redraw: Cell<bool>,
|
||||
size: PhysicalWindowSize,
|
||||
present_fn: PresentFn, // last field that keeps the gbm surface alive, that needs to outlive the renderer.
|
||||
}
|
||||
|
||||
impl WindowAdapter for SkiaWindowAdapter {
|
||||
|
@ -39,11 +43,13 @@ impl WindowAdapter for SkiaWindowAdapter {
|
|||
|
||||
impl SkiaWindowAdapter {
|
||||
pub fn new() -> Result<Rc<Self>, PlatformError> {
|
||||
let (renderer, size) = vulkandisplay::create_skia_renderer_with_vulkan()?;
|
||||
let (renderer, size, present_fn) = vulkandisplay::create_skia_renderer_with_vulkan()
|
||||
.or_else(|_| egldisplay::create_skia_renderer_with_egl())?;
|
||||
|
||||
Ok(Rc::<SkiaWindowAdapter>::new_cyclic(|self_weak| SkiaWindowAdapter {
|
||||
window: i_slint_core::api::Window::new(self_weak.clone()),
|
||||
renderer,
|
||||
present_fn,
|
||||
needs_redraw: Cell::new(true),
|
||||
size,
|
||||
}))
|
||||
|
@ -52,6 +58,7 @@ impl SkiaWindowAdapter {
|
|||
pub fn render_if_needed(&self) -> Result<(), PlatformError> {
|
||||
if self.needs_redraw.replace(false) {
|
||||
self.renderer.render()?;
|
||||
(self.present_fn)()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
219
internal/backends/linuxkms/skiawindowadapter/egldisplay.rs
Normal file
219
internal/backends/linuxkms/skiawindowadapter/egldisplay.rs
Normal file
|
@ -0,0 +1,219 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::fs::File;
|
||||
use std::os::fd::{AsFd, BorrowedFd};
|
||||
use std::sync::Arc;
|
||||
|
||||
use drm::control::Device;
|
||||
use gbm::AsRaw;
|
||||
use i_slint_core::api::PhysicalSize as PhysicalWindowSize;
|
||||
use i_slint_core::platform::PlatformError;
|
||||
use i_slint_renderer_skia::SkiaRenderer;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SharedFd(Arc<File>);
|
||||
impl AsFd for SharedFd {
|
||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||
self.0.as_fd()
|
||||
}
|
||||
}
|
||||
|
||||
impl drm::Device for SharedFd {}
|
||||
|
||||
impl drm::control::Device for SharedFd {}
|
||||
|
||||
struct OwnedFramebufferHandle {
|
||||
handle: drm::control::framebuffer::Handle,
|
||||
device: SharedFd,
|
||||
}
|
||||
|
||||
impl Drop for OwnedFramebufferHandle {
|
||||
fn drop(&mut self) {
|
||||
self.device.destroy_framebuffer(self.handle).ok();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_skia_renderer_with_egl(
|
||||
) -> Result<(SkiaRenderer, PhysicalWindowSize, super::PresentFn), PlatformError> {
|
||||
let drm_device = SharedFd(Arc::new(
|
||||
std::fs::OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open("/dev/dri/card0")
|
||||
.map_err(|err| format!("Error opening /dev/dri/card0: {}", err))?,
|
||||
));
|
||||
|
||||
let resources = drm_device
|
||||
.resource_handles()
|
||||
.map_err(|e| format!("Error reading DRM resource handles: {e}"))?;
|
||||
|
||||
let connector = if let Ok(requested_connector_name) = std::env::var("SLINT_DRM_OUTPUT") {
|
||||
let mut connectors = resources.connectors().iter().filter_map(|handle| {
|
||||
let connector = drm_device.get_connector(*handle, false).ok()?;
|
||||
let name = format!("{}-{}", connector.interface().as_str(), connector.interface_id());
|
||||
let connected = connector.state() == drm::control::connector::State::Connected;
|
||||
Some((name, connector, connected))
|
||||
});
|
||||
|
||||
if requested_connector_name.eq_ignore_ascii_case("list") {
|
||||
let names_and_status = connectors
|
||||
.map(|(name, _, connected)| format!("{} (connected: {})", name, connected))
|
||||
.collect::<Vec<_>>();
|
||||
// Can't return error here because newlines are escaped.
|
||||
panic!("\nDRM Output List Requested:\n{}\n", names_and_status.join("\n"));
|
||||
} else {
|
||||
let (_, connector, connected) =
|
||||
connectors.find(|(name, _, _)| name == &requested_connector_name).ok_or_else(
|
||||
|| format!("No output with the name '{}' found", requested_connector_name),
|
||||
)?;
|
||||
|
||||
if !connected {
|
||||
return Err(format!(
|
||||
"Requested output '{}' is not connected",
|
||||
requested_connector_name
|
||||
)
|
||||
.into());
|
||||
};
|
||||
|
||||
connector
|
||||
}
|
||||
} else {
|
||||
resources
|
||||
.connectors()
|
||||
.iter()
|
||||
.find_map(|handle| {
|
||||
let connector = drm_device.get_connector(*handle, false).ok()?;
|
||||
(connector.state() == drm::control::connector::State::Connected).then(|| connector)
|
||||
})
|
||||
.ok_or_else(|| format!("No connected display connector found"))?
|
||||
};
|
||||
|
||||
let mode = *connector
|
||||
.modes()
|
||||
.iter()
|
||||
.max_by(|current_mode, next_mode| {
|
||||
let current = (
|
||||
current_mode.mode_type().contains(drm::control::ModeTypeFlags::PREFERRED),
|
||||
current_mode.size().0 as u32 * current_mode.size().1 as u32,
|
||||
);
|
||||
let next = (
|
||||
next_mode.mode_type().contains(drm::control::ModeTypeFlags::PREFERRED),
|
||||
next_mode.size().0 as u32 * next_mode.size().1 as u32,
|
||||
);
|
||||
|
||||
current.cmp(&next)
|
||||
})
|
||||
.ok_or_else(|| format!("No preferred or non-zero size display mode found"))?;
|
||||
|
||||
let encoder = connector
|
||||
.encoders()
|
||||
.iter()
|
||||
.find_map(|handle| {
|
||||
if connector.current_encoder() == Some(*handle) {
|
||||
drm_device.get_encoder(*handle).ok()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.ok_or_else(|| format!("Not encoder found for connector"))?;
|
||||
|
||||
let crtc = encoder.crtc().ok_or_else(|| format!("no crtc for encoder"))?;
|
||||
|
||||
let (width, height) = mode.size();
|
||||
let width = std::num::NonZeroU32::new(width as _)
|
||||
.ok_or_else(|| format!("Invalid mode screen width {width}"))?;
|
||||
let height = std::num::NonZeroU32::new(height as _)
|
||||
.ok_or_else(|| format!("Invalid mode screen height {height}"))?;
|
||||
|
||||
//eprintln!("mode {}/{}", width, height);
|
||||
|
||||
let gbm_device = gbm::Device::new(drm_device.clone())
|
||||
.map_err(|e| format!("Error creating gbm device: {e}"))?;
|
||||
|
||||
let gbm_surface = gbm_device
|
||||
.create_surface::<OwnedFramebufferHandle>(
|
||||
width.get(),
|
||||
height.get(),
|
||||
gbm::Format::Xrgb8888,
|
||||
gbm::BufferObjectFlags::SCANOUT | gbm::BufferObjectFlags::RENDERING,
|
||||
)
|
||||
.map_err(|e| format!("Error creating gbm surface: {e}"))?;
|
||||
|
||||
let window_size = PhysicalWindowSize::new(width.get(), height.get());
|
||||
|
||||
let mut gbm_display_handle = raw_window_handle::GbmDisplayHandle::empty();
|
||||
gbm_display_handle.gbm_device = gbm_device.as_raw() as _;
|
||||
|
||||
let mut gbm_surface_handle = raw_window_handle::GbmWindowHandle::empty();
|
||||
gbm_surface_handle.gbm_surface = gbm_surface.as_raw() as _;
|
||||
|
||||
// Safety: This is safe because the handle remains valid; the next rwh release provides `new()` without unsafe.
|
||||
let active_handle = unsafe { raw_window_handle::ActiveHandle::new_unchecked() };
|
||||
|
||||
// Safety: API wise we can't guarantee that the window/display handles remain valid, so we
|
||||
// use unsafe here. However the present closure below keeps the surface alive,
|
||||
// and the window adapter closure and renderer alive at the same time.
|
||||
let (window_handle, display_handle) = unsafe {
|
||||
(
|
||||
raw_window_handle::WindowHandle::borrow_raw(
|
||||
raw_window_handle::RawWindowHandle::Gbm(gbm_surface_handle),
|
||||
active_handle,
|
||||
),
|
||||
raw_window_handle::DisplayHandle::borrow_raw(raw_window_handle::RawDisplayHandle::Gbm(
|
||||
gbm_display_handle,
|
||||
)),
|
||||
)
|
||||
};
|
||||
|
||||
use i_slint_renderer_skia::Surface;
|
||||
let surface = i_slint_renderer_skia::opengl_surface::OpenGLSurface::new(
|
||||
window_handle,
|
||||
display_handle,
|
||||
window_size,
|
||||
)?;
|
||||
|
||||
let last_buffer = Cell::default();
|
||||
|
||||
let present_fn = move || {
|
||||
let mut front_buffer = unsafe {
|
||||
gbm_surface
|
||||
.lock_front_buffer()
|
||||
.map_err(|e| format!("Error locking gmb surface front buffer: {e}"))?
|
||||
};
|
||||
// TODO: respect modifiers & planes if front_buffer has, use add_planar_framebuffer and fall back to add_framebuffer
|
||||
let fb = gbm_device
|
||||
.add_framebuffer(&front_buffer, 24, 32)
|
||||
.map_err(|e| format!("Error adding gbm buffer as framebuffer: {e}"))?;
|
||||
front_buffer
|
||||
.set_userdata(OwnedFramebufferHandle { handle: fb, device: drm_device.clone() })
|
||||
.map_err(|e| format!("Error setting userdata on gbm surface front buffer: {e}"))?;
|
||||
|
||||
if let Some(last_buffer) = last_buffer.replace(Some(front_buffer)) {
|
||||
gbm_device
|
||||
.page_flip(crtc, fb, drm::control::PageFlipFlags::EVENT, None)
|
||||
.map_err(|e| format!("Error presenting fb: {e}"))?;
|
||||
|
||||
for event in gbm_device.receive_events().unwrap() {
|
||||
if matches!(event, drm::control::Event::PageFlip(..)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
drop(last_buffer);
|
||||
} else {
|
||||
gbm_device
|
||||
.set_crtc(crtc, Some(fb), (0, 0), &[connector.handle()], Some(mode))
|
||||
.map_err(|e| format!("Error presenting fb: {e}"))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
};
|
||||
|
||||
Ok((
|
||||
i_slint_renderer_skia::SkiaRenderer::new_with_surface(surface),
|
||||
window_size,
|
||||
Box::new(present_fn),
|
||||
))
|
||||
}
|
|
@ -10,8 +10,10 @@ use vulkano::instance::{Instance, InstanceCreateInfo, InstanceExtensions};
|
|||
use vulkano::swapchain::display::{Display, DisplayPlane};
|
||||
use vulkano::VulkanLibrary;
|
||||
|
||||
use super::PresentFn;
|
||||
|
||||
pub fn create_skia_renderer_with_vulkan(
|
||||
) -> Result<(SkiaRenderer, PhysicalWindowSize), PlatformError> {
|
||||
) -> Result<(SkiaRenderer, PhysicalWindowSize, PresentFn), PlatformError> {
|
||||
let library = VulkanLibrary::new()
|
||||
.map_err(|load_err| format!("Error loading vulkan library: {load_err}"))?;
|
||||
|
||||
|
@ -144,5 +146,5 @@ pub fn create_skia_renderer_with_vulkan(
|
|||
size,
|
||||
)?;
|
||||
|
||||
Ok((SkiaRenderer::new_with_surface(surface), size))
|
||||
Ok((SkiaRenderer::new_with_surface(surface), size, Box::new(|| Ok(()))))
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue