VGA Graphics Mode Implementation Plan for AethelOS#
Overview#
Transform AethelOS from VGA text mode to graphics mode, enabling full Unicode support (including ◈) and setting the foundation for a GUI-capable operating system.
Architecture Decision: Dual-Mode Support#
Keep both text mode AND graphics mode:
- Text mode: Fast, simple, fallback
- Graphics mode: Unicode, beautiful, future-proof
- User can choose at boot or switch dynamically
Phase 1: Basic Graphics Infrastructure (1-2 days)#
1.1 Create Graphics Module Structure#
New files:
heartwood/src/weave_canvas/mod.rs- Main graphics module ("Weave Canvas" - thematic!)heartwood/src/weave_canvas/vga.rs- VGA hardware controlheartwood/src/weave_canvas/framebuffer.rs- Framebuffer abstractionheartwood/src/weave_canvas/mode.rs- Display mode management
1.2 Implement VGA Mode Switching#
- Mode 0x13 (320x200, 256 colors) - simplest to start
- Save/restore previous mode
- Detect available modes
- Mode enumeration and selection
Code to write:
pub enum DisplayMode {
Text80x25, // Current mode
Graphics320x200, // Mode 0x13
Graphics640x480, // Future: Mode 0x12
}
pub fn set_mode(mode: DisplayMode) -> Result<(), GraphicsError>
pub fn get_current_mode() -> DisplayMode
1.3 Framebuffer Access#
- Map VGA memory (0xA0000) safely
- Implement pixel drawing primitives
- Color palette management
Primitives to implement:
fn put_pixel(x: u16, y: u16, color: u8)
fn fill_rect(x: u16, y: u16, w: u16, h: u16, color: u8)
fn clear_screen(color: u8)
fn copy_region(src_x, src_y, dst_x, dst_y, w, h) // For scrolling
Estimated effort: 8-12 hours
Phase 2: Font Rendering System (2-3 days)#
2.1 Font Format - PSF (PC Screen Font)#
Decision: Use PSF2 format ✅
Why PSF:
- Standard Linux console font format
- Built-in Unicode mapping table
- Well-documented specification
- Many existing fonts available
- Supports custom glyphs (including ◈!)
Implementation approach:
- Embed a PSF2 font file in the kernel binary
- Parse PSF2 header at runtime
- Load glyph bitmaps and Unicode table
- Map UTF-8 characters → glyph indices
Recommended starter font:
- Terminus font (ter-u16n.psf or ter-u16b.psf)
- Clear, readable 8×16 bitmap font
- Excellent Unicode coverage
- ~50KB size
2.2 Create Rune Renderer (Font Module)#
New files:
heartwood/src/weave_canvas/runes/mod.rs- Font system ("Runes" - thematic!)heartwood/src/weave_canvas/runes/bitmap.rs- Bitmap fontsheartwood/src/weave_canvas/runes/psf.rs- PSF loader (Phase 2.5)
Core functionality:
pub struct RuneRenderer {
font_data: &'static [[u8; 16]; 256], // 8x16 bitmaps
char_width: u8,
char_height: u8,
}
impl RuneRenderer {
pub fn draw_char(&self, ch: char, x: u16, y: u16, fg: u8, bg: u8)
pub fn draw_string(&self, s: &str, x: u16, y: u16, fg: u8, bg: u8)
pub fn measure_string(&self, s: &str) -> u16 // Width in pixels
}
2.3 Design Custom ◈ Glyph#
- Create 8x16 bitmap for ◈
- Add to font data
- Test rendering
2.4 UTF-8 Support#
- Map Unicode codepoints to glyph indices
- Handle multi-byte UTF-8 sequences
- Fallback character for unmapped glyphs
Estimated effort: 12-16 hours
Phase 3: Terminal Emulation - "The Scribe" (3-4 days)#
3.1 Create Terminal Abstraction#
New file: heartwood/src/weave_canvas/scribe.rs ("The Scribe" writes to the canvas)
Core structure:
pub struct Scribe {
cursor_x: u16, // In characters
cursor_y: u16, // In characters
cols: u16, // Screen width in chars
rows: u16, // Screen height in chars
fg_color: u8, // Foreground color
bg_color: u8, // Background color
framebuffer: &'static mut [u8],
rune_renderer: RuneRenderer,
// For optimization
dirty: bool,
needs_scroll: bool,
}
3.2 Implement Core Terminal Operations#
impl Scribe {
pub fn write_char(&mut self, ch: char)
pub fn write_str(&mut self, s: &str)
fn newline(&mut self)
fn carriage_return(&mut self)
fn backspace(&mut self)
fn tab(&mut self)
fn scroll_up(&mut self) // Most complex operation!
fn clear_line(&mut self, line: u16)
fn clear_screen(&mut self)
pub fn set_cursor(&mut self, x: u16, y: u16)
pub fn get_cursor(&self) -> (u16, u16)
}
3.3 Handle Special Characters#
\n- Newline\r- Carriage return\x08- Backspace (erase previous char)\t- Tab (8-space alignment)- ANSI escape codes (future: colors, cursor movement)
3.4 Scrolling Implementation#
Challenge: Scrolling is expensive (must copy ~58KB)
Optimization strategies:
-
Naive approach: Copy entire framebuffer up
fn scroll_up_naive(&mut self) { let line_bytes = self.cols * CHAR_HEIGHT * SCREEN_WIDTH; unsafe { core::ptr::copy( self.framebuffer.as_ptr().add(line_bytes), self.framebuffer.as_mut_ptr(), (ROWS - 1) * line_bytes, ); } // Clear last line self.clear_line(self.rows - 1); } -
Optimization: Only redraw changed text (Phase 5)
Estimated effort: 20-24 hours
Phase 4: Integration with Existing Systems (2-3 days)#
4.1 Create Display Abstraction Layer#
New file: heartwood/src/display.rs
pub trait Display {
fn write_str(&mut self, s: &str);
fn write_char(&mut self, ch: char);
fn clear(&mut self);
fn set_color(&mut self, fg: Color, bg: Color);
}
// Implement for both:
impl Display for VgaTextWriter { ... }
impl Display for Scribe { ... }
4.2 Modify Print Macros#
Update print! and println! to work with either mode:
static DISPLAY_MODE: AtomicU8 = AtomicU8::new(DisplayMode::Text as u8);
static SCRIBE: Once<Mutex<Scribe>> = Once::new();
#[macro_export]
macro_rules! print {
($($arg:tt)*) => ({
match DISPLAY_MODE.load(Ordering::Relaxed) {
0 => $crate::vga_buffer::_print(...),
1 => $crate::weave_canvas::_print(...),
_ => {}
}
});
}
4.3 Update Eldarin Shell#
- Ensure all commands work in graphics mode
- Test command history navigation
- Test backspace
- Verify visual elements (progress bars, etc.)
4.4 Add Mode Switching Command#
fn cmd_display_mode(args: &str) {
match args {
"text" => switch_to_text_mode(),
"graphics" => switch_to_graphics_mode(),
"status" => show_current_mode(),
}
}
Estimated effort: 12-16 hours
Phase 5: Optimization & Polish (2-3 days)#
5.1 Double Buffering#
Problem: Flickering during redraws
Solution:
- Allocate second framebuffer in RAM
- Draw to back buffer
- Memcpy to VGA memory in one operation
struct DoubleBuffer {
front: &'static mut [u8], // VGA memory
back: Vec<u8>, // RAM buffer
}
impl DoubleBuffer {
fn swap(&mut self) {
self.front.copy_from_slice(&self.back);
}
}
5.2 Dirty Rectangle Tracking#
Problem: Redrawing entire screen is slow
Solution:
- Track which regions changed
- Only redraw dirty regions
struct DirtyRegion {
x: u16, y: u16,
width: u16, height: u16,
}
fn mark_dirty(&mut self, region: DirtyRegion)
fn redraw_dirty_regions(&mut self)
5.3 Scrolling Optimization#
Option 1: Ring buffer for lines
- Keep text in RAM
- Only redraw visible lines after scroll
- 10-100x faster
Option 2: Hardware scrolling
- Some VGA modes support hardware scrolling
- Research feasibility
5.4 Color Scheme#
Design AethelOS color palette:
- Background: Dark blue/purple (symbiotic theme)
- Text: Light cyan/white
- Highlights: Gold/amber
- Errors: Soft red
- Success: Soft green
Estimated effort: 16-20 hours
Phase 6: Advanced Features (Future)#
6.1 Higher Resolutions#
- Mode 0x12: 640x480, 16 colors
- VESA modes: 800x600, 1024x768 (requires VESA BIOS Extensions)
- Framebuffer support (UEFI GOP)
6.2 Better Font Rendering#
- Anti-aliased fonts
- Multiple font sizes
- Bold/italic variants
- Font loading from filesystem
6.3 Full Unicode Support#
- Unicode normalization
- Combining characters
- Right-to-left text
- CJK characters (complex!)
6.4 Graphics Primitives#
- Line drawing (Bresenham)
- Circle/ellipse
- Bezier curves
- Image loading (BMP, PPM)
6.5 Window System Foundation#
- Multiple overlapping windows
- Z-order management
- Event system
- Widget toolkit
File Structure#
heartwood/src/
├── weave_canvas/ # NEW: Graphics subsystem
│ ├── mod.rs # Main module, mode management
│ ├── vga.rs # VGA hardware control
│ ├── framebuffer.rs # Framebuffer abstraction
│ ├── mode.rs # Display mode enum/switching
│ ├── scribe.rs # Terminal emulation
│ └── runes/ # Font rendering
│ ├── mod.rs
│ ├── bitmap.rs # Hardcoded font data
│ └── psf.rs # PSF loader (later)
├── display.rs # NEW: Display trait abstraction
├── vga_buffer.rs # MODIFY: Implement Display trait
├── eldarin.rs # MODIFY: Add display-mode command
└── lib.rs # MODIFY: Initialize graphics
Testing Strategy#
Unit Tests#
- Pixel drawing functions
- Character rendering
- UTF-8 decoding
- Color conversion
Integration Tests#
- Mode switching
- Text rendering
- Scrolling
- Command execution
Manual Tests#
- Boot in graphics mode
- Run all Eldarin commands
- Test command history
- Test backspace
- Verify ◈ displays correctly
- Test long text output
- Test scrolling performance
Risk Mitigation#
Risk 1: Performance Issues#
Mitigation:
- Implement optimizations early
- Profile hotspots
- Keep text mode as fallback
Risk 2: Complexity Explosion#
Mitigation:
- Implement in small, testable phases
- Don't over-engineer early
- Get basic version working first
Risk 3: VGA Hardware Quirks#
Mitigation:
- Test on QEMU first
- Test on real hardware
- Research known issues
- Keep good documentation
Risk 4: Breaking Existing Functionality#
Mitigation:
- Keep text mode working
- Gradual migration
- Extensive testing
Timeline Estimate#
| Phase | Time | Cumulative |
|---|---|---|
| Phase 1: Graphics Infrastructure | 8-12 hours | 1.5 days |
| Phase 2: Font Rendering | 12-16 hours | 3.5 days |
| Phase 3: Terminal Emulation | 20-24 hours | 6.5 days |
| Phase 4: Integration | 12-16 hours | 8.5 days |
| Phase 5: Optimization | 16-20 hours | 11 days |
| Total (MVP) | 68-88 hours | 11-14 days |
Assumes focused work; real-time may be 2-3 weeks
Success Criteria#
Phase 1-3 (MVP):
- ✅ Can boot into graphics mode
- ✅ Eldarin shell works
- ✅ ◈ displays correctly
- ✅ All commands functional
- ✅ Command history works
- ✅ Acceptable performance
Phase 4-5 (Polish):
- ✅ No flickering
- ✅ Smooth scrolling
- ✅ Beautiful color scheme
- ✅ Can switch between text/graphics mode
Phase 6 (Future):
- ✅ Higher resolutions
- ✅ Full Unicode
- ✅ Foundation for GUI
Design Decisions#
Resolved Preferences#
-
Font choice: ✅ Use existing PSF (PC Screen Font) format
- Standard Linux console font format
- Supports Unicode mapping out of the box
- Well-documented and battle-tested
- Can start with a standard PSF2 font file
-
Default mode: ✅ Boot into graphics mode by default
- Graphics mode enabled at startup
- Text mode available as fallback (switchable via command or if graphics fails)
- Provides best user experience immediately
-
Compatibility: ✅ QEMU-only initially
- Focus on getting it working perfectly in QEMU first
- Real hardware support comes later
- Simplifies testing and debugging
-
Memory usage: ✅ 64KB for back buffer is acceptable
- Mode 0x13 (320x200) requires exactly 64,000 bytes
- Acceptable memory overhead for smooth rendering
- Eliminates flickering
Open Questions#
- Color palette: What specific colors best represent AethelOS's symbiotic aesthetic?
- Suggestion: Dark blue/purple background, cyan/white text, gold/amber highlights
- Needs testing and refinement
Implementation Roadmap#
Initialization Sequence (with defaults applied)#
// At boot (in main.rs or initialization)
fn init_display() -> Result<(), DisplayError> {
// Try to initialize graphics mode
match weave_canvas::init_graphics_mode() {
Ok(_) => {
println!("✓ Graphics mode initialized (320×200)");
println!(" Type 'display-mode text' to switch to text mode");
}
Err(e) => {
println!("⚠ Graphics mode failed, using text mode fallback");
println!(" Error: {:?}", e);
// Text mode already active, continue
}
}
Ok(())
}
Next Steps#
When ready to begin implementation:
-
Phase 1 - Basic Graphics Infrastructure
- Set up VGA mode 0x13 (320×200, 256 colors)
- Test pixel drawing in QEMU
-
Phase 2 - Font Rendering
- Embed Terminus PSF2 font (~50KB)
- Implement PSF2 parser
- Test rendering ASCII and ◈
-
Phase 3 - Terminal Emulation
- Build "The Scribe" terminal emulator
- Implement scrolling
- Test with Eldarin shell
-
Phase 4 - Integration
- Make graphics mode the default
- Add text mode fallback
- Add display-mode switching command
-
Phase 5 - Polish
- Implement double buffering
- Choose color palette
- Optimize scrolling
Development Guidelines#
- Test each phase thoroughly in QEMU before proceeding
- Keep text mode functional as fallback at all times
- Document all VGA register interactions
- Commit frequently with descriptive messages
- Measure performance at each stage