From cf905312a32f4f454775ecbcf89e4278dc030355 Mon Sep 17 00:00:00 2001 From: Tristan Ross Date: Sun, 5 Oct 2025 13:54:33 -0700 Subject: [PATCH] Add UEFI support --- Cargo.toml | 5 +- src/backend_dispatch.rs | 2 + src/backends/mod.rs | 2 + src/backends/uefi.rs | 229 ++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 4 +- 5 files changed, 240 insertions(+), 2 deletions(-) create mode 100644 src/backends/uefi.rs diff --git a/Cargo.toml b/Cargo.toml index d866e24..0c54558 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,7 @@ x11 = [ x11-dlopen = ["tiny-xlib/dlopen", "x11rb/dl-libxcb"] [dependencies] -raw_window_handle = { package = "raw-window-handle", version = "0.6", features = [ +raw_window_handle = { package = "raw-window-handle", git = "https://github.com/RossComputerGuy/raw-window-handle", branch = "feat/uefi", features = [ "std", ] } tracing = { version = "0.1.41", default-features = false } @@ -130,6 +130,9 @@ features = [ [target.'cfg(target_os = "redox")'.dependencies] redox_syscall = "0.5" +[target.'cfg(target_os = "uefi")'.dependencies] +uefi = "0.35.0" + [build-dependencies] cfg_aliases = "0.2.0" diff --git a/src/backend_dispatch.rs b/src/backend_dispatch.rs index 3b5d269..c672b5d 100644 --- a/src/backend_dispatch.rs +++ b/src/backend_dispatch.rs @@ -195,4 +195,6 @@ make_dispatch! { Web(backends::web::WebDisplayImpl, backends::web::WebImpl, backends::web::BufferImpl<'a, D, W>), #[cfg(target_os = "redox")] Orbital(D, backends::orbital::OrbitalImpl, backends::orbital::BufferImpl<'a, D, W>), + #[cfg(target_os = "uefi")] + Uefi(backends::uefi::UefiDisplayImpl, backends::uefi::UefiImpl, backends::uefi::BufferImpl<'a, D, W>), } diff --git a/src/backends/mod.rs b/src/backends/mod.rs index 703401d..47db81f 100644 --- a/src/backends/mod.rs +++ b/src/backends/mod.rs @@ -9,6 +9,8 @@ pub(crate) mod cg; pub(crate) mod kms; #[cfg(target_os = "redox")] pub(crate) mod orbital; +#[cfg(target_os = "uefi")] +pub(crate) mod uefi; #[cfg(wayland_platform)] pub(crate) mod wayland; #[cfg(target_arch = "wasm32")] diff --git a/src/backends/uefi.rs b/src/backends/uefi.rs new file mode 100644 index 0000000..696ee0e --- /dev/null +++ b/src/backends/uefi.rs @@ -0,0 +1,229 @@ +use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawDisplayHandle}; +use uefi::boot::{self, OpenProtocolAttributes, OpenProtocolParams}; +use uefi::proto::console::gop::{BltOp, BltPixel, BltRegion, GraphicsOutput, PixelFormat}; + +use crate::backend_interface::*; +use crate::error::{InitError, SwResultExt}; +use crate::{util, Rect, SoftBufferError}; +use std::marker::PhantomData; +use std::num::NonZeroU32; + +#[derive(Clone)] +pub struct UefiDisplayImpl { + display: D, +} + +impl ContextInterface for UefiDisplayImpl { + fn new(display: D) -> Result> { + let raw = display.display_handle()?.as_raw(); + let RawDisplayHandle::Uefi(..) = raw else { + return Err(InitError::Unsupported(display)); + }; + + Ok(Self { display }) + } +} + +pub struct UefiImpl { + /// The current canvas width/height. + size: Option<(NonZeroU32, NonZeroU32)>, + + /// The buffer that we're drawing to. + buffer: Vec, + + /// Buffer has been presented. + buffer_presented: bool, + + /// The underlying window handle. + window_handle: W, + + /// The underlying display handle. + _display: PhantomData, + + /// The UEFI protocol for graphics output. + proto: boot::ScopedProtocol, +} + +impl UefiImpl { + fn present_with_damage(&mut self, damage: &[Rect]) -> Result<(), SoftBufferError> { + let (buffer_width, _buffer_height) = self + .size + .expect("Must set size of surface before calling `present_with_damage()`"); + + let union_damage = if let Some(rect) = util::union_damage(damage) { + rect + } else { + return Ok(()); + }; + + let bitmap: Vec<_> = self + .buffer + .chunks_exact(buffer_width.get() as usize) + .skip(union_damage.y as usize) + .take(union_damage.height.get() as usize) + .flat_map(|row| { + row.iter() + .skip(union_damage.x as usize) + .take(union_damage.width.get() as usize) + }) + .copied() + .map(|pixel| ((pixel >> 16) as u8, (pixel >> 8) as u8, pixel as u8)) + .collect(); + + debug_assert_eq!( + bitmap.len() as u32, + union_damage.width.get() * union_damage.height.get() * 4 + ); + + let mode = self.proto.current_mode_info(); + if mode.pixel_format() == PixelFormat::BltOnly { + self.proto + .blt(BltOp::BufferToVideo { + buffer: &bitmap + .into_iter() + .map(|pixel| BltPixel::new(pixel.0, pixel.1, pixel.2)) + .collect::>(), + src: BltRegion::Full, + dest: (union_damage.x as usize, union_damage.y as usize), + dims: ( + union_damage.width.get() as usize, + union_damage.height.get() as usize, + ), + }) + .swbuf_err("Failed to blt buffer")?; + } else { + let mut fb = self.proto.frame_buffer(); + unsafe { + fb.write_value( + (union_damage.y as usize) * (union_damage.width.get() as usize) + mode.stride(), + bitmap + .into_iter() + .flat_map(|pixel| [pixel.0, pixel.1, pixel.2, 255]) + .collect::>(), + ); + } + } + + self.buffer_presented = true; + + Ok(()) + } +} + +impl SurfaceInterface for UefiImpl { + type Context = UefiDisplayImpl; + type Buffer<'a> + = BufferImpl<'a, D, W> + where + Self: 'a; + + fn new(window_handle: W, display: &UefiDisplayImpl) -> Result> { + let raw = display.display.display_handle()?.as_raw(); + let RawDisplayHandle::Uefi(display) = raw else { + return Err(InitError::Failure(SoftBufferError::IncompleteDisplayHandle)); + }; + + let proto = unsafe { + boot::open_protocol::( + OpenProtocolParams { + handle: uefi::data_types::Handle::new(display.handle), + agent: boot::image_handle(), + controller: None, + }, + OpenProtocolAttributes::GetProtocol, + ) + } + .swbuf_err("Failed to open the graphics output protocol")?; + + Ok(Self { + size: None, + buffer: Vec::new(), + buffer_presented: false, + window_handle, + _display: PhantomData, + proto, + }) + } + + /// Get the inner window handle. + #[inline] + fn window(&self) -> &W { + &self.window_handle + } + + fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> { + if self.size != Some((width, height)) { + self.buffer_presented = false; + self.buffer.resize(total_len(width.get(), height.get()), 0); + self.size = Some((width, height)); + } + + Ok(()) + } + + fn buffer_mut(&mut self) -> Result, SoftBufferError> { + Ok(BufferImpl { imp: self }) + } + + fn fetch(&mut self) -> Result, SoftBufferError> { + Ok(self.buffer.clone()) + } +} + +pub struct BufferImpl<'a, D, W> { + imp: &'a mut UefiImpl, +} + +impl BufferInterface for BufferImpl<'_, D, W> { + #[inline] + fn pixels(&self) -> &[u32] { + &self.imp.buffer + } + + #[inline] + fn pixels_mut(&mut self) -> &mut [u32] { + &mut self.imp.buffer + } + + #[inline] + fn age(&self) -> u8 { + if self.imp.buffer_presented { + 1 + } else { + 0 + } + } + + fn present(self) -> Result<(), SoftBufferError> { + let (width, height) = self + .imp + .size + .expect("Must set size of surface before calling `present()`"); + + self.imp.present_with_damage(&[Rect { + x: 0, + y: 0, + width, + height, + }]) + } + + fn present_with_damage(self, damage: &[Rect]) -> Result<(), SoftBufferError> { + self.imp.present_with_damage(damage) + } +} + +#[inline(always)] +fn total_len(width: u32, height: u32) -> usize { + // Convert width and height to `usize`, then multiply. + width + .try_into() + .ok() + .and_then(|w: usize| height.try_into().ok().and_then(|h| w.checked_mul(h))) + .unwrap_or_else(|| { + panic!( + "Overflow when calculating total length of buffer: {}x{}", + width, height + ); + }) +} diff --git a/src/lib.rs b/src/lib.rs index ec3f1e9..1cce13a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -304,6 +304,7 @@ fn window_handle_type_name(handle: &RawWindowHandle) -> &'static str { RawWindowHandle::Drm(_) => "DRM", RawWindowHandle::Gbm(_) => "GBM", RawWindowHandle::Haiku(_) => "Haiku", + RawWindowHandle::Uefi(_) => "UEFI", _ => "Unknown Name", //don't completely fail to compile if there is a new raw window handle type that's added at some point } } @@ -322,11 +323,12 @@ fn display_handle_type_name(handle: &RawDisplayHandle) -> &'static str { RawDisplayHandle::Haiku(_) => "Haiku", RawDisplayHandle::Windows(_) => "Windows", RawDisplayHandle::Android(_) => "Android", + RawDisplayHandle::Uefi(_) => "UEFI", _ => "Unknown Name", //don't completely fail to compile if there is a new raw window handle type that's added at some point } } -#[cfg(not(target_family = "wasm"))] +#[cfg(not(any(target_family = "wasm", target_os = "uefi")))] fn __assert_send() { fn is_send() {} fn is_sync() {}