use crate::proto::unsafe_protocol;
use crate::util::usize_from_u32;
use crate::{Result, StatusExt, boot};
use core::fmt::{Debug, Formatter};
use core::marker::PhantomData;
use core::ptr::{self, NonNull};
use uefi_raw::protocol::console::{
GraphicsOutputBltOperation, GraphicsOutputBltPixel, GraphicsOutputModeInformation,
GraphicsOutputProtocol, GraphicsOutputProtocolMode,
};
pub use uefi_raw::protocol::console::PixelBitmask;
#[derive(Debug)]
#[repr(transparent)]
#[unsafe_protocol(GraphicsOutputProtocol::GUID)]
pub struct GraphicsOutput(GraphicsOutputProtocol);
impl GraphicsOutput {
fn query_mode(&self, index: u32) -> Result<Mode> {
let mut info_sz = 0;
let mut info_heap_ptr = ptr::null();
unsafe { (self.0.query_mode)(&self.0, index, &mut info_sz, &mut info_heap_ptr) }
.to_result_with_val(|| {
let info = unsafe { *info_heap_ptr };
let info_heap_ptr = info_heap_ptr.cast::<u8>().cast_mut();
unsafe { boot::free_pool(NonNull::new(info_heap_ptr).unwrap()) }
.expect("buffer should be deallocatable");
Mode {
index,
info_sz,
info: ModeInfo(info),
}
})
}
#[must_use]
pub const fn modes(&self) -> ModeIter<'_> {
ModeIter {
gop: self,
current: 0,
max: self.mode().max_mode,
}
}
pub fn set_mode(&mut self, mode: &Mode) -> Result {
unsafe { (self.0.set_mode)(&mut self.0, mode.index) }.to_result()
}
pub fn blt(&mut self, op: BltOp) -> Result {
unsafe {
match op {
BltOp::VideoFill {
color,
dest: (dest_x, dest_y),
dims: (width, height),
} => {
self.check_framebuffer_region((dest_x, dest_y), (width, height));
(self.0.blt)(
&mut self.0,
ptr::from_ref(&color) as *mut _,
GraphicsOutputBltOperation::BLT_VIDEO_FILL,
0,
0,
dest_x,
dest_y,
width,
height,
0,
)
.to_result()
}
BltOp::VideoToBltBuffer {
buffer,
src: (src_x, src_y),
dest: dest_region,
dims: (width, height),
} => {
self.check_framebuffer_region((src_x, src_y), (width, height));
self.check_blt_buffer_region(dest_region, (width, height), buffer.len());
match dest_region {
BltRegion::Full => (self.0.blt)(
&mut self.0,
buffer.as_mut_ptr().cast(),
GraphicsOutputBltOperation::BLT_VIDEO_TO_BLT_BUFFER,
src_x,
src_y,
0,
0,
width,
height,
0,
)
.to_result(),
BltRegion::SubRectangle {
coords: (dest_x, dest_y),
px_stride,
} => (self.0.blt)(
&mut self.0,
buffer.as_mut_ptr().cast(),
GraphicsOutputBltOperation::BLT_VIDEO_TO_BLT_BUFFER,
src_x,
src_y,
dest_x,
dest_y,
width,
height,
px_stride * size_of::<BltPixel>(),
)
.to_result(),
}
}
BltOp::BufferToVideo {
buffer,
src: src_region,
dest: (dest_x, dest_y),
dims: (width, height),
} => {
self.check_blt_buffer_region(src_region, (width, height), buffer.len());
self.check_framebuffer_region((dest_x, dest_y), (width, height));
match src_region {
BltRegion::Full => (self.0.blt)(
&mut self.0,
buffer.as_ptr().cast::<GraphicsOutputBltPixel>().cast_mut(),
GraphicsOutputBltOperation::BLT_BUFFER_TO_VIDEO,
0,
0,
dest_x,
dest_y,
width,
height,
0,
)
.to_result(),
BltRegion::SubRectangle {
coords: (src_x, src_y),
px_stride,
} => (self.0.blt)(
&mut self.0,
buffer.as_ptr().cast::<GraphicsOutputBltPixel>().cast_mut(),
GraphicsOutputBltOperation::BLT_BUFFER_TO_VIDEO,
src_x,
src_y,
dest_x,
dest_y,
width,
height,
px_stride * size_of::<BltPixel>(),
)
.to_result(),
}
}
BltOp::VideoToVideo {
src: (src_x, src_y),
dest: (dest_x, dest_y),
dims: (width, height),
} => {
self.check_framebuffer_region((src_x, src_y), (width, height));
self.check_framebuffer_region((dest_x, dest_y), (width, height));
(self.0.blt)(
&mut self.0,
ptr::null_mut(),
GraphicsOutputBltOperation::BLT_VIDEO_TO_VIDEO,
src_x,
src_y,
dest_x,
dest_y,
width,
height,
0,
)
.to_result()
}
}
}
}
fn check_framebuffer_region(&self, coords: (usize, usize), dims: (usize, usize)) {
let (width, height) = self.current_mode_info().resolution();
assert!(
coords.0.saturating_add(dims.0) <= width,
"Horizontal framebuffer coordinate out of bounds"
);
assert!(
coords.1.saturating_add(dims.1) <= height,
"Vertical framebuffer coordinate out of bounds"
);
}
fn check_blt_buffer_region(&self, region: BltRegion, dims: (usize, usize), buf_length: usize) {
match region {
BltRegion::Full => assert!(
dims.1.saturating_mul(dims.0) <= buf_length,
"BltBuffer access out of bounds"
),
BltRegion::SubRectangle {
coords: (x, y),
px_stride,
} => {
assert!(
x.saturating_add(dims.0) <= px_stride,
"Horizontal BltBuffer coordinate out of bounds"
);
assert!(
y.saturating_add(dims.1).saturating_mul(px_stride) <= buf_length,
"Vertical BltBuffer coordinate out of bounds"
);
}
}
}
#[must_use]
pub const fn current_mode_info(&self) -> ModeInfo {
unsafe { *self.mode().info.cast_const().cast::<ModeInfo>() }
}
pub fn frame_buffer(&mut self) -> FrameBuffer<'_> {
assert!(
self.current_mode_info().pixel_format() != PixelFormat::BltOnly,
"Cannot access the framebuffer in a Blt-only mode"
);
let base = self.mode().frame_buffer_base as *mut u8;
let size = self.mode().frame_buffer_size;
FrameBuffer {
base,
size,
_lifetime: PhantomData,
}
}
const fn mode(&self) -> &GraphicsOutputProtocolMode {
unsafe { &*self.0.mode.cast_const() }
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[repr(u32)]
pub enum PixelFormat {
Rgb = 0,
Bgr,
Bitmask,
BltOnly,
}
#[derive(Copy, Clone, Debug)]
pub struct Mode {
index: u32,
info_sz: usize,
info: ModeInfo,
}
impl Mode {
#[must_use]
pub const fn info_size(&self) -> usize {
self.info_sz
}
#[must_use]
pub const fn info(&self) -> &ModeInfo {
&self.info
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[repr(transparent)]
pub struct ModeInfo(GraphicsOutputModeInformation);
impl ModeInfo {
#[must_use]
pub const fn resolution(&self) -> (usize, usize) {
(
usize_from_u32(self.0.horizontal_resolution),
usize_from_u32(self.0.vertical_resolution),
)
}
#[must_use]
pub const fn pixel_format(&self) -> PixelFormat {
match self.0.pixel_format.0 {
0 => PixelFormat::Rgb,
1 => PixelFormat::Bgr,
2 => PixelFormat::Bitmask,
3 => PixelFormat::BltOnly,
_ => panic!("invalid pixel format"),
}
}
#[must_use]
pub const fn pixel_bitmask(&self) -> Option<PixelBitmask> {
match self.pixel_format() {
PixelFormat::Bitmask => Some(self.0.pixel_information),
_ => None,
}
}
#[must_use]
pub const fn stride(&self) -> usize {
usize_from_u32(self.0.pixels_per_scan_line)
}
}
pub struct ModeIter<'gop> {
gop: &'gop GraphicsOutput,
current: u32,
max: u32,
}
impl Iterator for ModeIter<'_> {
type Item = Mode;
fn next(&mut self) -> Option<Self::Item> {
let index = self.current;
if index < self.max {
let m = self.gop.query_mode(index);
self.current += 1;
m.ok().or_else(|| self.next())
} else {
None
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let size = (self.max - self.current) as usize;
(size, Some(size))
}
}
impl Debug for ModeIter<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
f.debug_struct("ModeIter")
.field("current", &self.current)
.field("max", &self.max)
.finish()
}
}
impl ExactSizeIterator for ModeIter<'_> {}
#[allow(missing_docs)]
#[derive(Debug, Copy, Clone)]
#[repr(C)]
pub struct BltPixel {
pub blue: u8,
pub green: u8,
pub red: u8,
_reserved: u8,
}
impl BltPixel {
#[must_use]
pub const fn new(red: u8, green: u8, blue: u8) -> Self {
Self {
red,
green,
blue,
_reserved: 0,
}
}
}
impl From<u32> for BltPixel {
fn from(color: u32) -> Self {
Self {
blue: (color & 0x00_00_FF) as u8,
green: ((color & 0x00_FF_00) >> 8) as u8,
red: ((color & 0xFF_00_00) >> 16) as u8,
_reserved: 0,
}
}
}
#[derive(Clone, Copy, Debug)]
pub enum BltRegion {
Full,
SubRectangle {
coords: (usize, usize),
px_stride: usize,
},
}
#[derive(Debug)]
pub enum BltOp<'buf> {
VideoFill {
color: BltPixel,
dest: (usize, usize),
dims: (usize, usize),
},
VideoToBltBuffer {
buffer: &'buf mut [BltPixel],
src: (usize, usize),
dest: BltRegion,
dims: (usize, usize),
},
BufferToVideo {
buffer: &'buf [BltPixel],
src: BltRegion,
dest: (usize, usize),
dims: (usize, usize),
},
VideoToVideo {
src: (usize, usize),
dest: (usize, usize),
dims: (usize, usize),
},
}
#[derive(Debug)]
pub struct FrameBuffer<'gop> {
base: *mut u8,
size: usize,
_lifetime: PhantomData<&'gop mut u8>,
}
impl FrameBuffer<'_> {
pub const fn as_mut_ptr(&mut self) -> *mut u8 {
self.base
}
#[must_use]
pub const fn size(&self) -> usize {
self.size
}
#[inline]
pub unsafe fn write_byte(&mut self, index: usize, value: u8) {
debug_assert!(index < self.size, "Frame buffer accessed out of bounds");
unsafe { self.base.add(index).write_volatile(value) }
}
#[inline]
#[must_use]
pub unsafe fn read_byte(&self, index: usize) -> u8 {
debug_assert!(index < self.size, "Frame buffer accessed out of bounds");
unsafe { self.base.add(index).read_volatile() }
}
#[inline]
pub unsafe fn write_value<T>(&mut self, index: usize, value: T) {
debug_assert!(
index.saturating_add(size_of::<T>()) <= self.size,
"Frame buffer accessed out of bounds"
);
unsafe {
let ptr = self.base.add(index).cast::<T>();
ptr.write_volatile(value)
}
}
#[inline]
#[must_use]
pub unsafe fn read_value<T>(&self, index: usize) -> T {
debug_assert!(
index.saturating_add(size_of::<T>()) <= self.size,
"Frame buffer accessed out of bounds"
);
unsafe { (self.base.add(index) as *const T).read_volatile() }
}
}