Graphics

Working with the Graphics Output Protocol (GOP) for display output.

Graphics Output Protocol

GOP provides access to the display framebuffer:

flowchart LR
    subgraph app["UEFI Application"]
        CODE["Draw Code"]
    end

    subgraph gop["Graphics Output Protocol"]
        MODE["Mode Info"]
        FB["Framebuffer"]
    end

    subgraph display["Display"]
        SCREEN["Screen"]
    end

    CODE -->|"Blt()"| FB
    CODE -->|"Direct Write"| FB
    FB --> SCREEN

Finding GOP

use uefi::proto::console::gop::{GraphicsOutput, PixelFormat};

fn find_gop(bt: &BootServices) -> uefi::Result<&mut GraphicsOutput> {
    let gop = bt.locate_protocol::<GraphicsOutput>()?;

    let mode = gop.current_mode_info();
    log::info!("Current mode: {}x{}",
        mode.resolution().0,
        mode.resolution().1);

    Ok(gop)
}

Querying Display Modes

fn list_modes(gop: &GraphicsOutput) {
    log::info!("Available graphics modes:");

    for (i, mode) in gop.modes(bt).enumerate() {
        let info = mode.info();
        let (width, height) = info.resolution();
        let format = info.pixel_format();

        log::info!("  Mode {}: {}x{} {:?}",
            i, width, height, format);
    }
}

Setting Display Mode

fn set_mode(gop: &mut GraphicsOutput, bt: &BootServices, index: usize) -> uefi::Result {
    let modes: alloc::vec::Vec<_> = gop.modes(bt).collect();

    if index >= modes.len() {
        return Err(uefi::Status::INVALID_PARAMETER.into());
    }

    gop.set_mode(&modes[index])?;

    let info = gop.current_mode_info();
    log::info!("Set mode to {}x{}",
        info.resolution().0,
        info.resolution().1);

    Ok(())
}

fn find_best_mode(gop: &mut GraphicsOutput, bt: &BootServices) -> uefi::Result {
    // Find the highest resolution mode
    let mut best_mode = None;
    let mut best_pixels = 0u64;

    for mode in gop.modes(bt) {
        let info = mode.info();
        let (w, h) = info.resolution();
        let pixels = w as u64 * h as u64;

        if pixels > best_pixels {
            best_pixels = pixels;
            best_mode = Some(mode);
        }
    }

    if let Some(mode) = best_mode {
        gop.set_mode(&mode)?;
        log::info!("Selected best mode");
    }

    Ok(())
}

Pixel Formats

Format Description Byte Order
Rgb 32-bit RGB R, G, B, Reserved
Bgr 32-bit BGR B, G, R, Reserved
Bitmask Custom masks Defined by mode
BltOnly No direct access Use Blt only
fn get_pixel_info(gop: &GraphicsOutput) {
    let mode = gop.current_mode_info();

    match mode.pixel_format() {
        PixelFormat::Rgb => log::info!("Format: RGB (32-bit)"),
        PixelFormat::Bgr => log::info!("Format: BGR (32-bit)"),
        PixelFormat::Bitmask => {
            let mask = mode.pixel_bitmask().unwrap();
            log::info!("Format: Bitmask");
            log::info!("  Red:   {:#010x}", mask.red);
            log::info!("  Green: {:#010x}", mask.green);
            log::info!("  Blue:  {:#010x}", mask.blue);
        }
        PixelFormat::BltOnly => log::info!("Format: BltOnly (no direct access)"),
    }
}

Direct Framebuffer Access

use uefi::proto::console::gop::BltPixel;

fn direct_framebuffer_access(gop: &mut GraphicsOutput) -> uefi::Result {
    let mode = gop.current_mode_info();
    let (width, height) = mode.resolution();
    let stride = mode.stride();

    // Get framebuffer
    let mut fb = gop.frame_buffer();
    let fb_ptr = fb.as_mut_ptr() as *mut u32;

    // Clear screen to blue (assuming BGR format)
    let blue: u32 = 0x00FF0000; // BGR: Blue

    for y in 0..height {
        for x in 0..width {
            let offset = y * stride + x;
            unsafe {
                fb_ptr.add(offset).write_volatile(blue);
            }
        }
    }

    Ok(())
}

