Demilade Sonuga's blog

All posts

Printing Hello World Again II

2022-11-22 · 40 min read

In the last post, we pinned down our font for the letters 'A' - 'Z'. If you got stumped along the way, the full source for the last post is here.

Before we proceed to print hello world, let's first check out our new font. Because of the way the font is defined, printing a single character to the screen is simply a matter of iterating over the arrays of the character's description and then placing a pixel with a foreground color in the screen position when a true is encountered, and a background colored pixel in the screen position when a false is encountered.

At this point, we also need to make another addition to our font.rs:

pub const FONT: [[[bool; 16]; 16]; 26] = [
A, B, C, D, E, F, G, H, I, J, K, L, M, N,
O, P, Q, R, S, T, U, V, W, X, Y, Z
];

This FONT array is the full font description of all the characters our font can represent. This is also the public interface to our font, that is, any code that wants to make use of the font we've defined here will have to access it through the FONT array.

To print all characters on the screen, we first need to envision the screen like so:

Screen:
      0-15 16-31 32-47 48-63
0-15  A    B     C     D
16-32 E    F     G     H    

The above shows a 64x32 screen packed with letters 'A' to 'H'. Remember our font is 16x16 pixels. So on such a screen, a row can take only 64 / 16 == 4 characters and a column can take only 32 / 16 == 2 characters.

Now, we need an algorithm (a definite, step-by-step procedure) to draw the characters in our font on the screen. When one character is drawn, the position it's drawn in must not overlap any other character's position. When the end of the row is reached, the characters must continue at the beginning of the next row and when there are no more rows, the algorithm must be able to detect that and stop printing.

Stop for a moment and think about how you'll do this yourself.

Our algorithm:

fn draw_font()
    curr_row = 0
    curr_col = 0
    for font_char in 0..FONT.len()
        for i in 0..16
            for j in 0..16
                if FONT[font_char][i][j]
                    screen.pixels[curr_row + i][curr_col + j] = yellow pixel
                else
                    screen.pixels[curr_row + i][curr_col + j] = black pixel
        curr_col += 16
        if curr_col >= SCREEN_WIDTH
            curr_row = curr_row + 16
            curr_col = 0
        if curr_row >= SCREEN_HEIGHT
            break out of loops

The algorithm described above draws all the characters described in the FONT array on the screen. At each point, the curr_row and curr_col give the current position to write the next character into. The outer for loop that iterates over the indexes of the FONT array is for iterating over all the characters in the array. The inner for loops are responsible for the actual drawing of the character on screen and updating the curr_row and curr_col variables. The index variable i gives the row number of the character in its slot on the screen and the index variable j gives the column number of the character in its slot on the screen. In the loop body, whether or not a foreground color should be drawn is indicated by the FONT[font_char][i][j]. If it's true, a foreground-colored pixel (in this case, a yellow pixel) is drawn. If it's false, a background pixel (in this case, a black pixel) is drawn.

The screen position is updated by increasing curr_col by 16. This is because the font characters have a width of 16 pixels. The if condition comparing curr_col and SCREEN_WIDTH is simply checking if the position has reached the end of the screen. If it has, increase curr_row by 16 (move to the next row) and reset the curr_col variable to 0 (meaning start from the first cell of row curr_row).

The second if condition (curr_row >= SCREEN_HEIGHT) checks if the current position has passed all the rows on the screen. If it has, break the loop and stop printing.

Consider the following 32x32 screen:

Screen
       0-15  16-31
0-15
16-31

Say, all the pixels in this screen are all colored black (that is, the screen is blank). The execution of this algorithm with our 'A' - 'Z' font will go something like this:

  1. curr_row and curr_col are initialized to 0
  2. font_char is 0 and FONT[font_char] refers to the description for the letter 'A'.
  3. The inner 2 loops execute and put pixels in the screen's pixels array according to the letter 'A's font description. The screen now looks like this:
Screen
       0-15  16-31
0-15   A
16-31
  1. curr_col is increased by 16. curr_col and curr_cow are still less than SCREEN_WIDTH and the SCREEN_HEIGHT, so there is no need to move to the next row, or stop printing. curr_col is now 16, so the cell after the one in which 'A' was printed is the one that 'B' will be printed into.
  2. font_char is increased to 1 and FONT[font_char] now refers the the description of the letter 'B'.
  3. The inner two loops execute and put pixels in the screen's pixels array according to the letter 'B's font description. The screen now looks like this:
Screen
       0-15  16-31
0-15   A     B
16-31
  1. curr_col is increased from 16 to 16 + 16 == 32. 32 == SCREEN_WIDTH (SCREEN_WIDTH of our dummy screen is 32), so the first if-condition is fulfilled. The curr_row is increased from 0 to 16 and curr_col is reset to 0. So, the cell under the one in which 'A' was printed is the one that 'C' will be printed into.
  2. font_char is increased to 2 and FONT[font_char] now holds the font description of the character 'C'.
  3. The inner two loops execute and put pixels in the screen's pixels array according to 'C's font description. The screen looks like this now:
Screen
       0-15  16-31
0-15   A     B
16-31  C
  1. curr_col is increased from 0 to 16, so the next character will be printed under 'B'. Since the row isn't full yet and we haven't run out of rows to print in, the two if conditions aren't executed.
  2. font_char is increased to 3 and FONT[font_char] now holds 'D's font description.
  3. The inner two loops draw 'D' to the screen according to 'D's font description. The screen looks like this now:
Screen
       0-15  16-31
0-15   A     B
16-31  C     D
  1. curr_col is increased from 16 to 32. Since 32 == SCREEN_WIDTH, the current position needs to be moved to the next row. curr_row is increased to 16 + 16 == 32 and curr_col is reset to 0. But now, curr_row == SCREEN_HEIGHT (SCREEN HEIGHT in our tiny hypothetical screen is 32), so we're out of rows, so we stop printing and break out of the loop.

Okay. That was that. Now that we understand how this algorithm works, let's use it to draw out our font's characters.

Your main.rs's efi_main:

