Compare commits

...

4 Commits

Author SHA1 Message Date
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
4 changed files with 222 additions and 14 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
### `nm`
### List Symbols using `nm`
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:
@@ -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.
### `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.

View File

@@ -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`:
[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
rust-objdump -h target/x86_64-unknown-none/debug/kernel
@@ -1047,7 +1047,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:
```rust ,hl_lines=3 7-13
// in src/kernel/main.rs
// in kernel/src/main.rs
use bootloader_api::BootInfo;
@@ -1083,7 +1083,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:
```rust ,hl_lines=5-7
// in src/kernel/main.rs
// in kernel/src/main.rs
fn kernel_main(boot_info: &'static mut BootInfo) -> ! {
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:
[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
// in src/kernel/main.rs
// in kernel/src/main.rs
fn kernel_main(boot_info: &'static mut BootInfo) -> ! {
if let Some(framebuffer) = boot_info.framebuffer.as_mut() {
@@ -65,22 +65,229 @@ 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:
<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>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>
</tbody></table>
So top left pixel is stored at offset 0 in the bitmap array.
The pixel on its right is at offset `pixel_size`.
The first pixel of the next line starts at offset `line_length * pixel_size`.
The pixel on its right is at pixel offset 1.
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
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
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;
```
```rust ,hl_lines=3-16
// in new kernel/src/framebuffer.rs file
pub struct Position {
pub x: usize,
pub y: usize,
}
pub struct Color {
pub red: u8,
pub green: u8,
pub blue: u8,
}
pub fn set_pixel_in(framebuffer: &mut FrameBuffer, position: Position, color: Color) {
todo!()
}
```
TODO explain
```rust ,hl_lines=4-12 14-34
// in new kernel/src/framebuffer.rs file
pub fn set_pixel_in(framebuffer: &mut FrameBuffer, position: Position, color: Color) {
// calculate offset to first byte of pixel
let byte_offset = {
// use stride to calculate pixel offset of target line
let line_offset = position.y * framebuffer.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 * framebuffer.bytes_per_pixel
};
/// set pixel based on color format
match framebuffer.info.pixel_format {
PixelFormat::Rgb => {
let bytes = &mut framebuffer.buffer_mut()[byte_offset..][..3];
bytes[0] = color.red;
bytes[1] = color.green;
bytes[2] = color.blue;
}
PixelFormat::Bgr => {
let bytes = &mut framebuffer.buffer_mut()[byte_offset..][..3];
bytes[0] = color.blue;
bytes[1] = color.green;
bytes[2] = color.red;
}
PixelFormat::U8 => {
// use a simple average-based grayscale transform
let gray = color.red / 3 + color.green / 3 + color.blue / 3;
framebuffer.buffer_mut()[byte_offset] = gray;
}
other => panic!("unknown pixel format {other:?}"),
}
}
```
TODO explain
Let's try our new function:
```rust ,hl_lines=5-7
// 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: 200, y: 100 };
let color = framebuffer::Color { red: 0, green: 0, blue: 255 };
set_pixel_in(framebuffer, position, color);
}
loop {}
}
```
Of course a single pixel is difficult to see, so let's set a square of 10 pixels:
```rust ,hl_lines=6-11
// 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..10 {
for y in 0..10 {
let position = framebuffer::Position { x: 200 + x, y: 100 + y};
set_pixel_in(framebuffer, position, color);
}
}
}
loop {}
}
```
Now we modifications more easily: TODO image
## The `embedded-graphics` crate
### Implementing `DrawTarget`
```rust ,hl_lines=3
// in kernel/src/framebuffer.rs
pub struct Display {
framebuffer: Framebuffer,
}
impl Display {
pub fn new(framebuffer: Framebuffer) -> Display {
Self { framebuffer }
}
fn draw_pixel(&mut self, pixel: Pixel) {
// ignore any pixels that are out of bounds.
let (width, height) = {
let info = self.framebuffer.info();
(info.width, info.height)
}
if let Ok((x @ 0..width, y @ 0..height)) = coordinates.try_into() {
let color = Color { red: color.r(), green: color.g(), blue: color.b()};
set_pixel_in(&mut self.framebuffer, Position { x, y }, color);
}
}
}
impl embedded_graphics::draw_target::DrawTarget for Display {
type Color = embedded_graphics::pixelcolor::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(coordinates, color) in pixels.into_iter() {
self.draw_pixel(pixel);
}
Ok(())
}
}
```
---

View File

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