Drawing Primitives

Set Pixel

fn set_pixel(gop: &mut GraphicsOutput, x: usize, y: usize, color: u32) {
    let mode = gop.current_mode_info();
    let stride = mode.stride();

    let mut fb = gop.frame_buffer();
    let fb_ptr = fb.as_mut_ptr() as *mut u32;

    let offset = y * stride + x;
    unsafe {
        fb_ptr.add(offset).write_volatile(color);
    }
}

Draw Rectangle

fn draw_rect(
    gop: &mut GraphicsOutput,
    x: usize, y: usize,
    width: usize, height: usize,
    color: u32,
) {
    let mode = gop.current_mode_info();
    let stride = mode.stride();

    let mut fb = gop.frame_buffer();
    let fb_ptr = fb.as_mut_ptr() as *mut u32;

    for row in y..(y + height) {
        for col in x..(x + width) {
            let offset = row * stride + col;
            unsafe {
                fb_ptr.add(offset).write_volatile(color);
            }
        }
    }
}

Draw Line (Bresenham)

fn draw_line(
    gop: &mut GraphicsOutput,
    x0: i32, y0: i32,
    x1: i32, y1: i32,
    color: u32,
) {
    let dx = (x1 - x0).abs();
    let dy = -(y1 - y0).abs();
    let sx = if x0 < x1 { 1 } else { -1 };
    let sy = if y0 < y1 { 1 } else { -1 };
    let mut err = dx + dy;

    let mut x = x0;
    let mut y = y0;

    loop {
        set_pixel(gop, x as usize, y as usize, color);

        if x == x1 && y == y1 {
            break;
        }

        let e2 = 2 * err;
        if e2 >= dy {
            err += dy;
            x += sx;
        }
        if e2 <= dx {
            err += dx;
            y += sy;
        }
    }
}

Using Blt Operations

Blt (Block Transfer) operations are faster and work with all pixel formats:

Fill Rectangle with Blt

use uefi::proto::console::gop::{BltOp, BltPixel, BltRegion};

fn blt_fill_rect(
    gop: &mut GraphicsOutput,
    x: usize, y: usize,
    width: usize, height: usize,
    color: BltPixel,
) -> uefi::Result {
    gop.blt(BltOp::VideoFill {
        color,
        dest: (x, y),
        dims: (width, height),
    })
}

fn clear_screen(gop: &mut GraphicsOutput, color: BltPixel) -> uefi::Result {
    let mode = gop.current_mode_info();
    let (width, height) = mode.resolution();

    blt_fill_rect(gop, 0, 0, width, height, color)
}

Copy from Buffer to Screen

fn blt_buffer_to_video(
    gop: &mut GraphicsOutput,
    buffer: &[BltPixel],
    src_width: usize,
    dest_x: usize, dest_y: usize,
    width: usize, height: usize,
) -> uefi::Result {
    gop.blt(BltOp::BufferToVideo {
        buffer,
        src: BltRegion::SubRectangle {
            coords: (0, 0),
            px_stride: src_width,
        },
        dest: (dest_x, dest_y),
        dims: (width, height),
    })
}

Copy Screen Region

fn blt_copy_region(
    gop: &mut GraphicsOutput,
    src_x: usize, src_y: usize,
    dest_x: usize, dest_y: usize,
    width: usize, height: usize,
) -> uefi::Result {
    gop.blt(BltOp::VideoToVideo {
        src: (src_x, src_y),
        dest: (dest_x, dest_y),
        dims: (width, height),
    })
}

Double Buffering

struct DoubleBuffer {
    buffer: alloc::vec::Vec<BltPixel>,
    width: usize,
    height: usize,
}

impl DoubleBuffer {
    fn new(width: usize, height: usize) -> Self {
        let buffer = alloc::vec![BltPixel::new(0, 0, 0); width * height];
        DoubleBuffer { buffer, width, height }
    }

    fn clear(&mut self, color: BltPixel) {
        self.buffer.fill(color);
    }

    fn set_pixel(&mut self, x: usize, y: usize, color: BltPixel) {
        if x < self.width && y < self.height {
            self.buffer[y * self.width + x] = color;
        }
    }

