Project 14: Graphics Mode Bootloader with Logo
Build a bootloader that switches to a graphical VESA/VBE mode, displays a boot logo, shows a progress bar during kernel loading, and passes framebuffer info to the kernel.
Quick Reference
| Attribute | Value |
|---|---|
| Difficulty | ★★★☆☆ Advanced |
| Time Estimate | 1-2 weeks |
| Language | x86 Assembly + C (alt: Rust, Pure Assembly) |
| Prerequisites | Project 4 (Two-Stage Bootloader), basic understanding of graphics concepts, Protected Mode knowledge |
| Key Topics | VESA BIOS Extensions (VBE), linear framebuffer, BMP parsing, pixel manipulation, boot info structures |
1. Learning Objectives
By completing this project, you will:
-
Master VBE Mode Switching: Learn how to query available video modes, select appropriate resolutions, and switch from text mode to graphics mode using VESA BIOS Extensions.
-
Understand Linear Framebuffers: Work directly with video memory, understanding pixel formats (RGB, BGR, 16-bit, 32-bit) and memory layout.
-
Implement Image Rendering: Parse BMP file format and render images pixel-by-pixel to the framebuffer.
-
Build Real-Time Graphics: Create animated elements (progress bar) that update during boot operations.
-
Design Boot Information Structures: Pass framebuffer information to the kernel so it can continue graphics without mode switching.
-
Bridge BIOS and Modern Graphics: Understand how bootloaders provide graphics before GPU drivers exist.
2. Theoretical Foundation
2.1 Core Concepts
Video Mode History and VBE
The x86 graphics evolution explains why VBE exists:
Graphics Mode Evolution on x86:
┌─────────────────────────────────────────────────────────────────────────────┐
│ VIDEO STANDARDS TIMELINE │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 1981 1987 1990 1998 2007 Today │
│ │ │ │ │ │ │ │
│ ▼ ▼ ▼ ▼ ▼ ▼ │
│ ┌───┐ ┌───┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │CGA│ │VGA│ │SVGA │ │VBE │ │UEFI │ │GPU │ │
│ │ │──────│ │──────│ │────│2.0 │────│GOP │────│Driver│ │
│ └───┘ └───┘ └──────┘ └──────┘ └──────┘ └──────┘ │
│ │ │ │ │ │ │ │
│ 4 colors 256 colors Vendor Standard UEFI Full 3D │
│ 320x200 320x200 specific interface native acceleration │
│ 640x480 modes for SVGA graphics │
│ │
│ REAL MODE ──────────────────────────────────── PROTECTED MODE ──────────── │
│ (BIOS INT 10h works) (Need drivers or VBE) │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
The Problem VBE Solves:
- Each graphics card had proprietary mode-setting
- Software needed card-specific code for each vendor
- VBE provides a standard interface that works across vendors
- Bootloaders can set high resolutions without knowing the GPU
VBE Architecture
VBE System Architecture:
┌─────────────────────────────────────────────────────────────────────────────┐
│ VBE ARCHITECTURE │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ YOUR BOOTLOADER │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 1. Call VBE functions via INT 10h, AX=4Fxxh │ │
│ │ 2. Parse returned information │ │
│ │ 3. Switch to desired mode │ │
│ │ 4. Write pixels to linear framebuffer │ │
│ └──────────────────────────────┬──────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ BIOS INT 10h │ │
│ │ │ │
│ │ Standard VGA Functions (AX < 4F00h): │ │
│ │ AH=00h Set video mode (VGA modes 00h-13h) │ │
│ │ AH=0Eh Write character in teletype mode │ │
│ │ │ │
│ │ VBE Functions (AX=4Fxxh): │ │
│ │ 4F00h Get VBE Controller Info │ │
│ │ 4F01h Get Mode Info │ │
│ │ 4F02h Set VBE Mode │ │
│ │ 4F03h Get Current Mode │ │
│ │ 4F04h Save/Restore State │ │
│ │ 4F05h Display Window Control (bank switching) │ │
│ │ 4F06h Set/Get Logical Scan Line Length │ │
│ │ 4F07h Set/Get Display Start │ │
│ │ 4F08h Set/Get DAC Palette Control │ │
│ │ 4F09h Set/Get Palette Data │ │
│ │ 4F0Ah Get VBE Protected Mode Interface │ │
│ │ │ │
│ └──────────────────────────────┬──────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ VIDEO BIOS (VGA/VBE ROM) │ │
│ │ │ │
│ │ Located at: 0xC0000 - 0xC7FFF (32KB) │ │
│ │ Contains: │ │
│ │ - VGA compatibility routines │ │
│ │ - VBE implementation (vendor-specific) │ │
│ │ - Mode tables and timing data │ │
│ │ - Hardware initialization code │ │
│ │ │ │
│ └──────────────────────────────┬──────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ GPU HARDWARE │ │
│ │ │ │
│ │ ┌─────────────────────┐ ┌─────────────────────────────────────┐ │ │
│ │ │ Control Registers │ │ Video RAM (Framebuffer) │ │ │
│ │ │ │ │ │ │ │
│ │ │ - Mode selection │ │ Linear: 0xFD000000 (example) │ │ │
│ │ │ - Timing control │ │ Size: Width x Height x BytesPerPix │ │ │
│ │ │ - Palette (indexed)│ │ │ │ │
│ │ └─────────────────────┘ │ Each pixel: B G R [A] (32bpp) │ │ │
│ │ │ or R5 G6 B5 (16bpp) │ │ │
│ │ └─────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌─────────────────────┐ │ │
│ │ │ DISPLAY │ │ │
│ │ └─────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
VBE Function Details
VBE Function Reference (INT 10h):
┌─────────────────────────────────────────────────────────────────────────────┐
│ VBE 4F00h: GET CONTROLLER INFO │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Input: AX = 4F00h │
│ ES:DI = Pointer to 512-byte buffer │
│ Buffer[0-3] = "VBE2" to request VBE 2.0+ info │
│ │
│ Output: AX = Status (4Fh = supported, else error) │
│ Buffer filled with VbeInfoBlock │
│ │
│ VbeInfoBlock structure: │
│ ┌────────┬────────────────────────────────────────────────────────────┐ │
│ │ Offset │ Description │ │
│ ├────────┼────────────────────────────────────────────────────────────┤ │
│ │ 0 │ Signature: "VESA" (VBE 1.x) or "VBE2" (VBE 2.0+) │ │
│ │ 4 │ VBE Version (BCD, e.g., 0x0300 = 3.0) │ │
│ │ 6 │ OEM String Pointer (SEG:OFF) │ │
│ │ 10 │ Capabilities (bit flags) │ │
│ │ 14 │ Video Mode List Pointer (SEG:OFF) - list of WORDs, FFFFh end│ │
│ │ 18 │ Total Memory (64KB blocks) │ │
│ │ 20 │ OEM Software Revision │ │
│ │ 22 │ OEM Vendor Name Pointer │ │
│ │ 26 │ OEM Product Name Pointer │ │
│ │ 30 │ OEM Product Revision Pointer │ │
│ │ 34-255 │ Reserved │ │
│ │ 256-511│ OEM Data Area │ │
│ └────────┴────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ VBE 4F01h: GET MODE INFO │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Input: AX = 4F01h │
│ CX = Mode number │
│ ES:DI = Pointer to 256-byte ModeInfoBlock │
│ │
│ Output: AX = Status │
│ Buffer filled with mode information │
│ │
│ ModeInfoBlock structure (key fields): │
│ ┌────────┬────────────────────────────────────────────────────────────┐ │
│ │ Offset │ Description │ │
│ ├────────┼────────────────────────────────────────────────────────────┤ │
│ │ 0 │ Mode Attributes (bit 7 = LFB available) │ │
│ │ 18 │ X Resolution │ │
│ │ 20 │ Y Resolution │ │
│ │ 25 │ Bits Per Pixel │ │
│ │ 40 │ Physical Base Address of LFB (32-bit) │ │
│ │ 50 │ Bytes Per Scan Line │ │
│ │ 31 │ Red Mask Size │ │
│ │ 32 │ Red Field Position │ │
│ │ 33 │ Green Mask Size │ │
│ │ 34 │ Green Field Position │ │
│ │ 35 │ Blue Mask Size │ │
│ │ 36 │ Blue Field Position │ │
│ └────────┴────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ VBE 4F02h: SET VBE MODE │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Input: AX = 4F02h │
│ BX = Mode number with flags: │
│ Bits 0-13: Mode number from mode list │
│ Bit 14: Use Linear Frame Buffer (LFB) if set │
│ Bit 15: Don't clear display memory if set │
│ │
│ Output: AX = Status │
│ │
│ Example: Set mode 0x118 (1024x768x32) with LFB: │
│ MOV AX, 4F02h │
│ MOV BX, 4118h ; 0x4000 | 0x118 = LFB + mode 118 │
│ INT 10h │
│ │
│ IMPORTANT: After this call, INT 10h text functions no longer work! │
│ You must write directly to the framebuffer. │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Framebuffer Memory Layout
Linear Framebuffer Pixel Layout:
┌─────────────────────────────────────────────────────────────────────────────┐
│ FRAMEBUFFER ORGANIZATION │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ MEMORY LAYOUT (1024x768, 32bpp): │
│ │
│ Physical Address: 0xFD000000 (example, from ModeInfoBlock offset 40) │
│ Size: 1024 * 768 * 4 = 3,145,728 bytes (3MB) │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Row 0: Pixel(0,0) | Pixel(1,0) | ... | Pixel(1023,0) │ │
│ │ 4 bytes 4 bytes 4 bytes │ │
│ ├─────────────────────────────────────────────────────────────────────┤ │
│ │ Row 1: Pixel(0,1) | Pixel(1,1) | ... | Pixel(1023,1) │ │
│ ├─────────────────────────────────────────────────────────────────────┤ │
│ │ ... │ │
│ ├─────────────────────────────────────────────────────────────────────┤ │
│ │ Row 767: Pixel(0,767) | ... | Pixel(1023,767) │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ PIXEL FORMAT (32bpp BGRA, most common): │
│ │
│ Byte 0 Byte 1 Byte 2 Byte 3 │
│ ┌────────┬────────┬────────┬────────┐ │
│ │ Blue │ Green │ Red │ Alpha │ (or reserved) │
│ │ 0-255 │ 0-255 │ 0-255 │ 0-255 │ │
│ └────────┴────────┴────────┴────────┘ │
│ │
│ Offset Calculation: │
│ offset = (y * pitch) + (x * bytes_per_pixel) │
│ │
│ where: pitch = bytes per scan line (may include padding) │
│ bytes_per_pixel = bpp / 8 │
│ │
│ Example: Pixel at (100, 50) in 1024x768x32: │
│ pitch = 1024 * 4 = 4096 (assuming no padding) │
│ offset = (50 * 4096) + (100 * 4) = 204,800 + 400 = 205,200 │
│ address = 0xFD000000 + 205,200 = 0xFD032190 │
│ │
│ COMMON PIXEL FORMATS: │
│ ┌───────────┬─────────────────────────────────────────────────────────┐ │
│ │ BPP │ Format │ │
│ ├───────────┼─────────────────────────────────────────────────────────┤ │
│ │ 8 │ Indexed color (palette lookup) │ │
│ │ 15 │ X1R5G5B5 (1 bit unused, 5 bits each color) │ │
│ │ 16 │ R5G6B5 (5-6-5 bits) │ │
│ │ 24 │ B8G8R8 (3 bytes per pixel) │ │
│ │ 32 │ B8G8R8A8 or B8G8R8X8 (4 bytes, alpha or unused) │ │
│ └───────────┴─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
BMP File Format
BMP File Structure:
┌─────────────────────────────────────────────────────────────────────────────┐
│ BMP FILE FORMAT │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ BITMAPFILEHEADER (14 bytes) │ │
│ ├──────────────────────────────────────────────────────────────────────┤ │
│ │ Offset │ Size │ Field │ Description │ │
│ ├────────┼──────┼────────────────┼──────────────────────────────────────┤ │
│ │ 0 │ 2 │ bfType │ "BM" (0x4D42) │ │
│ │ 2 │ 4 │ bfSize │ Total file size in bytes │ │
│ │ 6 │ 2 │ bfReserved1 │ Reserved (0) │ │
│ │ 8 │ 2 │ bfReserved2 │ Reserved (0) │ │
│ │ 10 │ 4 │ bfOffBits │ Offset to pixel data │ │
│ └────────┴──────┴────────────────┴──────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ BITMAPINFOHEADER (40 bytes) │ │
│ ├──────────────────────────────────────────────────────────────────────┤ │
│ │ Offset │ Size │ Field │ Description │ │
│ ├────────┼──────┼────────────────┼──────────────────────────────────────┤ │
│ │ 14 │ 4 │ biSize │ Header size (40 for BITMAPINFOHEADER)│ │
│ │ 18 │ 4 │ biWidth │ Image width in pixels │ │
│ │ 22 │ 4 │ biHeight │ Image height (negative = top-down) │ │
│ │ 26 │ 2 │ biPlanes │ Color planes (must be 1) │ │
│ │ 28 │ 2 │ biBitCount │ Bits per pixel (24 or 32 typical) │ │
│ │ 30 │ 4 │ biCompression │ 0=BI_RGB (uncompressed) │ │
│ │ 34 │ 4 │ biSizeImage │ Image size (can be 0 for BI_RGB) │ │
│ │ 38 │ 4 │ biXPelsPerMeter│ Horizontal resolution │ │
│ │ 42 │ 4 │ biYPelsPerMeter│ Vertical resolution │ │
│ │ 46 │ 4 │ biClrUsed │ Colors in palette (0 = max) │ │
│ │ 50 │ 4 │ biClrImportant │ Important colors │ │
│ └────────┴──────┴────────────────┴──────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ PIXEL DATA │ │
│ ├──────────────────────────────────────────────────────────────────────┤ │
│ │ │ │
│ │ IMPORTANT: BMP stores pixels BOTTOM-UP by default! │ │
│ │ │ │
│ │ Memory Order: Screen Display: │ │
│ │ ┌───────────────┐ ┌───────────────┐ │ │
│ │ │ Row N-1 │ │ Row 0 │ ← Top of image │ │
│ │ │ Row N-2 │ │ Row 1 │ │ │
│ │ │ ... │ │ ... │ │ │
│ │ │ Row 1 │ │ Row N-2 │ │ │
│ │ │ Row 0 │ │ Row N-1 │ ← Bottom of image │ │
│ │ └───────────────┘ └───────────────┘ │ │
│ │ │ │
│ │ Each row is padded to 4-byte boundary! │ │
│ │ Row size = ((width * bpp + 31) / 32) * 4 │ │
│ │ │ │
│ │ 24-bit pixel order: B, G, R (3 bytes) │ │
│ │ 32-bit pixel order: B, G, R, A (4 bytes) │ │
│ │ │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
│ │
│ EXAMPLE: Reading a 24-bit BMP │
│ ───────────────────────────────────────────────────────────────────────── │
│ 1. Read bfOffBits to find pixel data start │
│ 2. Read biWidth, biHeight for dimensions │
│ 3. Calculate row padding: (4 - (width * 3) % 4) % 4 │
│ 4. For each row from (height-1) down to 0: │
│ For each column from 0 to (width-1): │
│ Read B, G, R bytes │
│ Write to framebuffer at (column, height-1-row) │
│ Skip padding bytes │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
2.2 Why This Matters
The Professional Boot Experience
Every commercial OS presents a graphical boot:
Boot Screen Comparison:
┌─────────────────────────────────────────────────────────────────────────────┐
│ COMMERCIAL BOOT SCREENS │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Windows Boot: macOS Boot: │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ │ │ │ │
│ │ [Windows Logo] │ │ [Apple Logo] │ │
│ │ │ │ │ │
│ │ [●●●●●○○○○○] │ │ [Progress Bar] │ │
│ │ │ │ │ │
│ └─────────────────────┘ └─────────────────────┘ │
│ │
│ Linux (Plymouth): Chrome OS: │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ │ │ │ │
│ │ [Distro Logo] │ │ [Chrome Logo] │ │
│ │ │ │ │ │
│ │ [Spinner/Bar] │ │ [Progress Dots] │ │
│ │ │ │ │ │
│ └─────────────────────┘ └─────────────────────┘ │
│ │
│ WHY GRAPHICS AT BOOT? │
│ ───────────────────────────────────────────────────────────────────────── │
│ • User perception: System "feels" faster with visual feedback │
│ • Branding: Company logo reinforces product identity │
│ • Hiding complexity: Users don't need to see BIOS/kernel messages │
│ • Progress indication: Shows system is working, not frozen │
│ • Smooth transition: No jarring text-to-graphics mode switch │
│ │
│ HOW IT'S IMPLEMENTED: │
│ ───────────────────────────────────────────────────────────────────────── │
│ • Bootloader (GRUB, Windows Boot Manager) sets graphics mode │
│ • Framebuffer info passed to kernel │
│ • Kernel uses framebuffer until GPU driver loads │
│ • Smooth handoff from bootloader graphics to kernel graphics │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
2.3 Historical Context
From Text to Graphics
- 1981-1987: CGA/EGA era - text mode dominant, graphics mode limited
- 1987: VGA introduces 320x200x256 and 640x480x16
- 1989: VESA formed to standardize SVGA
- 1994: VBE 1.2 - first widely adopted standard
- 1998: VBE 2.0 - adds linear framebuffer
- 2000: VBE 3.0 - adds refresh rate control
- 2007: UEFI GOP begins replacing VBE
- Today: UEFI GOP preferred, VBE for BIOS compatibility
2.4 Common Misconceptions
- “You need GPU drivers for graphics in bootloader”
- Reality: VBE provides basic graphics without drivers
- The video BIOS handles hardware specifics
- “VBE is obsolete”
- Reality: UEFI uses GOP, but VBE still works on BIOS systems
- Many systems still have CSM (Compatibility Support Module)
- “32-bit color means 32 bits of color”
- Reality: Usually 24-bit color (8 bits per R/G/B) + 8-bit padding or alpha
- The extra byte is for alignment, not more colors
- “BMP files are simple”
- Reality: Multiple header versions, compression options, color formats
- Start with uncompressed 24-bit BMP for simplicity
- “Mode numbers are standardized”
- Reality: Only some modes have standard numbers (e.g., 0x118 = 1024x768x32)
- Always query available modes, don’t hardcode
3. Project Specification
3.1 What You Will Build
A bootloader that:
- Queries VBE for available video modes
- Selects appropriate mode (e.g., 1024x768x32bpp)
- Switches to graphics mode with linear framebuffer
- Loads and displays a BMP logo image centered on screen
- Shows an animated progress bar during kernel loading
- Passes framebuffer info to kernel for continued graphics
3.2 Functional Requirements
| ID | Requirement | Priority |
|---|---|---|
| FR1 | Query VBE controller info and verify VBE support | Must Have |
| FR2 | Enumerate available video modes | Must Have |
| FR3 | Find suitable mode (prefer 32bpp, 1024x768 or higher) | Must Have |
| FR4 | Switch to graphics mode with linear framebuffer | Must Have |
| FR5 | Clear screen to solid background color | Must Have |
| FR6 | Load BMP file from disk | Must Have |
| FR7 | Parse BMP header and extract pixel data | Must Have |
| FR8 | Render BMP image centered on screen | Must Have |
| FR9 | Draw progress bar below logo | Should Have |
| FR10 | Animate progress bar during kernel load | Should Have |
| FR11 | Build boot info structure with framebuffer details | Must Have |
| FR12 | Jump to kernel with boot info pointer | Must Have |
3.3 Non-Functional Requirements
| ID | Requirement | Target |
|---|---|---|
| NFR1 | Support 800x600, 1024x768, 1280x1024 resolutions | Should Have |
| NFR2 | Support 16bpp, 24bpp, and 32bpp color depths | Should Have |
| NFR3 | Progress bar updates at least 10 times per second | Should Have |
| NFR4 | Total graphics code < 16KB | Should Have |
| NFR5 | Logo displays within 500ms of mode switch | Must Have |
3.4 Example Usage / Output
# QEMU window shows:
┌─────────────────────────────────────────────────────────────────────────────┐
│ │
│ │
│ ╔══════════════════════╗ │
│ ║ ║ │
│ ║ ████ ███ ███ ║ │
│ ║ █ █ █ █ █ █ ║ │
│ ║ █ █ ███ ████ ║ │
│ ║ █ █ █ █ █ ║ │
│ ║ ████ ███ ███ ║ │
│ ║ ║ │
│ ║ Your Custom Logo ║ │
│ ║ ║ │
│ ╚══════════════════════╝ │
│ │
│ Loading... │
│ │
│ ┌──────────────────────────────┐ │
│ │████████████████░░░░░░░░░░░░░░│ 60% │
│ └──────────────────────────────┘ │
│ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
# Serial output shows:
Initializing VBE...
VBE Version: 3.0
OEM: VESA VBE Adapter
Video Memory: 16 MB
Available modes:
0x112: 640x480x32
0x115: 800x600x32
0x118: 1024x768x32 <- Selected
0x11B: 1280x1024x32
Switching to mode 0x118...
Framebuffer: 0xFD000000
Pitch: 4096 bytes
Loading logo.bmp...
Size: 256x128
Bits: 24
Drawing logo at (384, 320)...
Drawing progress bar at (312, 520)...
Loading kernel.bin...
[=====================================] 100%
Jumping to kernel at 0x100000...
[Kernel continues with framebuffer graphics]
3.5 Real World Outcome
You will have built the same technology that powers:
- Windows boot logo (bootmgr graphics)
- Linux Plymouth boot splash
- GRUB graphical themes
- macOS boot screen
- Any “polished” boot experience
4. Solution Architecture
4.1 High-Level Design
Graphics Bootloader Architecture:
┌─────────────────────────────────────────────────────────────────────────────┐
│ GRAPHICS BOOTLOADER ARCHITECTURE │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ STAGE 1 (MBR - 512 bytes) │ │
│ ├─────────────────────────────────────────────────────────────────────┤ │
│ │ 1. Initialize stack and segments │ │
│ │ 2. Load Stage 2 from disk │ │
│ │ 3. Jump to Stage 2 │ │
│ └──────────────────────────────────┬──────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ STAGE 2 (Graphics Boot) │ │
│ ├─────────────────────────────────────────────────────────────────────┤ │
│ │ │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────────┐ │ │
│ │ │ VBE Module │ │ BMP Parser │ │ Graphics Primitives │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ -Get info │ │ -Read file │ │ -Clear screen │ │ │
│ │ │ -Enum modes │ │ -Parse hdr │ │ -Draw rect │ │ │
│ │ │ -Set mode │ │ -Flip image │ │ -Draw image │ │ │
│ │ │ -Get FB addr│ │ -Convert px │ │ -Update progress │ │ │
│ │ └──────┬──────┘ └──────┬──────┘ └──────┬──────────────────────┘ │ │
│ │ │ │ │ │ │
│ │ └────────────────┬┴─────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌─────────────────────────────────────────────────────────────────┐ │ │
│ │ │ FRAMEBUFFER ACCESS │ │ │
│ │ │ │ │ │
│ │ │ Physical Address: ModeInfoBlock.PhysBasePtr (e.g., 0xFD000000) │ │ │
│ │ │ │ │ │
│ │ │ In Real Mode: Need to access via segment:offset (A20, unreal) │ │ │
│ │ │ In Protected Mode: Direct 32-bit address (identity mapped) │ │ │
│ │ │ │ │ │
│ │ │ Pixel Write: │ │ │
│ │ │ offset = y * pitch + x * (bpp/8) │ │ │
│ │ │ framebuffer[offset+0] = Blue │ │ │
│ │ │ framebuffer[offset+1] = Green │ │ │
│ │ │ framebuffer[offset+2] = Red │ │ │
│ │ │ │ │ │
│ │ └─────────────────────────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ └──────────────────────────┼─────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ BOOT INFO STRUCTURE │ │
│ ├─────────────────────────────────────────────────────────────────────┤ │
│ │ │ │
│ │ struct boot_info { │ │
│ │ uint32_t magic; // Identifier │ │
│ │ uint32_t fb_addr; // Framebuffer physical address │ │
│ │ uint32_t fb_width; // Width in pixels │ │
│ │ uint32_t fb_height; // Height in pixels │ │
│ │ uint32_t fb_pitch; // Bytes per scanline │ │
│ │ uint32_t fb_bpp; // Bits per pixel │ │
│ │ uint32_t fb_red_mask; // Red mask position │ │
│ │ uint32_t fb_green_mask; // Green mask position │ │
│ │ uint32_t fb_blue_mask; // Blue mask position │ │
│ │ // ... memory map, etc. │ │
│ │ }; │ │
│ │ │ │
│ └──────────────────────────────────┬──────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ KERNEL │ │
│ │ │ │
│ │ Receives boot_info pointer in register (e.g., EBX) │ │
│ │ Can continue drawing to framebuffer immediately │ │
│ │ No mode switch needed - smooth visual transition │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
4.2 Key Components
- VBE Controller: Queries and sets video modes using INT 10h
- Mode Selector: Finds best available mode matching preferences
- Framebuffer Writer: Low-level pixel writing functions
- BMP Loader: Parses BMP headers and extracts pixel data
- Image Renderer: Draws BMP to framebuffer with correct orientation
- Progress Bar: Animated rectangle that updates during loading
- Boot Info Builder: Creates structure for kernel handoff
4.3 Data Structures
/* VBE Controller Info Block (512 bytes) */
typedef struct {
uint8_t signature[4]; /* "VESA" or "VBE2" */
uint16_t version; /* BCD version (e.g., 0x0300 = 3.0) */
uint32_t oem_string_ptr; /* Far pointer to OEM string */
uint32_t capabilities; /* Capability flags */
uint32_t video_mode_ptr; /* Far pointer to mode list */
uint16_t total_memory; /* Video memory in 64KB blocks */
uint16_t oem_software_rev; /* OEM software revision */
uint32_t oem_vendor_ptr; /* Far pointer to vendor name */
uint32_t oem_product_ptr; /* Far pointer to product name */
uint32_t oem_product_rev_ptr; /* Far pointer to product revision */
uint8_t reserved[222]; /* Reserved */
uint8_t oem_data[256]; /* OEM scratch area */
} __attribute__((packed)) vbe_info_t;
/* VBE Mode Info Block (256 bytes) */
typedef struct {
/* Mandatory VBE 1.x fields */
uint16_t mode_attributes; /* Mode attributes (bit 7 = LFB) */
uint8_t win_a_attributes; /* Window A attributes */
uint8_t win_b_attributes; /* Window B attributes */
uint16_t win_granularity; /* Window granularity in KB */
uint16_t win_size; /* Window size in KB */
uint16_t win_a_segment; /* Window A segment */
uint16_t win_b_segment; /* Window B segment */
uint32_t win_func_ptr; /* Far pointer to window function */
uint16_t bytes_per_scanline; /* Bytes per scan line */
/* VBE 1.2+ fields */
uint16_t x_resolution; /* Horizontal resolution */
uint16_t y_resolution; /* Vertical resolution */
uint8_t x_char_size; /* Character cell width */
uint8_t y_char_size; /* Character cell height */
uint8_t num_planes; /* Number of memory planes */
uint8_t bits_per_pixel; /* Bits per pixel */
uint8_t num_banks; /* Number of banks */
uint8_t memory_model; /* Memory model type */
uint8_t bank_size; /* Bank size in KB */
uint8_t num_image_pages; /* Number of image pages */
uint8_t reserved1; /* Reserved */
/* Direct color fields */
uint8_t red_mask_size; /* Size of red mask in bits */
uint8_t red_field_pos; /* Bit position of red mask LSB */
uint8_t green_mask_size; /* Size of green mask */
uint8_t green_field_pos; /* Bit position of green mask LSB */
uint8_t blue_mask_size; /* Size of blue mask */
uint8_t blue_field_pos; /* Bit position of blue mask LSB */
uint8_t reserved_mask_size; /* Size of reserved mask */
uint8_t reserved_field_pos; /* Bit position of reserved mask */
uint8_t direct_color_info; /* Direct color mode attributes */
/* VBE 2.0+ fields */
uint32_t phys_base_ptr; /* Physical address of LFB */
uint32_t reserved2; /* Reserved */
uint16_t reserved3; /* Reserved */
/* VBE 3.0+ fields */
uint16_t lin_bytes_per_scanline; /* Linear bytes per scan line */
uint8_t bank_num_image_pages; /* Bank mode image pages */
uint8_t lin_num_image_pages; /* Linear mode image pages */
uint8_t lin_red_mask_size; /* Linear mode red mask size */
uint8_t lin_red_field_pos; /* Linear mode red field position */
uint8_t lin_green_mask_size;
uint8_t lin_green_field_pos;
uint8_t lin_blue_mask_size;
uint8_t lin_blue_field_pos;
uint8_t lin_reserved_mask_size;
uint8_t lin_reserved_field_pos;
uint32_t max_pixel_clock; /* Maximum pixel clock */
uint8_t reserved4[189];
} __attribute__((packed)) vbe_mode_info_t;
/* BMP File Header */
typedef struct {
uint16_t type; /* "BM" = 0x4D42 */
uint32_t size; /* File size */
uint16_t reserved1;
uint16_t reserved2;
uint32_t off_bits; /* Offset to pixel data */
} __attribute__((packed)) bmp_file_header_t;
/* BMP Info Header (DIB header) */
typedef struct {
uint32_t size; /* Header size (40 for BITMAPINFOHEADER) */
int32_t width; /* Image width */
int32_t height; /* Image height (negative = top-down) */
uint16_t planes; /* Color planes (must be 1) */
uint16_t bit_count; /* Bits per pixel */
uint32_t compression; /* Compression type (0 = none) */
uint32_t size_image; /* Image size (can be 0) */
int32_t x_pels_per_meter; /* Horizontal resolution */
int32_t y_pels_per_meter; /* Vertical resolution */
uint32_t clr_used; /* Colors in palette */
uint32_t clr_important; /* Important colors */
} __attribute__((packed)) bmp_info_header_t;
/* Boot info structure passed to kernel */
typedef struct {
uint32_t magic; /* 0xB007B007 or similar */
uint32_t flags; /* Feature flags */
/* Framebuffer info */
uint32_t fb_addr; /* Physical address */
uint32_t fb_size; /* Size in bytes */
uint32_t fb_width; /* Width in pixels */
uint32_t fb_height; /* Height in pixels */
uint32_t fb_pitch; /* Bytes per scanline */
uint8_t fb_bpp; /* Bits per pixel */
uint8_t fb_red_pos; /* Red field position */
uint8_t fb_red_size; /* Red mask size */
uint8_t fb_green_pos;
uint8_t fb_green_size;
uint8_t fb_blue_pos;
uint8_t fb_blue_size;
uint8_t reserved;
/* Memory map, etc. would follow */
} __attribute__((packed)) boot_info_t;
4.4 Algorithm Overview
Graphics Boot Algorithm:
1. INITIALIZATION (Stage 2 entry)
├─ Enable A20 gate (for memory access above 1MB)
├─ Set up data segment for VBE buffers
└─ Prepare framebuffer access method
2. VBE QUERY
├─ Call VBE 4F00h: Get Controller Info
│ ├─ Set buffer[0-3] = "VBE2" to request VBE 2.0+ info
│ ├─ Check AX = 004Fh (supported and successful)
│ └─ Parse returned info for version, memory, mode list
│
└─ Enumerate modes from mode list
├─ For each mode number (until 0xFFFF):
│ ├─ Call VBE 4F01h: Get Mode Info
│ ├─ Check: LFB supported? (bit 7 of mode_attributes)
│ ├─ Check: Resolution and BPP acceptable?
│ └─ Store if better match than previous
└─ Select best mode found
3. MODE SWITCH
├─ Call VBE 4F02h: Set Mode
│ ├─ BX = mode_number | 0x4000 (bit 14 = use LFB)
│ └─ Check AX = 004Fh for success
├─ Save framebuffer address from mode info
└─ Calculate pitch (bytes per scanline)
4. FRAMEBUFFER ACCESS SETUP
├─ Option A: Use Unreal Mode
│ ├─ Switch to protected mode, set up flat GDT
│ ├─ Switch back to real mode but keep 32-bit segment limits
│ └─ Now can access memory above 1MB with 32-bit offsets
│
└─ Option B: Stay in Protected Mode
├─ Set up identity-mapped page tables or flat segments
└─ Access framebuffer directly
5. DRAW BACKGROUND
├─ Calculate total pixels: width * height
├─ For each pixel:
│ ├─ Calculate offset: y * pitch + x * (bpp/8)
│ └─ Write background color (e.g., dark blue: 0x00002B)
└─ Screen is now filled with solid color
6. LOAD AND DISPLAY LOGO
├─ Load logo.bmp from disk (using Stage 1's disk routines)
├─ Parse BMP headers
│ ├─ Verify signature = "BM"
│ ├─ Get dimensions from info header
│ ├─ Get bits per pixel
│ └─ Calculate row padding: (4 - (width * 3) % 4) % 4
│
├─ Calculate centered position
│ ├─ logo_x = (screen_width - logo_width) / 2
│ └─ logo_y = (screen_height - logo_height) / 2 - 100
│
└─ Render logo
├─ For row = height-1 down to 0: (BMP is bottom-up)
│ ├─ For col = 0 to width-1:
│ │ ├─ Read B, G, R from BMP data
│ │ ├─ Calculate screen position: (logo_x + col, logo_y + (height-1-row))
│ │ └─ Write pixel to framebuffer
│ └─ Skip row padding bytes
└─ Logo is now displayed
7. DRAW PROGRESS BAR
├─ Calculate bar position (centered, below logo)
├─ Draw bar background (dark gray rectangle)
├─ Draw bar border (light gray outline)
└─ Progress fill initially 0%
8. LOAD KERNEL WITH PROGRESS
├─ Start loading kernel from disk
├─ After each chunk loaded:
│ ├─ Calculate percentage: bytes_loaded / total_bytes * 100
│ ├─ Calculate fill width: (bar_width * percentage) / 100
│ └─ Update filled portion of progress bar
└─ Kernel fully loaded
9. BUILD BOOT INFO
├─ Fill boot_info structure with:
│ ├─ Framebuffer address, dimensions, pitch
│ ├─ Color mask positions and sizes
│ └─ Magic number for validation
└─ Store at known address (e.g., 0x9000)
10. JUMP TO KERNEL
├─ Set up protected mode (if not already)
├─ Load EBX with boot_info pointer
└─ Jump to kernel entry point
5. Implementation Guide
5.1 Development Environment Setup
# Required tools
$ brew install nasm qemu # macOS
$ apt install nasm qemu-system-x86 # Linux
# Create project directory
$ mkdir gfx-bootloader && cd gfx-bootloader
# Project structure
gfx-bootloader/
├── src/
│ ├── stage1.asm # MBR bootloader
│ ├── stage2.asm # Main graphics bootloader
│ ├── vbe.asm # VBE functions
│ ├── graphics.asm # Framebuffer primitives
│ ├── bmp.asm # BMP parser
│ ├── disk.asm # Disk read routines
│ └── kernel.asm # Simple test kernel
├── include/
│ ├── vbe.inc # VBE structure definitions
│ └── bmp.inc # BMP structure definitions
├── assets/
│ └── logo.bmp # Your 256x128 24-bit BMP logo
├── Makefile
└── run.sh
5.2 Project Structure
Creating a test BMP file:
# Use ImageMagick to create a simple logo
$ convert -size 256x128 xc:navy \
-fill white -gravity center \
-pointsize 36 -annotate 0 "My OS" \
-type TrueColor logo.bmp
5.3 The Core Question You’re Answering
“How do operating systems display graphics during boot, before any GPU drivers are loaded?”
This forces you to understand:
- How VBE provides standardized graphics across GPU vendors
- How framebuffers work at the hardware level
- How image formats store and represent pixel data
- How bootloaders hand off graphics state to kernels
5.4 Concepts You Must Understand First
Before implementing, you should be able to answer:
- What is a linear framebuffer?
- Contiguous region of memory representing the screen
- Each pixel is one or more bytes at a specific offset
- Writing to this memory directly changes screen output
- How do you calculate a pixel’s address?
- offset = y * pitch + x * bytes_per_pixel
- pitch may be larger than width * bpp for alignment
- Why is BMP stored bottom-up?
- Historical reason from early Windows/OS2
- Row 0 in file is bottom row of image
- Must flip Y coordinate when rendering
- What is the A20 gate?
- Historical hack that prevents access to memory above 1MB
- Must be enabled to access framebuffer (typically above 1MB)
5.5 Questions to Guide Your Design
VBE Implementation:
- How do you detect if VBE is supported?
- How do you find a mode with your desired resolution and color depth?
- What does bit 14 in the mode number do?
- Where is the framebuffer address in the mode info structure?
Graphics Rendering:
- How do you access memory above 1MB from real mode?
- What’s the difference between RGB and BGR pixel order?
- How do you handle different bits-per-pixel values?
- How do you center an image on screen?
BMP Parsing:
- What offset contains the start of pixel data?
- How do you calculate row padding?
- What order are the color components in a 24-bit BMP?
- How do you handle top-down vs bottom-up BMPs?
5.6 Thinking Exercise
Before coding, trace through this scenario:
You have set mode 0x118 (1024x768x32bpp).
Mode info shows:
- PhysBasePtr = 0xFD000000
- BytesPerScanline = 4096
- BitsPerPixel = 32
- RedMaskSize = 8, RedFieldPosition = 16
- GreenMaskSize = 8, GreenFieldPosition = 8
- BlueMaskSize = 8, BlueFieldPosition = 0
1. What is the memory address of pixel (100, 200)?
2. To draw a red pixel (255, 0, 0), what bytes do you write?
3. If you have a 200x100 logo, what are the coordinates to center it?
4. A BMP file has width=200, height=100, bpp=24. What is the row padding?
5. The BMP stores row 0 at the bottom. Which screen Y does row 50 map to?
5.7 Hints in Layers
Hint 1: Detecting VBE (Starting Point)
detect_vbe:
; Request VBE 2.0+ info
mov dword [vbe_info_buffer], 'VBE2'
mov ax, 0x4F00
mov di, vbe_info_buffer
int 0x10
cmp ax, 0x004F
jne .no_vbe
; Check signature
cmp dword [vbe_info_buffer], 'VESA'
jne .no_vbe
; VBE is available
; vbe_info_buffer now contains controller info
Hint 2: Finding and Setting a Mode (Next Level)
find_mode:
; Get pointer to mode list from VBE info
mov si, [vbe_info_buffer + 14] ; Video mode pointer (offset)
mov ax, [vbe_info_buffer + 16] ; Video mode pointer (segment)
mov fs, ax
.mode_loop:
mov cx, [fs:si] ; Get mode number
cmp cx, 0xFFFF ; End of list?
je .not_found
; Get info for this mode
push si
mov ax, 0x4F01
mov di, mode_info_buffer
int 0x10
pop si
; Check if this mode is suitable
cmp word [mode_info_buffer + 18], 1024 ; X resolution
jne .next_mode
cmp word [mode_info_buffer + 20], 768 ; Y resolution
jne .next_mode
cmp byte [mode_info_buffer + 25], 32 ; Bits per pixel
jne .next_mode
; Check LFB support (bit 7 of mode attributes)
test byte [mode_info_buffer], 0x80
jz .next_mode
; Found it! Set this mode
mov bx, cx
or bx, 0x4000 ; Use linear framebuffer
mov ax, 0x4F02
int 0x10
jmp .done
.next_mode:
add si, 2
jmp .mode_loop
Hint 3: Drawing a Pixel (Technical Details)
; Draw pixel in 32bpp mode
; Input: EAX = X, EBX = Y, ECX = color (0x00RRGGBB)
draw_pixel:
push eax
push ebx
; Calculate offset: y * pitch + x * 4
mov eax, ebx
mov edx, [pitch]
mul edx ; EAX = y * pitch
pop ebx
shl ebx, 2 ; EBX = x * 4
add eax, ebx ; EAX = offset
; Add framebuffer base
add eax, [framebuffer_addr]
; Write pixel (need to be in protected mode or unreal mode)
mov [eax], ecx
pop eax
ret
Hint 4: Loading and Drawing BMP (Verification)
draw_bmp:
; Assume BMP is loaded at bmp_data
; Get pixel data offset
mov eax, [bmp_data + 10] ; bfOffBits
add eax, bmp_data ; Absolute address
mov [pixel_data], eax
; Get dimensions
mov eax, [bmp_data + 18] ; biWidth
mov [bmp_width], eax
mov eax, [bmp_data + 22] ; biHeight
mov [bmp_height], eax
; Calculate row padding (rows must be 4-byte aligned)
mov eax, [bmp_width]
imul eax, 3 ; Bytes per row (24bpp)
and eax, 3 ; Remainder
jz .no_padding
mov ebx, 4
sub ebx, eax
mov [row_padding], ebx
jmp .calc_done
.no_padding:
mov dword [row_padding], 0
.calc_done:
; Calculate centered position
mov eax, [screen_width]
sub eax, [bmp_width]
shr eax, 1 ; / 2
mov [draw_x], eax
mov eax, [screen_height]
sub eax, [bmp_height]
shr eax, 1
mov [draw_y], eax
; Draw rows (BMP is bottom-up)
mov esi, [pixel_data]
mov ecx, [bmp_height]
dec ecx ; Start at bottom row
.row_loop:
push ecx
mov ecx, [bmp_width]
.col_loop:
; Read BGR from BMP
lodsb ; Blue
mov bl, al
lodsb ; Green
mov bh, al
lodsb ; Red
mov ah, al
; Build color: 0x00RRGGBB
movzx eax, ah ; Red
shl eax, 16
mov al, bh ; Green
shl ax, 8
mov al, bl ; Blue
; Calculate screen position
; screen_x = draw_x + (width - col)
; screen_y = draw_y + row
; ... draw pixel ...
loop .col_loop
; Skip padding
add esi, [row_padding]
pop ecx
loop .row_loop
5.8 The Interview Questions They’ll Ask
- “How does a bootloader display graphics without GPU drivers?”
- Strong answer: “Uses VESA BIOS Extensions (VBE) which provides a standard interface implemented in the video BIOS ROM. VBE allows querying available modes and setting a mode that gives direct access to a linear framebuffer. The bootloader writes pixels directly to this memory-mapped region without needing vendor-specific drivers.”
- “What is a linear framebuffer and how do you calculate pixel addresses?”
- Strong answer: “A linear framebuffer is a contiguous region of video memory where each pixel’s color is stored at a calculable offset. For a given (x,y), the offset is: y * pitch + x * bytes_per_pixel. Pitch is the number of bytes per row, which may be larger than width * bpp for alignment. The physical address is framebuffer_base + offset.”
- “Why are BMP files stored bottom-up and how do you handle this?”
- Strong answer: “BMP’s bottom-up storage is historical, from the OS/2 and early Windows coordinate system where Y=0 was at the bottom. When rendering, you either read rows in reverse order, or calculate screen_y = image_height - 1 - bmp_row. You also need to handle row padding since each row must be 4-byte aligned.”
- “How would you implement a progress bar animation?”
- Strong answer: “Draw a border rectangle first, then track a ‘fill percentage’. On each update, calculate fill_width = bar_width * percentage / 100, then fill that portion with the ‘filled’ color. To animate smoothly, update after each chunk of loading, using the bytes_loaded/total_bytes ratio.”
- “What’s the difference between VBE and UEFI GOP?”
- Strong answer: “VBE works in real mode via INT 10h and requires the video BIOS ROM. UEFI GOP is a native UEFI protocol that works in protected mode, uses the UEFI runtime, and is more modern. GOP is simpler to use in UEFI environments and doesn’t require mode switching. VBE is still useful for BIOS-based or CSM boot.”
5.9 Books That Will Help
| Topic | Book Reference |
|---|---|
| VBE Specification | VBE 3.0 Specification - Primary reference |
| Framebuffer Graphics | “Computer Graphics from Scratch” by Gabriel Gambetta, Chapter 1 |
| BMP File Format | BMP Wikipedia - Detailed format description |
| x86 Real Mode Graphics | “Low-Level Programming” by Igor Zhirkov, Chapter 5 |
| Protected Mode Access | “Operating Systems: Three Easy Pieces” - Arpaci-Dusseau, Virtualization section |
| OSDev Reference | OSDev Wiki - VESA Video Modes |
5.10 Implementation Phases
Phase 1: VBE Query and Mode Setting (3-4 days)
- Implement VBE controller info query
- Enumerate available modes
- Find and set a suitable mode
- Verify mode is active (screen changes)
- Test: Screen clears to black or garbage (no more text mode)
Phase 2: Basic Framebuffer Access (2-3 days)
- Set up unreal mode or protected mode for FB access
- Implement pixel drawing function
- Fill screen with solid color
- Draw simple shapes (rectangle)
- Test: Screen fills with chosen color
Phase 3: BMP Loading and Rendering (3-4 days)
- Load BMP file from disk
- Parse BMP headers
- Handle bottom-up row order
- Render to framebuffer
- Test: Logo appears on screen (correctly oriented)
Phase 4: Progress Bar and Animation (2-3 days)
- Draw progress bar outline
- Implement fill animation
- Tie to actual loading progress
- Test: Progress bar animates during kernel load
Phase 5: Kernel Handoff (2-3 days)
- Build boot info structure
- Transition to protected mode
- Pass framebuffer info to kernel
- Test: Kernel continues graphics
5.11 Key Implementation Decisions
- Unreal Mode vs Protected Mode for FB Access?
- Unreal Mode: Easier, can still use BIOS calls
- Protected Mode: Cleaner, but no BIOS for disk reads
- Recommendation: Use unreal mode for simplicity
- Which Resolution to Target?
- 640x480: Always available, small
- 1024x768: Good balance, widely supported
- 1920x1080: Modern, may not be available
- Recommendation: Query for 1024x768, fall back to 800x600
- Color Depth?
- 16bpp: Faster, but color banding
- 24bpp: True color, odd byte alignment
- 32bpp: True color, easy addressing
- Recommendation: Use 32bpp when available
6. Testing Strategy
# Test mode query
$ qemu-system-x86_64 -hda bootloader.img -serial stdio
# Expected: List of available modes printed
# Test with Bochs (better VBE emulation)
$ bochs -f bochsrc
# Test different resolutions
# Modify mode selection code to try different modes
# Test BMP loading
$ file logo.bmp # Should show: "PC bitmap, Windows 3.x format, 256 x 128"
# Test on real hardware
# Write to USB drive, boot real PC
7. Common Pitfalls & Debugging
Problem 1: Screen goes blank after mode switch, nothing visible
- Cause: Framebuffer address wrong or not accessible
- Fix: Verify PhysBasePtr from mode info, check A20 enabled
- Verify: Write to 0xFD000000 + 0 should change top-left pixel
Problem 2: Image appears upside down
- Cause: Not accounting for BMP’s bottom-up storage
- Fix: Iterate rows from height-1 to 0, or flip screen_y
- Verify: Check if row 0 of BMP goes to screen bottom
Problem 3: Image has wrong colors (blue faces, etc.)
- Cause: BGR vs RGB confusion
- Fix: BMP stores Blue first, then Green, then Red
- Verify: Draw pure red (255,0,0) - if it’s blue, swap R and B
Problem 4: Image has vertical lines or is stretched
- Cause: Wrong pitch or row padding calculation
- Fix: Use BytesPerScanline from mode info for pitch
- Verify: pitch may be > width * bpp for alignment
Problem 5: Screen works in QEMU but not real hardware
- Cause: VBE version or mode support differs
- Fix: Check VBE version, use mode enumeration not hardcoded modes
- Verify: Print VBE version and available modes on real hardware
8. Extensions & Challenges
- Support Multiple Image Formats: Add TGA or PNG support
- Smooth Transitions: Implement fade-in/fade-out effects
- Multiple Themes: Load different logos based on configuration
- Boot Menu: Graphical boot menu with mouse support
- Animation: Display animated logo (sprite sheet or simple animation)
- Font Rendering: Draw text using a bitmap font
- Double Buffering: Eliminate flicker with off-screen buffer
9. Real-World Connections
- Windows Boot: bootmgr uses UEFI GOP or VBE for splash screen
- Linux Plymouth: Uses kernel framebuffer set by bootloader
- GRUB Themes: GRUB2 supports graphical themes via VBE/UEFI GOP
- macOS Boot: Uses EFI GOP for Apple logo display
- Chrome OS: Depthcharge bootloader handles graphics
10. Resources
Specifications:
Tutorials:
Reference Code:
11. Self-Assessment Checklist
Before considering this project complete, verify:
- Can explain VBE architecture and function calls
- Can describe linear framebuffer memory layout
- Can calculate pixel addresses for any (x,y) position
- VBE mode query works and lists available modes
- Graphics mode switches successfully
- Screen fills with solid background color
- BMP logo loads and displays correctly (right colors, orientation)
- Progress bar animates during kernel load
- Kernel receives framebuffer info and can continue drawing
- Works in QEMU with different resolutions
12. Submission / Completion Criteria
Your implementation is complete when:
- VBE Detection: Queries and displays VBE version and capabilities
- Mode Selection: Finds suitable mode and switches successfully
- Solid Background: Screen fills with chosen background color
- Logo Display: BMP logo renders centered with correct colors
- Progress Bar: Animates during kernel loading
- Kernel Handoff: Passes framebuffer info, kernel continues graphics
- Documentation: README explains setup and customization
Bonus Points:
- Support for multiple resolutions with fallback
- Graceful handling when VBE not available
- Support for 24-bit and 32-bit BMPs
- Smooth fade transitions
“The boot splash screen is the first thing users see. It sets expectations for the entire experience. Building one teaches you that graphics isn’t magic - it’s just writing the right bytes to the right addresses at the right time.”