#[no_mangle]
extern "efiapi" fn efi_main(handle: *const core::ffi::c_void, sys_table: *mut SystemTable) -> usize {
let boot_services = unsafe { (*sys_table).boot_services };
let gop_guid = Guid {
first_chunk: 0x9042a9de,
second_chunk: 0x23dc,
third_chunk: 0x4a38,
other_chunks: [0x96, 0xfb, 0x7a, 0xde, 0xd0, 0x80, 0x51, 0x6a]
};
let mut gop: *mut core::ffi::c_void = core::ptr::null_mut();
let guid_ptr = &gop_guid as *const Guid;
let registration = core::ptr::null_mut();
let gop_ptr = &mut gop as *mut _;
let locate_gop_status = unsafe { ((*boot_services).locate_protocol)(
guid_ptr,
registration,
gop_ptr
) };

if locate_gop_status != 0 {
let mut string_u16 = [0u16; 22];
let string = "Failed to locate GOP\n";
string.encode_utf16()
.enumerate()
.for_each(|(i, letter)| string_u16[i] = letter);
let simple_text_output = unsafe { (*sys_table).simple_text_output };
unsafe { ((*simple_text_output).output_string)(simple_text_output, string_u16.as_mut_ptr()); }
loop {}
}

let gop = gop as *mut GraphicsOutput;
let mode = unsafe { (*gop).mode };
let max_mode = unsafe { (*mode).max_mode };
let mut desired_mode = 0;
for mode_number in 0..max_mode {
let size_of_info = core::mem::size_of::<GraphicsModeInfo>();
let mut mode: *const GraphicsModeInfo = core::ptr::null_mut();
let query_mode = unsafe { (*gop).query_mode };
let query_status = (query_mode)(
gop,
mode_number,
&size_of_info as *const _,
&mut mode as *mut _
);
if query_status != 0 {
let mut string_u16 = [0u16; 19];
let string = "query_mode failed\n";
string.encode_utf16()
.enumerate()
.for_each(|(i, letter)| string_u16[i] = letter);
let simple_text_output = unsafe { (*sys_table).simple_text_output };
unsafe { ((*simple_text_output).output_string)(simple_text_output, string_u16.as_mut_ptr()); }
loop {}
}

let horizontal_resolution = unsafe { (*mode).horizontal_resolution };
let vertical_resolution = unsafe { (*mode).vertical_resolution };
let pixel_format = unsafe { (*mode).pixel_format };
if horizontal_resolution == 640 && vertical_resolution == 480
&& pixel_format == PixelFormat::BlueGreenRedReserved {
desired_mode = mode_number;
break;
}
if mode_number == max_mode - 1 {
let mut string_u16 = [0u16; 32];
let string = "Couldn't find the desired mode\n";
string.encode_utf16()
.enumerate()
.for_each(|(i, letter)| string_u16[i] = letter);
let simple_text_output = unsafe { (*sys_table).simple_text_output };
unsafe { ((*simple_text_output).output_string)(simple_text_output, string_u16.as_mut_ptr()); }
loop {}
}
}

let set_mode_status = unsafe { ((*gop).set_mode)(
gop,
desired_mode
) };

if set_mode_status != 0 {
let mut string_u16 = [0u16; 32];
let string = "Failed to set the desired mode\n";
string.encode_utf16()
.enumerate()
.for_each(|(i, letter)| string_u16[i] = letter);
let simple_text_output = unsafe { (*sys_table).simple_text_output };
unsafe { ((*simple_text_output).output_string)(simple_text_output, string_u16.as_mut_ptr()); }
loop {}
}

let framebuffer_base = unsafe { (*mode).framebuffer_base };

let screen = framebuffer_base as *mut Screen;

/* DELETED:
let space = 20;
let mut row_number = 0;
loop {
row_number += space;
if row_number >= NO_OF_PIXELS_IN_A_COLUMN {
break;
} else {
let mut column_number = 0;
while column_number < NO_OF_PIXELS_IN_A_ROW {
unsafe {
(*screen).pixels[row_number][column_number] = Pixel {
red: u8::MAX,
blue: 0,
green: 0,
reserved: 0
};
}
column_number += 1;
}
}
}
*/


// NEW:
// The current row index of the starting row the next character will be drawn in
let mut curr_row = 0;
// The current column index of the starting column the next character will be drawn in
let mut curr_col = 0;
// Iterating over all character descriptions
for font_char in 0..FONT.len() {
// Drawing the character according to the font descriptions
for i in 0..16 {
for j in 0..16 {
if FONT[font_char][i][j] {
// Red and green is yellow
unsafe {
(*screen).pixels[curr_row + i][curr_col + j] = Pixel {
red: 255,
green: 255,
blue: 0,
reserved: 0,
};
}
} else {
// All 0 is black
unsafe {
(*screen).pixels[curr_row + i][curr_col + j] = Pixel {
red: 0,
green: 0,
blue: 0,
reserved: 0,
};
}
}
}
}
// The column index for the next character to be drawn
curr_col += 16;
// Is the current row full?
if curr_col >= NO_OF_PIXELS_IN_A_ROW {
// Move to the next row
curr_row = curr_row + 16;
// Start from the beginning of the row
curr_col = 0;
}
// Is the newly set row an actual row or are we out
// of rows to draw in on the screen
if curr_row >= NO_OF_PIXELS_IN_A_COLUMN {
// Out of rows to draw in
// Stop drawing
break;
}
}

0
}