    fn present(&self, gop: &mut GraphicsOutput) -> uefi::Result {
        gop.blt(BltOp::BufferToVideo {
            buffer: &self.buffer,
            src: BltRegion::Full,
            dest: (0, 0),
            dims: (self.width, self.height),
        })
    }
}

Color Helpers

fn rgb(r: u8, g: u8, b: u8) -> BltPixel {
    BltPixel::new(r, g, b)
}

// Common colors
const BLACK: BltPixel = BltPixel::new(0, 0, 0);
const WHITE: BltPixel = BltPixel::new(255, 255, 255);
const RED: BltPixel = BltPixel::new(255, 0, 0);
const GREEN: BltPixel = BltPixel::new(0, 255, 0);
const BLUE: BltPixel = BltPixel::new(0, 0, 255);
const YELLOW: BltPixel = BltPixel::new(255, 255, 0);
const CYAN: BltPixel = BltPixel::new(0, 255, 255);
const MAGENTA: BltPixel = BltPixel::new(255, 0, 255);

Complete Example

#![no_main]
#![no_std]

extern crate alloc;

use uefi::prelude::*;
use uefi::proto::console::gop::{GraphicsOutput, BltOp, BltPixel};

#[entry]
fn main(_image: Handle, st: SystemTable<Boot>) -> Status {
    uefi::helpers::init().unwrap();

    let bt = st.boot_services();

    // Find GOP
    let gop = match bt.locate_protocol::<GraphicsOutput>() {
        Ok(g) => g,
        Err(_) => {
            log::error!("No graphics output found");
            return Status::NOT_FOUND;
        }
    };

    // Get current mode info
    let mode = gop.current_mode_info();
    let (width, height) = mode.resolution();
    log::info!("Resolution: {}x{}", width, height);

    // Clear screen to dark blue
    let dark_blue = BltPixel::new(0, 0, 64);
    gop.blt(BltOp::VideoFill {
        color: dark_blue,
        dest: (0, 0),
        dims: (width, height),
    }).unwrap();

    // Draw some colored rectangles
    let colors = [
        BltPixel::new(255, 0, 0),    // Red
        BltPixel::new(0, 255, 0),    // Green
        BltPixel::new(0, 0, 255),    // Blue
        BltPixel::new(255, 255, 0),  // Yellow
    ];

    let rect_width = width / 6;
    let rect_height = height / 4;
    let margin = 20;

    for (i, color) in colors.iter().enumerate() {
        let x = margin + i * (rect_width + margin);
        let y = height / 2 - rect_height / 2;

        gop.blt(BltOp::VideoFill {
            color: *color,
            dest: (x, y),
            dims: (rect_width, rect_height),
        }).unwrap();
    }

    // Draw a white border
    let white = BltPixel::new(255, 255, 255);
    let border = 5;

    // Top border
    gop.blt(BltOp::VideoFill {
        color: white,
        dest: (0, 0),
        dims: (width, border),
    }).unwrap();

    // Bottom border
    gop.blt(BltOp::VideoFill {
        color: white,
        dest: (0, height - border),
        dims: (width, border),
    }).unwrap();

    // Left border
    gop.blt(BltOp::VideoFill {
        color: white,
        dest: (0, 0),
        dims: (border, height),
    }).unwrap();

    // Right border
    gop.blt(BltOp::VideoFill {
        color: white,
        dest: (width - border, 0),
        dims: (border, height),
    }).unwrap();

    log::info!("Graphics demo complete!");

    // Wait for key press
    st.stdin().reset(false).unwrap();
    bt.wait_for_event(&mut [st.stdin().wait_for_key_event().unwrap()]).unwrap();

    Status::SUCCESS
}

Summary

Operation Method
Find GOP locate_protocol::<GraphicsOutput>()
Query modes gop.modes()
Set mode gop.set_mode()
Get framebuffer gop.frame_buffer()
Fill rectangle gop.blt(VideoFill)
Draw buffer gop.blt(BufferToVideo)
Copy region gop.blt(VideoToVideo)

See Also

Next Steps

Learn about UEFI Variables for persistent storage.


Back to top

Rust Programming Guide is not affiliated with the Rust Foundation. Content is provided for educational purposes.

This site uses Just the Docs, a documentation theme for Jekyll.