Compare commits

..

23 Commits

Author SHA1 Message Date
Philipp Oppermann
bd3550ea87 Merge pull request #1410 from v4zha/edition-3
loading UEFI using ovmf_prebuilt=0.2.3 with ovmf_code and ovmf_vars
2025-05-09 15:51:53 +02:00
V4zha
ecb60ec326 Update index.md
change to_str().unwrap() to display() in format! args
2025-04-24 18:48:52 +05:30
v4zha
8a1267477a loading UEFI using ovmf_prebuilt=0.2.3 with ovmf_code and ovmf_vars 2025-04-24 16:58:59 +05:30
Philipp Oppermann
ce01059620 Fix typos
Fixes #1339
2024-08-26 07:56:55 +02:00
Philipp Oppermann
4d0c3ac188 Merge pull request #1333 from proudmuslim-dev/patch-5
Move import to sensible location in chapter 3
2024-07-25 22:57:03 +02:00
proudmuslim-dev
d565cd125b Move import to sensible location in chapter 3
It served no purpose in the previous code block and would only confuse the user
2024-07-04 19:15:21 +00:00
Philipp Oppermann
ca86085360 Merge pull request #1299 from spocino/patch-1
fix typo'd variable name in post 3 (doesn't compile)
2024-02-27 07:52:00 +01:00
Samuel Pocino
5f3d38884c fix typo'd variable name in post 3 (doesn't compile) 2024-02-24 20:32:28 -05:00
Philipp Oppermann
f557d1c698 Merge pull request #1276 from proudmuslim-dev/patch-4
Fix `embedded_graphics` code + typo in chapter 3
2024-02-04 17:09:00 +01:00
proudmuslim-dev
0c248d027e Fix embedded_graphics code + correct typo in chapter 3
Compiles now
2024-01-31 12:55:20 -08:00
Philipp Oppermann
2cf0675a2d Merge pull request #1269 from proudmuslim-dev/patch-3
Fix typos in code for `embedded_graphics` crate in chapter 3
2024-01-28 12:18:43 +01:00
Philipp Oppermann
916ad36e78 Merge pull request #1270 from lachsdachs/patch-1
fix a lil typo
2024-01-28 12:09:11 +01:00
lachsdachs
3c2e91fa4e fix a lil typo
sturcts -> structs
2024-01-27 21:23:35 +01:00
proudmuslim-dev
c9683a2cd9 Fix typos in code for embedded_graphics crate in chapter 3
This still won't compile on account of the fact that the `Point` type apparently doesn't implement `Into<(usize, usize)>`. Attempting to change the type just results in more issues
2024-01-27 00:21:18 +00:00
Philipp Oppermann
514736b1d8 Merge pull request #1265 from proudmuslim-dev/patch-1
Fix typo in chapter 2
2024-01-24 10:01:44 +01:00
Philipp Oppermann
647b509971 Merge pull request #1266 from proudmuslim-dev/patch-2
Fix formatting in chapter 2
2024-01-22 11:27:39 +01:00
proudmuslim-dev
1118350b16 Fix formatting in chapter 2 2024-01-21 22:00:19 +00:00
proudmuslim-dev
fb096a7484 Fix typo in chapter 2 2024-01-21 21:34:52 +00:00
Philipp Oppermann
8a41fd65bf Polish section about drawing blue pixels and squares 2023-12-29 18:57:01 +01:00
Philipp Oppermann
50802c8332 Continue work on framebuffer post 2023-12-28 20:12:07 +01:00
Philipp Oppermann
ba410f40ba Fix source path 2023-12-28 20:11:46 +01:00
Philipp Oppermann
a119d36cc9 Tweak heading style 2023-12-28 17:59:23 +01:00
Philipp Oppermann
9080e69a09 Improve headings 2023-12-28 17:59:13 +01:00
6 changed files with 330 additions and 28 deletions

View File

