//! Graphics output protocol. //! //! The UEFI GOP is meant to replace existing [VGA][vga] hardware interfaces. //! //! The GOP provides access to a hardware frame buffer and allows UEFI apps //! to draw directly to the graphics output device. //! //! The advantage of the GOP over legacy VGA is that it allows multiple GPUs //! to exist and be used on the system. There is a GOP implementation for every //! unique GPU in the system which supports UEFI. //! //! [vga]: https://en.wikipedia.org/wiki/Video_Graphics_Array //! //! # Definitions //! //! All graphics operations use a coordinate system where the top-left of the screen //! is mapped to the point (0, 0), and `y` increases going down. //! //! Rectangles are defined by their top-left corner, and their width and height. //! //! The stride is understood as the length in bytes of a scan line / row of a buffer. //! In theory, a buffer with a width of 640 should have (640 * 4) bytes per row, //! but in practice there might be some extra padding used for efficiency. //! //! Frame buffers represent the graphics card's image buffers, backing the displays. //! //! Blits (**bl**ock **t**ransfer) can do high-speed memory copy between //! the frame buffer and itself, or to and from some other buffers. //! //! # Blitting //! //! On certain hardware, the frame buffer is in a opaque format, //! or cannot be accessed by the CPU. In those cases, it is not possible //! to draw directly to the frame buffer. You must draw to another buffer //! with a known pixel format, and then submit a blit command to copy that buffer //! into the back buffer. //! //! Blitting can also copy a rectangle from the frame buffer to //! another rectangle in the frame buffer, or move data out of the frame buffer //! into a CPU-visible buffer. It can also do very fast color fills. //! //! The source and destination rectangles must always be of the same size: //! no stretching / squashing will be done. //! //! # Animations //! //! UEFI does not mention if double buffering is used, nor how often //! the frame buffer gets sent to the screen, but it's safe to assume that //! the graphics card will re-draw the buffer at around the monitor's refresh rate. //! You will have to implement your own double buffering if you want to //! avoid tearing with animations. use crate::proto::unsafe_protocol; use crate::util::usize_from_u32; use crate::{boot, Result, StatusExt}; use core::fmt::{Debug, Formatter}; use core::marker::PhantomData; use core::mem; use core::ptr::{self, NonNull}; use uefi_raw::protocol::console::{ GraphicsOutputBltOperation, GraphicsOutputModeInformation, GraphicsOutputProtocol, GraphicsOutputProtocolMode, }; pub use uefi_raw::protocol::console::PixelBitmask; /// Provides access to the video hardware's frame buffer. /// /// The GOP can be used to set the properties of the frame buffer, /// and also allows the app to access the in-memory buffer. #[derive(Debug)] #[repr(transparent)] #[unsafe_protocol(GraphicsOutputProtocol::GUID)] pub struct GraphicsOutput(GraphicsOutputProtocol); impl GraphicsOutput { /// Returns information for an available graphics mode that the graphics /// device and the set of active video output devices supports. fn query_mode(&self, index: u32) -> Result { let mut info_sz = 0; let mut info_heap_ptr = ptr::null(); // query_mode allocates a buffer and stores the heap ptr in the provided // variable. In this buffer, the queried data can be found. unsafe { (self.0.query_mode)(&self.0, index, &mut info_sz, &mut info_heap_ptr) } .to_result_with_val(|| { // Transform to owned info on the stack. let info = unsafe { *info_heap_ptr }; let info_heap_ptr = info_heap_ptr.cast::().cast_mut(); // User has no benefit from propagating this error. If this // fails, it is an error of the UEFI implementation. unsafe { boot::free_pool(NonNull::new(info_heap_ptr).unwrap()) } .expect("buffer should be deallocatable"); Mode { index, info_sz, info: ModeInfo(info), } }) } /// Returns a [`ModeIter`]. #[must_use] pub const fn modes(&self) -> ModeIter { ModeIter { gop: self, current: 0, max: self.mode().max_mode, } } /// Sets the video device into the specified mode, clearing visible portions /// of the output display to black. /// /// This function will invalidate the current framebuffer. pub fn set_mode(&mut self, mode: &Mode) -> Result { unsafe { (self.0.set_mode)(&mut self.0, mode.index) }.to_result() } /// Performs a blt (block transfer) operation on the frame buffer. /// /// Every operation requires different parameters. pub fn blt(&mut self, op: BltOp) -> Result { // Demultiplex the operation type. 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, &color as *const _ 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 * core::mem::size_of::(), ) .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() as *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() as *mut _, GraphicsOutputBltOperation::BLT_BUFFER_TO_VIDEO, src_x, src_y, dest_x, dest_y, width, height, px_stride * core::mem::size_of::(), ) .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() } } } } /// Memory-safety check for accessing a region of the framebuffer 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" ); } /// Memory-safety check for accessing a region of a user-provided buffer 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" ); } } } /// Returns the frame buffer information for the current mode. #[must_use] pub const fn current_mode_info(&self) -> ModeInfo { unsafe { *self.mode().info.cast_const().cast::() } } /// Access the frame buffer directly 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() } } } /// Represents the format of the pixels in a frame buffer. #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[repr(u32)] pub enum PixelFormat { /// Each pixel is 32-bit long, with 24-bit RGB, and the last byte is reserved. Rgb = 0, /// Each pixel is 32-bit long, with 24-bit BGR, and the last byte is reserved. Bgr, /// Custom pixel format, check the associated bitmask. Bitmask, /// The graphics mode does not support drawing directly to the frame buffer. /// /// This means you will have to use the `blt` function which will /// convert the graphics data to the device's internal pixel format. BltOnly, // SAFETY: UEFI also defines a PixelFormatMax variant, and states that all // valid enum values are guaranteed to be smaller. Since that is the // case, adding a new enum variant would be a breaking change, so it // is safe to model this C enum as a Rust enum. } /// Represents a graphics mode compatible with a given graphics device. #[derive(Copy, Clone, Debug)] pub struct Mode { index: u32, info_sz: usize, info: ModeInfo, } impl Mode { /// The size of the info structure in bytes. /// /// Newer versions of the spec might add extra information, in a backwards compatible way. #[must_use] pub const fn info_size(&self) -> usize { self.info_sz } /// Returns a reference to the mode info structure. #[must_use] pub const fn info(&self) -> &ModeInfo { &self.info } } /// Information about a graphics output mode. #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[repr(transparent)] pub struct ModeInfo(GraphicsOutputModeInformation); impl ModeInfo { /// Returns the (horizontal, vertical) resolution. /// /// On desktop monitors, this usually means (width, height). #[must_use] pub const fn resolution(&self) -> (usize, usize) { ( usize_from_u32(self.0.horizontal_resolution), usize_from_u32(self.0.vertical_resolution), ) } /// Returns the format of the frame buffer. #[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"), } } /// Returns the bitmask of the custom pixel format, if available. #[must_use] pub const fn pixel_bitmask(&self) -> Option { match self.pixel_format() { PixelFormat::Bitmask => Some(self.0.pixel_information), _ => None, } } /// Returns the number of pixels per scanline. /// /// Due to performance reasons, the stride might not be equal to the width, /// instead the stride might be bigger for better alignment. #[must_use] pub const fn stride(&self) -> usize { usize_from_u32(self.0.pixels_per_scan_line) } } /// Iterator for [`Mode`]s of the [`GraphicsOutput`] protocol. pub struct ModeIter<'gop> { gop: &'gop GraphicsOutput, current: u32, max: u32, } impl<'gop> Iterator for ModeIter<'gop> { type Item = Mode; fn next(&mut self) -> Option { 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) { let size = (self.max - self.current) as usize; (size, Some(size)) } } impl<'gop> Debug for ModeIter<'gop> { 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<'_> {} /// Format of pixel data used for blitting. /// /// This is a BGR 24-bit format with an 8-bit padding, to keep each pixel 32-bit in size. #[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 { /// Create a new pixel from RGB values. #[must_use] pub const fn new(red: u8, green: u8, blue: u8) -> Self { Self { red, green, blue, _reserved: 0, } } } impl From 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, } } } /// Region of the `BltBuffer` which we are operating on /// /// Some `Blt` operations can operate on either the full `BltBuffer` or a /// sub-rectangle of it, but require the stride to be known in the latter case. #[derive(Clone, Copy, Debug)] pub enum BltRegion { /// Operate on the full `BltBuffer` Full, /// Operate on a sub-rectangle of the `BltBuffer` SubRectangle { /// Coordinate of the rectangle in the `BltBuffer` coords: (usize, usize), /// Stride (length of each row of the `BltBuffer`) in **pixels** px_stride: usize, }, } /// Blit operation to perform. #[derive(Debug)] pub enum BltOp<'buf> { /// Fills a rectangle of video display with a pixel color. VideoFill { /// The color to fill with. color: BltPixel, /// The X / Y coordinates of the destination rectangle. dest: (usize, usize), /// The width / height of the rectangle. dims: (usize, usize), }, /// Reads data from the video display to the buffer. VideoToBltBuffer { /// Buffer into which to copy data. buffer: &'buf mut [BltPixel], /// Coordinates of the source rectangle, in the frame buffer. src: (usize, usize), /// Location of the destination rectangle in the user-provided buffer dest: BltRegion, /// Width / height of the rectangles. dims: (usize, usize), }, /// Write data from the buffer to the video rectangle. /// Delta must be the stride (count of bytes in a row) of the buffer. BufferToVideo { /// Buffer from which to copy data. buffer: &'buf [BltPixel], /// Location of the source rectangle in the user-provided buffer. src: BltRegion, /// Coordinates of the destination rectangle, in the frame buffer. dest: (usize, usize), /// Width / height of the rectangles. dims: (usize, usize), }, /// Copy from the source rectangle in video memory to /// the destination rectangle, also in video memory. VideoToVideo { /// Coordinates of the source rectangle, in the frame buffer. src: (usize, usize), /// Coordinates of the destination rectangle, also in the frame buffer. dest: (usize, usize), /// Width / height of the rectangles. dims: (usize, usize), }, } /// Direct access to a memory-mapped frame buffer #[derive(Debug)] pub struct FrameBuffer<'gop> { base: *mut u8, size: usize, _lifetime: PhantomData<&'gop mut u8>, } impl<'gop> FrameBuffer<'gop> { /// Access the raw framebuffer pointer /// /// To use this pointer safely and correctly, you must... /// - Honor the pixel format and stride specified by the mode info /// - Keep memory accesses in bound /// - Use volatile reads and writes /// - Make sure that the pointer does not outlive the FrameBuffer /// /// On some implementations this framebuffer pointer can be used after /// exiting boot services, but that is not guaranteed by the UEFI Specification. pub fn as_mut_ptr(&mut self) -> *mut u8 { self.base } /// Query the framebuffer size in bytes #[must_use] pub const fn size(&self) -> usize { self.size } /// Modify the i-th byte of the frame buffer /// /// # Safety /// /// This operation is unsafe because... /// - You must honor the pixel format and stride specified by the mode info /// - There is no bound checking on memory accesses in release mode #[inline] pub unsafe fn write_byte(&mut self, index: usize, value: u8) { debug_assert!(index < self.size, "Frame buffer accessed out of bounds"); self.base.add(index).write_volatile(value) } /// Read the i-th byte of the frame buffer /// /// # Safety /// /// This operation is unsafe because... /// - You must honor the pixel format and stride specified by the mode info /// - There is no bound checking on memory accesses in release mode #[inline] #[must_use] pub unsafe fn read_byte(&self, index: usize) -> u8 { debug_assert!(index < self.size, "Frame buffer accessed out of bounds"); self.base.add(index).read_volatile() } /// Write a value in the frame buffer, starting at the i-th byte /// /// We only recommend using this method with [u8; N] arrays. Once Rust has /// const generics, it will be deprecated and replaced with a write_bytes() /// method that only accepts [u8; N] input. /// /// # Safety /// /// This operation is unsafe because... /// - It is your responsibility to make sure that the value type makes sense /// - You must honor the pixel format and stride specified by the mode info /// - There is no bound checking on memory accesses in release mode #[inline] pub unsafe fn write_value(&mut self, index: usize, value: T) { debug_assert!( index.saturating_add(mem::size_of::()) <= self.size, "Frame buffer accessed out of bounds" ); let ptr = self.base.add(index).cast::(); ptr.write_volatile(value) } /// Read a value from the frame buffer, starting at the i-th byte /// /// We only recommend using this method with [u8; N] arrays. Once Rust has /// const generics, it will be deprecated and replaced with a read_bytes() /// method that only accepts [u8; N] input. /// /// # Safety /// /// This operation is unsafe because... /// - It is your responsibility to make sure that the value type makes sense /// - You must honor the pixel format and stride specified by the mode info /// - There is no bound checking on memory accesses in release mode #[inline] #[must_use] pub unsafe fn read_value(&self, index: usize) -> T { debug_assert!( index.saturating_add(mem::size_of::()) <= self.size, "Frame buffer accessed out of bounds" ); (self.base.add(index) as *const T).read_volatile() } }