Skip to main content

bootloader_x86_64_common/
framebuffer.rs

1use bootloader_api::info::{FrameBufferInfo, PixelFormat};
2use core::{fmt, ptr};
3use font_constants::BACKUP_CHAR;
4use noto_sans_mono_bitmap::{
5    FontWeight, RasterHeight, RasterizedChar, get_raster, get_raster_width,
6};
7
8/// Additional vertical space between lines
9const LINE_SPACING: usize = 2;
10/// Additional horizontal space between characters.
11const LETTER_SPACING: usize = 0;
12
13/// Padding from the border. Prevent that font is too close to border.
14const BORDER_PADDING: usize = 1;
15
16/// Constants for the usage of the [`noto_sans_mono_bitmap`] crate.
17mod font_constants {
18    use super::*;
19
20    /// Height of each char raster. The font size is ~0.84% of this. Thus, this is the line height that
21    /// enables multiple characters to be side-by-side and appear optically in one line in a natural way.
22    pub const CHAR_RASTER_HEIGHT: RasterHeight = RasterHeight::Size16;
23
24    /// The width of each single symbol of the mono space font.
25    pub const CHAR_RASTER_WIDTH: usize = get_raster_width(FontWeight::Regular, CHAR_RASTER_HEIGHT);
26
27    /// Backup character if a desired symbol is not available by the font.
28    /// The '�' character requires the feature "unicode-specials".
29    pub const BACKUP_CHAR: char = '�';
30
31    pub const FONT_WEIGHT: FontWeight = FontWeight::Regular;
32}
33
34/// Returns the raster of the given char or the raster of [`font_constants::BACKUP_CHAR`].
35fn get_char_raster(c: char) -> RasterizedChar {
36    fn get(c: char) -> Option<RasterizedChar> {
37        get_raster(
38            c,
39            font_constants::FONT_WEIGHT,
40            font_constants::CHAR_RASTER_HEIGHT,
41        )
42    }
43    get(c).unwrap_or_else(|| get(BACKUP_CHAR).expect("Should get raster of backup char."))
44}
45
46/// Allows logging text to a pixel-based framebuffer.
47pub struct FrameBufferWriter {
48    framebuffer: &'static mut [u8],
49    info: FrameBufferInfo,
50    x_pos: usize,
51    y_pos: usize,
52}
53
54impl FrameBufferWriter {
55    /// Creates a new logger that uses the given framebuffer.
56    pub fn new(framebuffer: &'static mut [u8], info: FrameBufferInfo) -> Self {
57        let mut logger = Self {
58            framebuffer,
59            info,
60            x_pos: 0,
61            y_pos: 0,
62        };
63        logger.clear();
64        logger
65    }
66
67    fn newline(&mut self) {
68        self.y_pos += font_constants::CHAR_RASTER_HEIGHT.val() + LINE_SPACING;
69        self.carriage_return()
70    }
71
72    fn carriage_return(&mut self) {
73        self.x_pos = BORDER_PADDING;
74    }
75
76    /// Erases all text on the screen. Resets `self.x_pos` and `self.y_pos`.
77    pub fn clear(&mut self) {
78        self.x_pos = BORDER_PADDING;
79        self.y_pos = BORDER_PADDING;
80        self.framebuffer.fill(0);
81    }
82
83    fn width(&self) -> usize {
84        self.info.width
85    }
86
87    fn height(&self) -> usize {
88        self.info.height
89    }
90
91    /// Writes a single char to the framebuffer. Takes care of special control characters, such as
92    /// newlines and carriage returns.
93    fn write_char(&mut self, c: char) {
94        match c {
95            '\n' => self.newline(),
96            '\r' => self.carriage_return(),
97            c => {
98                let new_xpos = self.x_pos + font_constants::CHAR_RASTER_WIDTH;
99                if new_xpos >= self.width() {
100                    self.newline();
101                }
102                let new_ypos =
103                    self.y_pos + font_constants::CHAR_RASTER_HEIGHT.val() + BORDER_PADDING;
104                if new_ypos >= self.height() {
105                    self.clear();
106                }
107                self.write_rendered_char(get_char_raster(c));
108            }
109        }
110    }
111
112    /// Prints a rendered char into the framebuffer.
113    /// Updates `self.x_pos`.
114    fn write_rendered_char(&mut self, rendered_char: RasterizedChar) {
115        for (y, row) in rendered_char.raster().iter().enumerate() {
116            for (x, byte) in row.iter().enumerate() {
117                self.write_pixel(self.x_pos + x, self.y_pos + y, *byte);
118            }
119        }
120        self.x_pos += rendered_char.width() + LETTER_SPACING;
121    }
122
123    fn write_pixel(&mut self, x: usize, y: usize, intensity: u8) {
124        let pixel_offset = y * self.info.stride + x;
125        let color = match self.info.pixel_format {
126            PixelFormat::Rgb => [intensity, intensity, intensity / 2, 0],
127            PixelFormat::Bgr => [intensity / 2, intensity, intensity, 0],
128            PixelFormat::U8 => [if intensity > 200 { 0xf } else { 0 }, 0, 0, 0],
129            other => {
130                // set a supported (but invalid) pixel format before panicking to avoid a double
131                // panic; it might not be readable though
132                self.info.pixel_format = PixelFormat::Rgb;
133                panic!("pixel format {:?} not supported in logger", other)
134            }
135        };
136        let bytes_per_pixel = self.info.bytes_per_pixel;
137        let byte_offset = pixel_offset * bytes_per_pixel;
138        self.framebuffer[byte_offset..(byte_offset + bytes_per_pixel)]
139            .copy_from_slice(&color[..bytes_per_pixel]);
140        let _ = unsafe { ptr::read_volatile(&self.framebuffer[byte_offset]) };
141    }
142}
143
144unsafe impl Send for FrameBufferWriter {}
145unsafe impl Sync for FrameBufferWriter {}
146
147impl fmt::Write for FrameBufferWriter {
148    fn write_str(&mut self, s: &str) -> fmt::Result {
149        for c in s.chars() {
150            self.write_char(c);
151        }
152        Ok(())
153    }
154}