Demilade Sonuga's blog
All postsPrinting Hello World Again II
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:
curr_row
andcurr_col
are initialized to 0font_char
is 0 andFONT[font_char]
refers to the description for the letter 'A'.- 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
curr_col
is increased by 16.curr_col
andcurr_cow
are still less thanSCREEN_WIDTH
and theSCREEN_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.font_char
is increased to 1 andFONT[font_char]
now refers the the description of the letter 'B'.- 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
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. Thecurr_row
is increased from 0 to 16 andcurr_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.font_char
is increased to 2 andFONT[font_char]
now holds the font description of the character 'C'.- 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
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.font_char
is increased to 3 andFONT[font_char]
now holds 'D's font description.- 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
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 andcurr_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:
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!).