08. Text Rendering - Writing to the Screen¶
We can draw pixels, but for an OS, we need text. Let's implement a system to render characters and strings on our framebuffer.
How Text Rendering Works¶
Since we are in graphics mode, "text" is just a collection of pixels. To draw a letter 'A', we need to know which pixels to turn on.
Bitmap Fonts¶
A bitmap font stores each character as a grid of bits. - 1: Pixel on (foreground color) - 0: Pixel off (background color)
We'll use a simple 8x8 font. Each character is 8 pixels wide and 8 pixels high.
Example: The Letter 'A'¶
In C, this is an array of 8 bytes: {0x18, 0x3C, 0x66, 0x66, 0x7E, 0x66, 0x66, 0x00}.
Implementing the Font Driver¶
Font Data: drivers/font.c¶
We define a large array containing the bitmap data for ASCII characters (32-127).
Implementing the Console Driver¶
To make it useful, we need a console abstraction that handles:
- Cursor position (x, y)
- Newlines (\n)
- Scrolling when the screen is full
Console State¶
Drawing a Character¶
Scrolling¶
When cursor_y reaches the bottom, we need to scroll. To implement proper line-by-line scrolling, we need memmove to shift the framebuffer contents up. But since we compile with -nostdinc -nostdlib, we must implement it ourselves.
Kernel String/Memory Functions¶
Before implementing scrolling, we need basic memory manipulation functions. These are essential building blocks for any OS kernel.
Why Implement Our Own?¶
When compiling a bare-metal kernel with:
We have no access to the standard C library. Functions like memcpy, memset, and memmove simply don't exist. We must implement them ourselves.
include/string.h¶
kernel/string.c¶
Why memmove Instead of memcpy?¶
When scrolling the framebuffer, the source and destination regions overlap:
memcpy has undefined behavior when regions overlap. memmove handles this correctly by detecting the overlap direction and copying in the appropriate order.
Proper Scrolling Implementation¶
Now we can implement real scrolling in console.c:
This provides smooth scrolling behavior instead of the jarring screen clear.
Building and Testing¶
1. Build¶
2. Deploy¶
Copy text_demo.img to SD card (rename to kernel8.img).
3. Expected Output¶
You should see: - "Raspberry Pi OS - Text Mode" - A list of all ASCII characters - Any text you type in the serial terminal will appear on the screen!
Integrating Console with the Kernel¶
When building a full-featured kernel, we face a timing challenge: the framebuffer and console are not available during early boot. The UART is initialized first and can output messages immediately, but the framebuffer requires mailbox communication with the GPU, which takes time.
The Problem¶
If we try to write to the console before it's initialized, the system will crash. But we want boot messages to appear on both UART (for debugging) and the screen (for users without serial cables).
Solution: Dual Output with kprint¶
We introduce a kprint function that outputs to both UART and console (when available):
Replaying Boot Messages¶
After the console is initialized, we replay the boot messages so they appear on screen:
Preserving Boot Messages¶
In console_init(), we avoid clearing the screen so any early graphics are preserved:
This pattern is common in real operating systems. Linux, for example, uses an "early console" for boot messages and switches to a proper framebuffer console later.
Performance Note¶
Drawing pixel-by-pixel is slow. Real OSes use: - Hardware Acceleration: GPU blitting - Optimized Assembly: Copying 64 bits at a time - Double Buffering: To prevent flickering
Complete Source Code¶
- include/string.h - String/memory function declarations
- kernel/string.c - String/memory function implementations
- include/font.h
- drivers/font.c
- include/console.h
- drivers/console.c
- examples/08_text_demo.c
What's Next?¶
We have a working display! Now we can start building the core OS features:
- Memory Management: Allocating memory dynamically (malloc)
- Virtual Memory: Using the MMU for process isolation
The next article covers Memory Allocation.