Initial EGL implementation using gbm surfaces

This commit is contained in:
Simon Hausmann 2023-05-26 23:30:14 +02:00 committed by Simon Hausmann
parent 5c9eed62f1
commit 236d6ec183
4 changed files with 235 additions and 3 deletions

View file

@ -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" }

View file

@ -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(())
}

View 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),
))
}

View file

@ -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(()))))
}