One more thing is missing. Before we can use the FONT array, we first have to import it (because it's defined somewhere else).

Add this at the top of your main.rs file:

#![no_std]
#![no_main]
#![feature(abi_efiapi)]

// NEW
mod font;
use font::FONT;

Okay. Now, upon building and running, your screen should look like this:

Letters A to Z

Take Away

Code till now:

Directory view:

blasterball/
| .cargo/
| | config.toml
| src/
| | font.rs
| | main.rs
| .gitignore
| Cargo.lock
| Cargo.toml

font.rs contents

// ... Your font descriptions for letters 'A' to 'Z'

pub const FONT: [[[bool; 16]; 16]; 26] = [
A, B, C, D, E, F, G, H, I, J, K, L, M, N
O, P, Q, R, S, T, U, V, W, X, Y, Z
];

main.rs contents

// ... Before efi_main

#[no_mangle]
extern "efiapi" fn efi_main(handle: *const core::ffi::c_void, sys_table: *mut SystemTable) -> usize {
let boot_services = unsafe { (*sys_table).boot_services };
let gop_guid = Guid {
first_chunk: 0x9042a9de,
second_chunk: 0x23dc,
third_chunk: 0x4a38,
other_chunks: [0x96, 0xfb, 0x7a, 0xde, 0xd0, 0x80, 0x51, 0x6a]
};
let mut gop: *mut core::ffi::c_void = core::ptr::null_mut();
let guid_ptr = &gop_guid as *const Guid;
let registration = core::ptr::null_mut();
let gop_ptr = &mut gop as *mut _;
let locate_gop_status = unsafe { ((*boot_services).locate_protocol)(
guid_ptr,
registration,
gop_ptr
) };

if locate_gop_status != 0 {
let mut string_u16 = [0u16; 22];
let string = "Failed to locate GOP\n";
string.encode_utf16()
.enumerate()
.for_each(|(i, letter)| string_u16[i] = letter);
let simple_text_output = unsafe { (*sys_table).simple_text_output };
unsafe { ((*simple_text_output).output_string)(simple_text_output, string_u16.as_mut_ptr()); }
loop {}
}

let gop = gop as *mut GraphicsOutput;
let mode = unsafe { (*gop).mode };
let max_mode = unsafe { (*mode).max_mode };
let mut desired_mode = 0;
for mode_number in 0..max_mode {
let size_of_info = core::mem::size_of::<GraphicsModeInfo>();
let mut mode: *const GraphicsModeInfo = core::ptr::null_mut();
let query_mode = unsafe { (*gop).query_mode };
let query_status = (query_mode)(
gop,
mode_number,
&size_of_info as *const _,
&mut mode as *mut _
);
if query_status != 0 {
let mut string_u16 = [0u16; 19];
let string = "query_mode failed\n";
string.encode_utf16()
.enumerate()
.for_each(|(i, letter)| string_u16[i] = letter);
let simple_text_output = unsafe { (*sys_table).simple_text_output };
unsafe { ((*simple_text_output).output_string)(simple_text_output, string_u16.as_mut_ptr()); }
loop {}
}

let horizontal_resolution = unsafe { (*mode).horizontal_resolution };
let vertical_resolution = unsafe { (*mode).vertical_resolution };
let pixel_format = unsafe { (*mode).pixel_format };
if horizontal_resolution == 640 && vertical_resolution == 480
&& pixel_format == PixelFormat::BlueGreenRedReserved {
desired_mode = mode_number;
break;
}
if mode_number == max_mode - 1 {
let mut string_u16 = [0u16; 32];
let string = "Couldn't find the desired mode\n";
string.encode_utf16()
.enumerate()
.for_each(|(i, letter)| string_u16[i] = letter);
let simple_text_output = unsafe { (*sys_table).simple_text_output };
unsafe { ((*simple_text_output).output_string)(simple_text_output, string_u16.as_mut_ptr()); }
loop {}
}
}

let set_mode_status = unsafe { ((*gop).set_mode)(
gop,
desired_mode
) };

if set_mode_status != 0 {
let mut string_u16 = [0u16; 32];
let string = "Failed to set the desired mode\n";
string.encode_utf16()
.enumerate()
.for_each(|(i, letter)| string_u16[i] = letter);
let simple_text_output = unsafe { (*sys_table).simple_text_output };
unsafe { ((*simple_text_output).output_string)(simple_text_output, string_u16.as_mut_ptr()); }
loop {}
}

let framebuffer_base = unsafe { (*mode).framebuffer_base };

let screen = framebuffer_base as *mut Screen;

let mut curr_row = 0;
let mut curr_col = 0;
for font_char in 0..FONT.len() {
for i in 0..16 {
for j in 0..16 {
if FONT[font_char][i][j] {
screen.pixels[curr_row + i][curr_col + j] = Pixel {
red: 255,
green: 255,
blue: 0,
reserved: 0
}
} else {
screen.pixels[curr_row + i][curr_col + j] = Pixel {
red: 0,
green: 0,
blue: 0,
reserved: 0
}
}
}
}
curr_col += 16;
if curr_col >= SCREEN_WIDTH {
curr_row = curr_row + 16;
curr_col = 0;
}
if curr_row >= SCREEN_HEIGHT {
break;
}
}

0
}

In the Next Post

We'll be printing hello world (again!).