From dffee5652c90ddfd48c028eda54df7ffd450a87c Mon Sep 17 00:00:00 2001 From: James Martin Date: Thu, 16 Jul 2020 13:30:42 -0700 Subject: [PATCH] Got a working graphical display, text terminal, and tty! --- run.sh | 2 +- src/allocator.rs | 10 +-- src/graphics.rs | 105 ++++++++----------------------- src/graphics/color.rs | 29 +++++++++ src/graphics/display.rs | 56 +++++++++++++++++ src/graphics/display/gop.rs | 65 +++++++++++++++++++ src/graphics/font.rs | 19 +----- src/graphics/font/psf.rs | 1 - src/graphics/terminal.rs | 12 ++++ src/graphics/terminal/display.rs | 78 +++++++++++++++++++++++ src/graphics/tty.rs | 8 +++ src/graphics/tty/terminal.rs | 77 +++++++++++++++++++++++ src/main.rs | 1 - 13 files changed, 360 insertions(+), 103 deletions(-) create mode 100644 src/graphics/color.rs create mode 100644 src/graphics/display.rs create mode 100644 src/graphics/display/gop.rs create mode 100644 src/graphics/terminal.rs create mode 100644 src/graphics/terminal/display.rs create mode 100644 src/graphics/tty.rs create mode 100644 src/graphics/tty/terminal.rs diff --git a/run.sh b/run.sh index 9e02123..a1f9077 100755 --- a/run.sh +++ b/run.sh @@ -2,4 +2,4 @@ profile=${1:-"debug"} mkdir -p drive/EFI/Boot cp "target/x86_64-unknown-uefi/$profile/bootproof.efi" drive/EFI/Boot/BootX64.efi -qemu-system-x86_64 -nodefaults -cpu host -smp 8 -m 512M -machine "q35,accel=kvm:tcg" -drive "if=pflash,format=raw,file=/usr/share/OVMF/OVMF_CODE.fd,readonly=on" -drive "if=pflash,format=raw,file=/usr/share/OVMF/OVMF_VARS.fd,readonly=on" -drive "format=raw,file=fat:rw:drive" -display gtk,gl=on -vga virtio -serial stdio +qemu-system-x86_64 -nodefaults -cpu host -smp 8 -m 1G -machine "q35,accel=kvm:tcg" -drive "if=pflash,format=raw,file=/usr/share/OVMF/OVMF_CODE.fd,readonly=on" -drive "if=pflash,format=raw,file=/usr/share/OVMF/OVMF_VARS.fd,readonly=on" -drive "format=raw,file=fat:rw:drive" -display gtk,gl=on -vga virtio -serial stdio diff --git a/src/allocator.rs b/src/allocator.rs index 0d9b196..e5685d4 100644 --- a/src/allocator.rs +++ b/src/allocator.rs @@ -1,6 +1,6 @@ use alloc::alloc::GlobalAlloc; use core::alloc::Layout; -use uefi::table::boot::{AllocateType, MemoryDescriptor, MemoryType}; +use uefi::table::boot::{AllocateType, MemoryType}; pub enum Allocator { None, @@ -11,10 +11,9 @@ unsafe impl GlobalAlloc for Allocator { unsafe fn alloc(&self, layout: Layout) -> *mut u8 { match self { Allocator::Uefi(st) => { - crate::log!("Allocate {:?}", layout); st.boot_services().allocate_pages(AllocateType::AnyPages, MemoryType::LOADER_DATA, layout.size()) .expect("Failed to allocate memory!") - .expect("Failed to allocate memory! 2") + .unwrap() as *mut u8 }, Allocator::None => panic!("No allocator available!") @@ -24,8 +23,9 @@ unsafe impl GlobalAlloc for Allocator { unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { match self { Allocator::Uefi(st) => { - crate::log!("Free {:?}", layout); - st.boot_services().free_pages(ptr as u64, layout.size()); + st.boot_services().free_pages(ptr as u64, layout.size()) + .expect("Failed to free memory!") + .unwrap(); }, Allocator::None => { panic!("No allocator available!"); diff --git a/src/graphics.rs b/src/graphics.rs index 10b165b..25a1316 100644 --- a/src/graphics.rs +++ b/src/graphics.rs @@ -1,85 +1,34 @@ -mod font; +pub mod color; +pub mod display; +pub mod font; +pub mod terminal; +pub mod tty; -use crate::println; -use uefi::proto::console::gop::*; - -fn px_index(stride: usize, x: usize, y: usize) -> usize { - 4 * (y * stride + x) -} - -fn make_px(r: u8, g: u8, b: u8) -> [u8; 4] { - [b, g, r, 0] -} - -unsafe fn draw_char(fb: &mut FrameBuffer, stride: usize, cx: usize, cy: usize, c: char) { - let font = font::font(); - let glyph = font.lookup(c).expect("Character missing from font."); - let color = make_px(255, 255, 255); - for dx in 0..num_integer::div_ceil(glyph.width(), 8) * 8 { - for dy in 0..glyph.height() { - if glyph.get(dx, dy) { - let scale = 2; - for sdx in 0..scale { - for sdy in 0..scale { - let px = cx + scale * dx as usize + sdx; - let py = cy + scale * dy as usize + sdy; - fb.write_value(px_index(stride, px, py), color); - } - } - } - } - } -} - -unsafe fn draw_str(fb: &mut FrameBuffer, stride: usize, mut cx: usize, cy: usize, text: &str) { - let width = font::font().width as usize * 2; - for c in text.chars() { - draw_char(fb, stride, cx, cy, c); - cx += width; - } -} - -fn draw(fb: &mut FrameBuffer, stride: usize, width: usize, height: usize) { - for x in 0..width { - for y in 0..height { - let i = px_index(stride, x, y); - let r = (x * 256 / width) as u8; - let g = (y * 256 / height) as u8; - let b = 255 - ((r as u16 + g as u16) / 2) as u8; - let px = make_px(r, g, b); - unsafe { - fb.write_value(i, px); - } - } - } - - let c_width = width / 8; - let c_height = height / 16; - unsafe { - draw_str(fb, stride, 8, 8, "✔ Hello, world! ♡"); - } -} +use crate::graphics::color::{COLOR_BLACK, COLOR_WHITE}; +use crate::graphics::display::gop::GopDisplay; +use crate::graphics::font::font; +use crate::graphics::terminal::display::DisplayTerminal; +use crate::graphics::tty::Tty; +use crate::graphics::tty::terminal::TerminalTty; pub fn do_graphics(st: &uefi::prelude::SystemTable) { - let gop = st.boot_services().locate_protocol::() - .unwrap() - .expect("UEFI Graphics Output Protocol (GOP) is not present."); - let mut gop = unsafe { &mut *gop.get() }; - let mut mode = None; - for gop_mode in gop.modes() { - let gop_mode = gop_mode.expect("Warning while accessing GOP mode."); - if let PixelFormat::BGR = gop_mode.info().pixel_format() { - mode = Some(gop_mode); - } else { - println!("Ignoring non-BGR pixel format."); + let display = GopDisplay::init(st.boot_services()); + let terminal = DisplayTerminal::create(display, font(), COLOR_BLACK, COLOR_WHITE); + let mut tty = TerminalTty::create(terminal); + + for _ in 0..30 { + for c in 'a'..'z' { + tty.putc(c); + tty.putc('\n'); } } - let mode = mode.expect("No usable pixel formats found."); - let (width, height) = mode.info().resolution(); - let stride = mode.info().stride(); - println!("Using mode: {}x{} {:?}", width, height, mode.info().pixel_format()); - gop.set_mode(&mode).unwrap().expect("Failed to set UEFI Graphics Output mode."); - let mut fb = gop.frame_buffer(); - draw(&mut fb, stride, width, height); + for _ in 0..20 { + for c in 'a'..'z' { + tty.putc(c); + } + } + tty.putc('\n'); + tty.puts("✔ Hello, world! ♡"); + tty.flush(); } diff --git a/src/graphics/color.rs b/src/graphics/color.rs new file mode 100644 index 0000000..a0db86e --- /dev/null +++ b/src/graphics/color.rs @@ -0,0 +1,29 @@ +#[derive(Copy, Clone)] +pub struct RGB { + r: u8, + g: u8, + b: u8 +} + +pub trait Color: Copy { + fn r(&self) -> u8; + fn g(&self) -> u8; + fn b(&self) -> u8; + + fn into_rgb(&self) -> RGB { + RGB { + r: self.r(), + g: self.g(), + b: self.b() + } + } +} + +impl Color for RGB { + fn r(&self) -> u8 { self.r } + fn g(&self) -> u8 { self.g } + fn b(&self) -> u8 { self.b } +} + +pub const COLOR_BLACK: RGB = RGB { r: 0x23, g: 0x23, b: 0x23 }; +pub const COLOR_WHITE: RGB = RGB { r: 0xFF, g: 0xFF, b: 0xFF }; diff --git a/src/graphics/display.rs b/src/graphics/display.rs new file mode 100644 index 0000000..5517699 --- /dev/null +++ b/src/graphics/display.rs @@ -0,0 +1,56 @@ +use crate::graphics::color::Color; +use crate::graphics::font::psf::PSFGlyph; + +pub mod gop; + +pub trait Display { + fn resolution(&self) -> (usize, usize); + fn width(&self) -> usize { self.resolution().0 } + fn height(&self) -> usize { self.resolution().1 } + + unsafe fn set_pixel(&mut self, color: impl Color, x: usize, y: usize); + fn set_pixel_ignore_oob(&mut self, color: impl Color, x: usize, y: usize) { + if x > self.width() || y > self.height() { + return; + } + + unsafe { + self.set_pixel(color, x, y); + } + } + + fn clear(&mut self, color: impl Color); + + unsafe fn draw_glyph(&mut self, color: impl Color, x: usize, y: usize, glyph: PSFGlyph) { + // Glyphs may actually be larger than their nominal bounding box. + // In fact, the Cozette font is like this: the heart symbol is 7 pixels wide, + // despite nominally being a 6x13 font. + // However, despite not being an intended use of the format, that extra pixel + // can still be stored in the padding bits of the glyph (and is!). + // Therefore, we just continue writing those extra bits if they are present. + // Note that there is no similar trick for the height, + // because the height doesn't have padding. + for glyph_x in 0..glyph.width() { + for glyph_y in 0..glyph.height() { + if glyph.get(glyph_x, glyph_y) { + self.set_pixel(color, x + glyph_x as usize, y + glyph_y as usize); + } + } + } + + // Sometimes, a font may actually have pixels outside its bounding box! + // For example, in Cozette, a 6x13 font, ♡ is actually 7 pixels wide. + // This data is still stored in the padding bits of the glyph. + // Note that there is no similar trick for height because height doesn't have padding. + // Futhermore, this only works on fonts whose width is not a multiple of eight. + for glyph_x in glyph.width()..num_integer::div_ceil(glyph.width(), 8) * 8 { + for glyph_y in 0..glyph.height() { + if glyph.get(glyph_x, glyph_y) { + // These pixels *nominally* aren't supposed to be there, + // so we only force the pixels inside the bounding box. + self.set_pixel_ignore_oob(color, x + glyph_x as usize, y + glyph_y as usize); + } + } + } + } +} diff --git a/src/graphics/display/gop.rs b/src/graphics/display/gop.rs new file mode 100644 index 0000000..c8246ac --- /dev/null +++ b/src/graphics/display/gop.rs @@ -0,0 +1,65 @@ +use crate::graphics::color::Color; +use crate::graphics::display::Display; +use uefi::proto::console::gop::{FrameBuffer, GraphicsOutput, ModeInfo, PixelFormat}; + +const PIXEL_WIDTH_BYTES: usize = 4; + +pub struct GopDisplay<'a> { + fb: FrameBuffer<'a>, + mode: ModeInfo, +} + +impl GopDisplay<'_> { + pub fn init<'boot>(bs: &'boot uefi::table::boot::BootServices) -> GopDisplay<'boot> { + let gop = bs.locate_protocol::() + .expect("UEFI Graphics Output Protocol (GOP) is not present.") + .unwrap(); + let gop = unsafe { &mut *gop.get() }; + let mut mode = None; + for gop_mode in gop.modes() { + let gop_mode = gop_mode.unwrap(); + if let PixelFormat::BGR = gop_mode.info().pixel_format() { + mode = Some(gop_mode); + } + } + let mode = mode.expect("No usable pixel formats found."); + let (width, height) = mode.info().resolution(); + crate::log!("Using mode: {}x{} {:?}", width, height, mode.info().pixel_format()); + gop.set_mode(&mode).expect("Failed to set UEFI Graphics Output mode.").unwrap(); + + let info = gop.current_mode_info(); + GopDisplay { + fb: gop.frame_buffer(), + mode: info, + } + } + + // Convert a color to a BGR-formatted byte array. + fn make_pixel(&self, color: impl Color) -> [u8; 4] { + [color.b(), color.g(), color.r(), 0] + } + + fn pixel_index(&self, x: usize, y: usize) -> usize { + PIXEL_WIDTH_BYTES * (self.mode.stride() * y + x) + } +} + +impl Display for GopDisplay<'_> { + fn resolution(&self) -> (usize, usize) { self.mode.resolution() } + + unsafe fn set_pixel(&mut self, color: impl Color, x: usize, y: usize) { + self.fb.write_value(self.pixel_index(x, y), self.make_pixel(color)); + } + + fn clear(&mut self, color: impl Color) { + let (width, height) = self.resolution(); + let px = self.make_pixel(color); + for x in 0..width { + for y in 0..height { + unsafe { + self.fb.write_value(self.pixel_index(x, y), px); + } + } + } + } +} diff --git a/src/graphics/font.rs b/src/graphics/font.rs index 220949b..c3124ec 100644 --- a/src/graphics/font.rs +++ b/src/graphics/font.rs @@ -2,7 +2,7 @@ use alloc::sync::Arc; use alloc::vec::Vec; use psf::*; -mod psf; +pub mod psf; static mut FONT: Option> = None; @@ -16,20 +16,6 @@ pub fn font() -> Arc { } } -fn pad(slice: &[u8]) -> [u8; 4] { - if slice.len() == 1 { - [slice[0], 0, 0, 0] - } else if slice.len() == 2 { - [slice[0], slice[1], 0, 0] - } else if slice.len() == 3 { - [slice[0], slice[1], slice[2], 0] - } else if slice.len() == 4 { - [slice[0], slice[1], slice[2], slice[3]] - } else { - crate::panic!("Bad character length {}", slice.len()) - } -} - fn parse_font() -> PSF { use core::convert::TryInto; let font = core::include_bytes!("font/cozette.psf"); @@ -37,14 +23,13 @@ fn parse_font() -> PSF { let charsize = u32::from_le_bytes(font[20..24].try_into().unwrap()); let height = u32::from_le_bytes(font[24..28].try_into().unwrap()); let width = u32::from_le_bytes(font[28..32].try_into().unwrap()); - crate::log!("{} {} {}", width, height, charsize); let glyphs_size = (length * charsize) as usize; let mut glyphs = Vec::with_capacity(glyphs_size); glyphs.extend_from_slice(&font[32..glyphs_size + 32]); let mut unicode_map = Vec::new(); - let mut unicode_info = &font[glyphs_size + 32..]; + let unicode_info = &font[glyphs_size + 32..]; let mut glyph = 0; let mut i = 0; while i < unicode_info.len() { diff --git a/src/graphics/font/psf.rs b/src/graphics/font/psf.rs index 4a4de17..c177768 100644 --- a/src/graphics/font/psf.rs +++ b/src/graphics/font/psf.rs @@ -51,7 +51,6 @@ impl PSFGlyph<'_> { pub fn get(&self, x: u32, y: u32) -> bool { let line_size = num_integer::div_ceil(self.width, 8); - let char_size = line_size * self.height; let (line_byte_index, bit_index) = num_integer::div_rem(x, 8); let mask = 0b10000000 >> bit_index; let byte = self.bitmap[(y * line_size + line_byte_index) as usize]; diff --git a/src/graphics/terminal.rs b/src/graphics/terminal.rs new file mode 100644 index 0000000..b06d963 --- /dev/null +++ b/src/graphics/terminal.rs @@ -0,0 +1,12 @@ +pub mod display; + +pub trait Terminal { + fn resolution(&self) -> (usize, usize); + fn width(&self) -> usize { self.resolution().0 } + fn height(&self) -> usize { self.resolution().1 } + + fn set_char(&mut self, x: usize, y: usize, c: char); + fn clear(&mut self); + + fn refresh(&mut self); +} diff --git a/src/graphics/terminal/display.rs b/src/graphics/terminal/display.rs new file mode 100644 index 0000000..5ecd982 --- /dev/null +++ b/src/graphics/terminal/display.rs @@ -0,0 +1,78 @@ +use alloc::sync::Arc; +use alloc::vec::Vec; +use crate::graphics::color::{Color, RGB}; +use crate::graphics::display::Display; +use crate::graphics::display::gop::GopDisplay; +use crate::graphics::font::psf::PSF; +use crate::graphics::terminal::Terminal; + +pub struct DisplayTerminal<'a> { + dp: GopDisplay<'a>, + font: Arc, + buf: Vec, + bg: RGB, + fg: RGB, +} + +impl DisplayTerminal<'_> { + pub fn create<'a>(dp: GopDisplay<'a>, font: Arc, bg: impl Color, fg: impl Color) -> DisplayTerminal<'a> { + let (dp_width, dp_height) = dp.resolution(); + let (font_width, font_height) = (font.width, font.height); + DisplayTerminal { + dp: dp, + font: font, + buf: { + let char_count = (dp_width / font_width as usize) * (dp_height / font_height as usize); + let mut buf = Vec::with_capacity(char_count); + for _ in 0..char_count { + buf.push(' '); + } + buf + }, + bg: bg.into_rgb(), + fg: fg.into_rgb(), + } + } + + fn get_index(&self, x: usize, y: usize) -> usize { + self.width() * y + x + } + + fn get_char(&self, x: usize, y: usize) -> char { + let i = self.get_index(x, y); + self.buf[i] + } +} + +impl Terminal for DisplayTerminal<'_> { + fn resolution(&self) -> (usize, usize) { + let width = self.dp.width() / self.font.width as usize; + let height = self.dp.height() / self.font.height as usize; + (width, height) + } + + fn set_char(&mut self, x: usize, y: usize, c: char) { + let i = self.get_index(x, y); + self.buf.as_mut_slice()[i] = c; + } + + fn clear(&mut self) { + for x in 0..self.width() { + for y in 0..self.height() { + self.set_char(x, y, ' '); + } + } + } + + fn refresh(&mut self) { + self.dp.clear(self.bg); + for x in 0..self.width() { + for y in 0..self.height() { + let glyph = self.font.lookup(self.get_char(x, y)).expect("Character missing from font."); + unsafe { + self.dp.draw_glyph(self.fg, self.font.width as usize * x, self.font.height as usize * y, glyph); + } + } + } + } +} diff --git a/src/graphics/tty.rs b/src/graphics/tty.rs new file mode 100644 index 0000000..8862731 --- /dev/null +++ b/src/graphics/tty.rs @@ -0,0 +1,8 @@ +pub mod terminal; + +pub trait Tty { + fn putc(&mut self, c: char); + fn puts(&mut self, s: &str); + fn clear(&mut self); + fn flush(&mut self); +} diff --git a/src/graphics/tty/terminal.rs b/src/graphics/tty/terminal.rs new file mode 100644 index 0000000..37ce9fb --- /dev/null +++ b/src/graphics/tty/terminal.rs @@ -0,0 +1,77 @@ +use alloc::string::{String, ToString}; +use alloc::vec::Vec; +use crate::graphics::terminal::Terminal; +use crate::graphics::terminal::display::DisplayTerminal; +use crate::graphics::tty::Tty; + +pub struct TerminalTty<'a> { + term: DisplayTerminal<'a>, + lines: Vec, +} + +impl TerminalTty<'_> { + pub fn create<'a>(term: DisplayTerminal<'a>) -> TerminalTty<'a> { + TerminalTty { + term: term, + lines: { + let mut vec = Vec::new(); + vec.push("".to_string()); + vec + }, + } + } +} + +impl Tty for TerminalTty<'_> { + fn putc(&mut self, c: char) { + if c == '\n' { + self.lines.push("".to_string()); + return; + } + let i = self.lines.len() - 1; + self.lines[i].push(c); + } + + fn puts(&mut self, s: &str) { + for c in s.chars() { + self.putc(c); + } + } + + fn clear(&mut self) { + self.lines.clear(); + self.lines.push("".to_string()); + } + + fn flush(&mut self) { + let mut physical_lines = Vec::new(); + for line in &self.lines { + let mut chars = line.chars().collect::>().into_iter(); + while chars.len() > 0 { + let mut physical_line = String::new(); + let width = chars.len().min(self.term.width()); + for _ in 0..width { + physical_line.push(chars.next().unwrap()); + } + physical_lines.push(physical_line); + } + } + + let mut y = physical_lines.len().min(self.term.height() - 1); + for line in physical_lines.into_iter().rev() { + let mut x = 0; + for c in line.chars() { + self.term.set_char(x, y, c); + x += 1; + } + + if y == 0 { + break; + } + + y -= 1; + } + + self.term.refresh(); + } +} diff --git a/src/main.rs b/src/main.rs index 0074517..cb85044 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,6 @@ #![no_std] #![no_main] #![feature(abi_efiapi)] -#![feature(alloc)] #![feature(alloc_error_handler)] #![feature(asm)] extern crate alloc;