@@ -445,7 +445,7 @@ Afterwards, you can run the tools through `rust-nm`, `rust-objdump`, and `rust-s
[`cargo-binutils`]: https://github.com/rust-embedded/cargo-binutils [`cargo-binutils`]: https://github.com/rust-embedded/cargo-binutils
### `nm` ### List Symbols using `nm`
We defined a `_start` function as the entry point of our kernel. We defined a `_start` function as the entry point of our kernel.
To verify that it is properly exposed in the executable, we can run `nm` to list all the symbols defined in the executable: To verify that it is properly exposed in the executable, we can run `nm` to list all the symbols defined in the executable:
@@ -463,7 +463,7 @@ If we comment out the `_start` function or if we remove the `#[no_mangle]` attri
This way we can ensure that we set the `_start` function correctly. This way we can ensure that we set the `_start` function correctly.
### `objdump` ### Inspect ELF File using `objdump`
The `objdump` tool can inspect different parts of executables that use the [ELF file format]. This is the file format that the `x86_64-unknown-none` target uses, so we can use `objdump` to inspect our kernel executable. The `objdump` tool can inspect different parts of executables that use the [ELF file format]. This is the file format that the `x86_64-unknown-none` target uses, so we can use `objdump` to inspect our kernel executable.

View File

@@ -56,7 +56,7 @@ UEFI, in contrast, is more modern and has much more features, but also more comp
### BIOS ### BIOS
Almost all x86 systems have support for BIOS booting, including most UEFI-based machines that support an emulated BIOS. Almost all x86 systems have support for BIOS booting, including most UEFI-based machines that support an emulated BIOS.
This is great, because you can use the same boot logic across all machines from the last centuries. This is great, because you can use the same boot logic across all machines from the last century.
The drawback is that the standard is very old, for example the CPU is put into a 16-bit compatibility mode called [real mode] before booting so that archaic bootloaders from the 1980s would still work. The drawback is that the standard is very old, for example the CPU is put into a 16-bit compatibility mode called [real mode] before booting so that archaic bootloaders from the 1980s would still work.
Also, BIOS-compatibility will be slowly removed on newer UEFI machines over the next years (see below). Also, BIOS-compatibility will be slowly removed on newer UEFI machines over the next years (see below).
@@ -75,7 +75,7 @@ This structure has the following general format:
| 446 | partition entry 1 | 16 | | 446 | partition entry 1 | 16 |
| 462 | partition entry 2 | 16 | | 462 | partition entry 2 | 16 |
| 478 | partition entry 3 | 16 | | 478 | partition entry 3 | 16 |
| 444 | partition entry 4 | 16 | | 494 | partition entry 4 | 16 |
| 510 | boot signature | 2 | | 510 | boot signature | 2 |
The bootstrap code is commonly called the _bootloader_ and responsible for loading and starting the operating system kernel. The bootstrap code is commonly called the _bootloader_ and responsible for loading and starting the operating system kernel.
@@ -292,7 +292,7 @@ There are a few notable things:
To verify that the `entry_point` macro worked as expected, we can use the `objdump` tool as [described in the previous post][objdump-prev]. First, we recompile using `cargo build --target x86_64-unknown-none`, then we inspect the section headers using `objdump` or `rust-objdump`: To verify that the `entry_point` macro worked as expected, we can use the `objdump` tool as [described in the previous post][objdump-prev]. First, we recompile using `cargo build --target x86_64-unknown-none`, then we inspect the section headers using `objdump` or `rust-objdump`:
[objdump-prev]: @/edition-3/posts/01-minimal-kernel/index.md#objdump [objdump-prev]: @/edition-3/posts/01-minimal-kernel/index.md#inspect-elf-file-using-objdump
```bash,hl_lines=8 ```bash,hl_lines=8
rust-objdump -h target/x86_64-unknown-none/debug/kernel rust-objdump -h target/x86_64-unknown-none/debug/kernel
@@ -491,7 +491,7 @@ profile = "default"
targets = ["x86_64-unknown-none"] targets = ["x86_64-unknown-none"]
``` ```
The `channel` field specifies which [toolchain`] to use. The `channel` field specifies which [`toolchain`] to use.
In our case, we want to use the latest nightly compiler. In our case, we want to use the latest nightly compiler.
We could also specify a specific nightly here, e.g. `nightly-2023-04-30`, which can be useful when there is some breakage in the newest nightly. We could also specify a specific nightly here, e.g. `nightly-2023-04-30`, which can be useful when there is some breakage in the newest nightly.
In the `targets` list, we can specify additional targets that we want to compile to. In the `targets` list, we can specify additional targets that we want to compile to.
@@ -706,7 +706,7 @@ We then use the the `create_uefi_image` and `create_bios_image` methods to creat
[requires build scripts]: https://doc.rust-lang.org/cargo/reference/build-scripts.html#outputs-of-the-build-script [requires build scripts]: https://doc.rust-lang.org/cargo/reference/build-scripts.html#outputs-of-the-build-script
[`join`]: https://doc.rust-lang.org/std/path/struct.PathBuf.html#method.join [`join`]: https://doc.rust-lang.org/std/path/struct.PathBuf.html#method.join
We can now use use a simple `cargo build` to cross-compile our kernel, build the bootloader, and combine them to create a bootable disk image: We can now use a simple `cargo build` to cross-compile our kernel, build the bootloader, and combine them to create a bootable disk image:
``` ```
cargo build cargo build
@@ -1011,27 +1011,35 @@ Now we can create our `qemu-uefi` executable at `src/bin/qemu-uefi.rs`:
```rust ,hl_lines=3-15 ```rust ,hl_lines=3-15
// src/bin/qemu-uefi.rs // src/bin/qemu-uefi.rs
use std::{ use std::{
env, env, process::{self, Command}
process::{self, Command},
}; };
use ovmf_prebuilt::{Arch, FileType, Prebuilt, Source};
fn main() { fn main() {
let prebuilt =
Prebuilt::fetch(Source::LATEST, "target/ovmf").unwrap();
let ovmf_code = prebuilt.get_file(Arch::X64, FileType::Code);
let ovmf_vars = prebuilt.get_file(Arch::X64, FileType::Vars);
let mut qemu = Command::new("qemu-system-x86_64"); let mut qemu = Command::new("qemu-system-x86_64");
qemu.arg("-drive"); qemu.args([
qemu.arg(format!("format=raw,file={}", env!("UEFI_IMAGE"))); "-drive",
qemu.arg("-bios").arg(ovmf_prebuilt::ovmf_pure_efi()); &format!("format=raw,if=pflash,readonly=on,file={}", ovmf_code.display()),
"-drive",
&format!("format=raw,if=pflash,file={}", ovmf_vars.display()),
"-drive",
&format!("format=raw,file={}", env!("UEFI_IMAGE")),
]);
let exit_status = qemu.status().unwrap(); let exit_status = qemu.status().unwrap();
process::exit(exit_status.code().unwrap_or(-1)); process::exit(exit_status.code().unwrap_or(-1));
} }
``` ```
It's very similar to our `qemu-bios` executable. It's very similar to our `qemu-bios` executable.
The only two differences are that it passes an additional `-bios` argument and that it uses the `UEFI_IMAGE` instead of the `BIOS_IMAGE`. The only two differences are that it passes two additional `-drive if=pflash,..` arguments to load UEFI firmware (`OVMF_CODE.fd`) and writable NVRAM (`OVMF_VARS.fd`), and that it uses the `UEFI_IMAGE` instead of the `BIOS_IMAGE`.
Using a quick `cargo run --bin qemu-uefi`, we can confirm that it works as intended. Using a quick `cargo run --bin qemu-uefi`, we can confirm that it works as intended.
### Screen Output ### Screen Output
While we see some screen output from the bootloader, our kernel still does nothing. While we see some screen output from the bootloader, our kernel still does nothing.
@@ -1047,7 +1055,7 @@ Since the size, pixel format, and memory location of the framebuffer can vary be
The easiest way to do this is to read it from the [boot information structure][`BootInfo`] that the bootloader passes as argument to our kernel entry point: The easiest way to do this is to read it from the [boot information structure][`BootInfo`] that the bootloader passes as argument to our kernel entry point:
```rust ,hl_lines=3 7-13 ```rust ,hl_lines=3 7-13
// in src/kernel/main.rs // in kernel/src/main.rs
use bootloader_api::BootInfo; use bootloader_api::BootInfo;
@@ -1083,7 +1091,7 @@ For now, let's just try setting the whole screen to some color.
For this, we just set every pixel in the byte slice to some fixed value: For this, we just set every pixel in the byte slice to some fixed value:
```rust ,hl_lines=5-7 ```rust ,hl_lines=5-7
// in src/kernel/main.rs // in kernel/src/main.rs
fn kernel_main(boot_info: &'static mut BootInfo) -> ! { fn kernel_main(boot_info: &'static mut BootInfo) -> ! {
if let Some(framebuffer) = boot_info.framebuffer.as_mut() { if let Some(framebuffer) = boot_info.framebuffer.as_mut() {

View File

@@ -41,10 +41,10 @@ Using the [`BootInfo`] provided by the bootloader, we were able to access a spec
We wrote some example code to display a gray background: We wrote some example code to display a gray background:
[previous post]: @/edition-3/posts/02-booting/index.md [previous post]: @/edition-3/posts/02-booting/index.md
[`BootInfo`]: https://docs.rs/bootloader_api/latest/bootloader_api/info/struct.BootInfo.html [`BootInfo`]: https://docs.rs/bootloader_api/0.11/bootloader_api/info/struct.BootInfo.html
```rust ```rust
// in src/kernel/main.rs // in kernel/src/main.rs
fn kernel_main(boot_info: &'static mut BootInfo) -> ! { fn kernel_main(boot_info: &'static mut BootInfo) -> ! {
if let Some(framebuffer) = boot_info.framebuffer.as_mut() { if let Some(framebuffer) = boot_info.framebuffer.as_mut() {
@@ -65,22 +65,315 @@ The pixels are laid out line by line, typically starting at the top.
For example, the pixels of an image with width 10 and height 3 would be typically stored in this order: For example, the pixels of an image with width 10 and height 3 would be typically stored in this order:
<table><tbody> <table style = "width: fit-content;"><tbody>
<tr><td>0</td><td>1</td><td>2</td><td>3</td><td>4</td><td>5</td><td>6</td><td>7</td><td>8</td><td>9</td></tr> <tr><td>0</td><td>1</td><td>2</td><td>3</td><td>4</td><td>5</td><td>6</td><td>7</td><td>8</td><td>9</td></tr>
<tr><td>10</td><td>11</td><td>12</td><td>13</td><td>14</td><td>15</td><td>16</td><td>17</td><td>18</td><td>19</td></tr> <tr><td>10</td><td>11</td><td>12</td><td>13</td><td>14</td><td>15</td><td>16</td><td>17</td><td>18</td><td>19</td></tr>
<tr><td>20</td><td>21</td><td>22</td><td>23</td><td>24</td><td>25</td><td>26</td><td>27</td><td>28</td><td>29</td></tr> <tr><td>20</td><td>21</td><td>22</td><td>23</td><td>24</td><td>25</td><td>26</td><td>27</td><td>28</td><td>29</td></tr>
</tbody></table> </tbody></table>
So top left pixel is stored at offset 0 in the bitmap array. So top left pixel is stored at offset 0 in the bitmap array.
The pixel on its right is at offset `pixel_size`. The pixel on its right is at pixel offset 1.
The first pixel of the next line starts at offset `line_length * pixel_size`. The first pixel of the next line starts at pixel offset `line_length`, which is 10 in this case.
The last line starts at pixel offset 20, which is `line_length * 2`.
### Padding ### Padding
Depending on the hardware and GPU firmware, it is often more efficient Depending on the hardware and GPU firmware, it is often more efficient to make lines start at well-aligned offsets.
Because of this, there is often some additional padding at the end of each line.
So the actual memory layout of the 10x3 example image might look like this, with the padding marked as yellow:
<table style = "width: fit-content;"><tbody>
<tr><td>0</td><td>1</td><td>2</td><td>3</td><td>4</td><td>5</td><td>6</td><td>7</td><td>8</td><td>9</td><td style="background-color:yellow;">10</td><td style="background-color:yellow;">11</td></tr>
<tr><td>12</td><td>13</td><td>14</td><td>15</td><td>16</td><td>17</td><td>18</td><td>19</td><td>20</td><td>21</td><td style="background-color:yellow;">22</td><td style="background-color:yellow;">23</td></tr>
<tr><td>24</td><td>25</td><td>26</td><td>27</td><td>28</td><td>29</td><td>30</td><td>31</td><td>32</td><td>33</td><td style="background-color:yellow;">34</td><td style="background-color:yellow;">35</td></tr>
</tbody></table>
So now the second line starts at pixel offset 12.
The two pixels at the end of each line are considered as padding and ignored.
So if we want to set the first pixel of the second line, we need to be aware of the additional padding and set the pixel at offset 12 instead of offset 10.
The line length plus the padding bytes is typically called the _stride_ or _pitch_ of the buffer.
In the example above, the stride is 12 and the line length is 10.
Since the amount of padding depends on the hardware, the stride is only known at runtime.
The `bootloader` crate queries the framebuffer parameters from the UEFI or BIOS firmware and reports them as part of the `BootInfo`.
It provides the stride of the framebuffer, among other parameters, in form of a [`FrameBufferInfo`] struct that can be created using the [`FrameBuffer::info`] method.
[`FrameBufferInfo`]: https://docs.rs/bootloader_api/0.11/bootloader_api/info/struct.FrameBufferInfo.html
[`FrameBuffer::info`]: https://docs.rs/bootloader_api/0.11/bootloader_api/info/struct.FrameBuffer.html#method.info
### Color formats ### Color formats
The [`FrameBufferInfo`] also specifies the [`PixelFormat`] of the framebuffer, which also depends on the underlying hardware.
Using this information, we can set pixels to different colors.
For example, the [`PixelFormat::Rgb`] variant specifies that each pixel is represented in the [RGB color space], which stores the red, green, and blue parts of the pixel as separate bytes.
In this model, the color red would be represented as the three bytes `[255, 0, 0]`, or `0xff0000` in [hexadecimal representation].
The color yellow is represented the addition of red and green, which results in `[255, 255, 0]` (or `0xffff00` in hexadecimal representation).
[`PixelFormat`]: https://docs.rs/bootloader_api/0.11/bootloader_api/info/enum.PixelFormat.html
[`PixelFormat::Rgb`]: https://docs.rs/bootloader_api/0.11/bootloader_api/info/enum.PixelFormat.html#variant.Rgb
[RGB color space]: https://en.wikipedia.org/wiki/RGB_color_spaces
[hexadecimal representation]: https://en.wikipedia.org/wiki/RGB_color_model#Numeric_representations
While the `Rgb` format is most common, there are also framebuffers that use a different color format.
For example, the [`PixelFormat::Bgr`] stores the three colors in inverted order, i.e. blue first and red last.
There are also buffers that don't support colors at all and can represent only grayscale pixels.
The `bootloader_api` crate reports such buffers as [`PixelFormat::U8`].
[`PixelFormat::Bgr`]: https://docs.rs/bootloader_api/0.11.5/bootloader_api/info/enum.PixelFormat.html#variant.Bgr
[`PixelFormat::U8`]: https://docs.rs/bootloader_api/0.11.5/bootloader_api/info/enum.PixelFormat.html#variant.U8
Note that there might be some additional padding at the pixel-level as well.
For example, an `Rgb` pixel might be stored as 4 bytes instead of 3 to ensure 32-bit alignment.
The number of bytes per pixel is reported by the bootloader in the [`FrameBufferInfo::bytes_per_pixel`] field.
[`FrameBufferInfo::bytes_per_pixel`]: https://docs.rs/bootloader_api/0.11/bootloader_api/info/struct.FrameBufferInfo.html#structfield.bytes_per_pixel
## Setting specific Pixels
Based on this above details, we can now create a function to set a specific pixel to a certain color.
We start by creating a new `framebuffer` [module]:
[module]: https://doc.rust-lang.org/book/ch07-02-defining-modules-to-control-scope-and-privacy.html
```rust ,hl_lines=3-5
// in kernel/src/main.rs
// declare a submodule -> the compiler will automatically look
// for a file named `framebuffer.rs` or `framebuffer/mod.rs`
mod framebuffer;
```
In the new module, we create basic structs for representing pixel positions and colors:
```rust ,hl_lines=3-16
// in new kernel/src/framebuffer.rs file
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Position {
pub x: usize,
pub y: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Color {
pub red: u8,
pub green: u8,
pub blue: u8,
}
```
By marking the structs and their fields as `pub`, we make them accessible from the parent `kernel` module.
We use the `#[derive]` attribute to implement the [`Debug`], [`Clone`], [`Copy`], [`PartialEq`], and [`Eq`] traits of Rust's core library.
These traits allow us to duplicate, compare, and print the structs.
[`Debug`]: https://doc.rust-lang.org/stable/core/fmt/trait.Debug.html
[`Clone`]: https://doc.rust-lang.org/stable/core/clone/trait.Clone.html
[`Copy`]: https://doc.rust-lang.org/stable/core/marker/trait.Copy.html
[`PartialEq`]: https://doc.rust-lang.org/stable/core/cmp/trait.PartialEq.html
[`Eq`]: https://doc.rust-lang.org/stable/core/cmp/trait.Eq.html
Next, we create a function for setting a specific pixel in the framebuffer to a given color:
```rust ,hl_lines=3 5-39
// in new kernel/src/framebuffer.rs file
use bootloader_api::info::{FrameBuffer, PixelFormat};
pub fn set_pixel_in(framebuffer: &mut FrameBuffer, position: Position, color: Color) {
let info = framebuffer.info();
// calculate offset to first byte of pixel
let byte_offset = {
// use stride to calculate pixel offset of target line
let line_offset = position.y * info.stride;
// add x position to get the absolute pixel offset in buffer
let pixel_offset = line_offset + position.x;
// convert to byte offset
pixel_offset * info.bytes_per_pixel
};
// set pixel based on color format
let pixel_buffer = &mut framebuffer.buffer_mut()[byte_offset..];
match info.pixel_format {
PixelFormat::Rgb => {
pixel_buffer[0] = color.red;
pixel_buffer[1] = color.green;
pixel_buffer[2] = color.blue;
}
PixelFormat::Bgr => {
pixel_buffer[0] = color.blue;
pixel_buffer[1] = color.green;
pixel_buffer[2] = color.red;
}
PixelFormat::U8 => {
// use a simple average-based grayscale transform
let gray = color.red / 3 + color.green / 3 + color.blue / 3;
pixel_buffer[0] = gray;
}
other => panic!("unknown pixel format {other:?}"),
}
}
```
The first step is to calculate the byte offset within the framebuffer slice at which the pixel starts.
For this, we first calculate the pixel offset of the line by multiplying the `y` position with the stride of the framebuffer, i.e. its line width plus the line padding.
We then add the `x` position to get the absolute index of the pixel.
As the framebuffer slice is a byte slice, we need to transform the pixel index to a byte offset by multiplying it with the number of `bytes_per_pixel`.
[`FrameBuffer::buffer_mut`]: https://docs.rs/bootloader_api/0.11.5/bootloader_api/info/struct.FrameBuffer.html#method.buffer_mut
The second step is to set the pixel to the desired color.
We first use the [`FrameBuffer::buffer_mut`] method to get access to the actual bytes of the framebuffer in form of a slice.
Then, we use the slicing operator `[byte_offset..]` to get a sub-slice starting at the `byte_offset` of the target pixel.
As the write operation depends on the pixel format, we use a [`match`] statement:
[`match`]: https://doc.rust-lang.org/stable/std/keyword.match.html
- For `Rgb` framebuffers, we write three bytes; first red, then green, then blue.
- For `Bgr` framebuffers, we also write three bytes, but blue first and red last.
- For `U8` framebuffers, we first convert the color to grayscale by taking the average of the three color channels.
Note that there are multiple [different ways to convert colors to grayscale], so you can also use different factors here.
- For all other framebuffer formats, we [panic] for now.
[different ways to convert colors to grayscale]: https://www.baeldung.com/cs/convert-rgb-to-grayscale#bd-convert-rgb-to-grayscale
[panic]: https://doc.rust-lang.org/stable/core/macro.panic.html
Let's try to use our new function to write a blue pixel in our `kernel_main` function:
```rust ,hl_lines=5-11
// in kernel/src/main.rs
fn kernel_main(boot_info: &'static mut BootInfo) -> ! {
if let Some(framebuffer) = boot_info.framebuffer.as_mut() {
let position = framebuffer::Position { x: 20, y: 100 };
let color = framebuffer::Color {
red: 0,
green: 0,
blue: 255,
};
framebuffer::set_pixel_in(framebuffer, position, color);
}
loop {}
}
```
When we run our code in QEMU using `cargo run --bin qemu-bios` (or `--bin qemu-uefi`) and look _very closely_, we can see the blue pixel.
It's really difficult to see, so I marked with an arrow below:
![QEMU the bootloader text output with one pixel set to blue. An annotated arrow points to the pixel](qemu-blue-pixel.png)
As this single pixel is too difficult to see, let's draw a filled square of 100x100 pixels instead:
```rust ,hl_lines=10-18
// in kernel/src/main.rs
fn kernel_main(boot_info: &'static mut BootInfo) -> ! {
if let Some(framebuffer) = boot_info.framebuffer.as_mut() {
let color = framebuffer::Color {
red: 0,
green: 0,
blue: 255,
};
for x in 0..100 {
for y in 0..100 {
let position = framebuffer::Position {
x: 20 + x,
y: 100 + y,
};
framebuffer::set_pixel_in(framebuffer, position, color);
}
}
}
loop {}
}
```
Now we clearly see that our code works as intended:
![QEMU showing a blue square above the bootloader text output](qemu-blue-square.png)
Feel free to experiment with different positions and colors if you like.
You can also try to draw a circle instead of a square, or a line with a certain thickness.
As you can probably imagine, it would be a lot of work to draw more complex shapes this way.
One example for such complex shapes is _text_, i.e. the rendering of letters and punctuation.
Fortunately, there is the nice `no_std`-compatible [`embedded-graphics`] crate, which provides draw functions for text, various shapes, and image data.
[`embedded-graphics`]: https://docs.rs/embedded-graphics/latest/embedded_graphics/index.html
## The `embedded-graphics` crate
### Implementing `DrawTarget`
```rust ,hl_lines=3
// in kernel/src/framebuffer.rs
use embedded_graphics::{
Pixel,
draw_target::DrawTarget,
geometry::{OriginDimensions, Size},
pixelcolor::{Rgb888, RgbColor},
};
pub struct Display<'f> {
framebuffer: &'f mut FrameBuffer,
}
impl<'f> Display<'f> {
pub fn new(framebuffer: &'f mut FrameBuffer) -> Display {
Display { framebuffer }
}
fn draw_pixel(&mut self, Pixel(coordinates, color): Pixel<Rgb888>) {
// ignore any out of bounds pixels
let (width, height) = {
let info = self.framebuffer.info();
(info.width, info.height)
};
let (x, y) = {
let c: (i32, i32) = coordinates.into();
(c.0 as usize, c.1 as usize)
};
if (0..width).contains(&x) && (0..height).contains(&y) {
let color = Color { red: color.r(), green: color.g(), blue: color.b() };
set_pixel_in(self.framebuffer, Position { x, y }, color);
}
}
}
impl<'f> DrawTarget for Display<'f> {
type Color = Rgb888;
/// Drawing operations can never fail.
type Error = core::convert::Infallible;
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Pixel<Self::Color>>,
{
for pixel in pixels.into_iter() {
self.draw_pixel(pixel);
}
Ok(())
}
}
impl<'f> OriginDimensions for Display<'f> {
fn size(&self) -> Size {
let info = self.framebuffer.info();
Size::new(info.width as u32, info.height as u32)
}
}
```
--- ---
@@ -89,7 +382,7 @@ Depending on the hardware and GPU firmware, it is often more efficient
draw shapes and pixels directly onto the framebuffer. That's fine and all, but how is one able to go from that to displaying text on the screen? Understanding this requires taking a deep dive into how characters are rendered behind the scenes. So far, we have drawn shapes and pixels directly onto the framebuffer. That's fine and all, but how is one able to go from that to displaying text on the screen? Understanding this requires taking a deep dive into how characters are rendered behind the scenes.
When a key is pressed on the keyboard, it sends a character code to the CPU. It's the CPU's job at that point to then interpret the character code and match it with an image to draw on the screen. The image is then sent to either the GPU or the framebuffer (the latter in our case) to be drawn on the screen, and the user sees that image as a letter, number, CJK character, emoji, or whatever else he or she wanted to have displayed by pressing that key. When a key is pressed on the keyboard, it sends a character code to the CPU. It's the CPU's job at that point to then interpret the character code and match it with an image to draw on the screen. The image is then sent to either the GPU or the framebuffer (the latter in our case) to be drawn on the screen, and the user sees that image as a letter, number, CJK character, emoji, or whatever else he or she wanted to have displayed by pressing that key.
@@ -179,7 +472,7 @@ fn kernel_main(boot_info: &'static mut bootloader_api::BootInfo) -> ! {
let frame_buffer_struct = frame_buffer_option.unwrap(); let frame_buffer_struct = frame_buffer_option.unwrap();
// extract the framebuffer info and, to satisfy the borrow checker, clone it // extract the framebuffer info and, to satisfy the borrow checker, clone it
let frame_buffer_info = frame_buffer.info().clone(); let frame_buffer_info = frame_buffer_struct.info().clone();
// get the framebuffer's mutable raw byte slice // get the framebuffer's mutable raw byte slice
let raw_frame_buffer = frame_buffer_struct.buffer_mut(); let raw_frame_buffer = frame_buffer_struct.buffer_mut();

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

View File

@@ -76,7 +76,7 @@ html {
--background-color: #fff; --background-color: #fff;
--text-color: #515151; --text-color: #515151;
--heading-color: #313131; --heading-color: #313131;
--heading-code-color: #a0565c; --heading-code-color: #313131;
--link-color: #268bd2; --link-color: #268bd2;
--hr-color-top: #eee; --hr-color-top: #eee;
--hr-color-bottom: #fff; --hr-color-bottom: #fff;
@@ -181,11 +181,11 @@ h6 {
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
} }
h1 { h1 {
font-size: 2rem; font-size: 2.25rem;
} }
h2 { h2 {
margin-top: 1rem; margin-top: 1rem;
font-size: 1.5rem; font-size: 1.75rem;
} }
h3 { h3 {
margin-top: 1.5rem; margin-top: 1.5rem;
@@ -196,6 +196,7 @@ h5,
h6 { h6 {
margin-top: 1rem; margin-top: 1rem;
font-size: 1rem; font-size: 1rem;
font-style: italic;
} }
/* Body text */ /* Body text */