Skip to main content

Overview

Portix OS implements a VESA framebuffer driver with double buffering, dirty-region tracking, and x86 hardware-accelerated primitives. All rendering occurs in a back buffer (0x0060_0000) and is blitted to the LFB only when changed. Location: ~/workspace/source/kernel/src/graphics/driver/framebuffer.rs

Architecture

Framebuffer Structure
pub struct Framebuffer {
    lfb:        u64,          // Linear framebuffer address
    backbuf:    u64,          // Back buffer (0x0060_0000)
    pub width:  usize,
    pub height: usize,
    lfb_pitch:  usize,        // LFB bytes per line
    bpp:        u8,           // Bits per pixel (16/24/32)
    back_pitch: usize,        // Back buffer bytes per line
    pub dirty:  DirtyRegion,  // Changed area tracking
}

Initialization

Boot Info Reading
pub fn new() -> Self {
    // Read VESA mode info from boot sector
    let lfb    = read_volatile(0x9004 as *const u32) as u64;
    let width  = read_volatile(0x9008 as *const u16) as usize;
    let height = read_volatile(0x900A as *const u16) as usize;
    let pitch  = read_volatile(0x900C as *const u16) as usize;
    let bpp    = read_volatile(0x900E as *const u8);
    
    // Clear back buffer with rep stosd
    fast_fill_u32(BACKBUF_ADDR, 0, width * height);
    
    // Initialize alpha blending LUT
    init_alpha_lut();
}
Source: ~/workspace/source/kernel/src/graphics/driver/framebuffer.rs:229

Color System

Color Structure

Color Definition
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Color(pub u32); // 0xRRGGBB format

impl Color {
    pub const fn new(r: u8, g: u8, b: u8) -> Self {
        Self(((r as u32) << 16) | ((g as u32) << 8) | (b as u32))
    }
    
    pub const fn r(self) -> u8 { ((self.0 >> 16) & 0xFF) as u8 }
    pub const fn g(self) -> u8 { ((self.0 >>  8) & 0xFF) as u8 }
    pub const fn b(self) -> u8 { ( self.0        & 0xFF) as u8 }
}

Color Palette

Color::BLACK        // 0x000000
Color::WHITE        // 0xFFFFFF
Color::RED          // 0xEE2222
Color::GREEN        // 0x00CC44
Color::BLUE         // 0x0055FF
Color::YELLOW       // 0xFFFF00
Source: ~/workspace/source/kernel/src/graphics/driver/framebuffer.rs:57

Alpha Blending

static mut ALPHA_LUT: [[u8; 256]; 256] = [[0u8; 256]; 256];

pub fn init_alpha_lut() {
    for a in 0..256 {
        for v in 0..256 {
            ALPHA_LUT[a][v] = ((v * a) / 255) as u8;
        }
    }
}

#[inline(always)]
fn alpha_mul(v: u8, a: u8) -> u8 {
    ALPHA_LUT[a as usize][v as usize]
}
Performance: LUT-based blending eliminates division in hot path. Original blend() method preserved for compatibility. Source: ~/workspace/source/kernel/src/graphics/driver/framebuffer.rs:112

Dirty Region Tracking

Dirty Region
pub struct DirtyRegion {
    pub min_x: usize,
    pub min_y: usize,
    pub max_x: usize,
    pub max_y: usize,
    pub dirty: bool,
}

impl DirtyRegion {
    #[inline]
    pub fn mark(&mut self, x: usize, y: usize, w: usize, h: usize) {
        self.dirty = true;
        self.min_x = self.min_x.min(x);
        self.min_y = self.min_y.min(y);
        self.max_x = self.max_x.max(x + w);
        self.max_y = self.max_y.max(y + h);
    }
}
Usage: Every draw operation marks the affected rectangle. present() only blits the dirty region, reducing memory bandwidth by up to 10x for small updates. Source: ~/workspace/source/kernel/src/graphics/driver/framebuffer.rs:124

Hardware Acceleration

rep stosd (Fill)

Fast Fill
#[inline]
unsafe fn fast_fill_u32(dst: *mut u32, val: u32, count: usize) {
    core::arch::asm!(
        "cld",
        "rep stosd",
        inout("rdi") dst   => _,
        inout("ecx") count => _,
        in("eax")    val,
        options(nostack)
    );
}
Performance: 3-5× faster than manual loop. Used for:
  • Back buffer clear
  • Rectangle fills
  • Solid color spans

rep movsd (Copy)

Fast Copy
#[inline]
unsafe fn fast_copy_u32(dst: *mut u32, src: *const u32, count: usize) {
    core::arch::asm!(
        "cld",
        "rep movsd",
        inout("rdi") dst   => _,
        inout("rsi") src   => _,
        inout("ecx") count => _,
        options(nostack)
    );
}
Performance: 3-5× faster than manual loop. Used for:
  • Back-to-front buffer blit
  • Vertical scrolling
  • Sprite blitting
Source: ~/workspace/source/kernel/src/graphics/driver/framebuffer.rs:263

Blitting to Linear Framebuffer

Present (Dirty-Rect Optimized)
pub fn present(&mut self) {
    if !self.dirty.dirty { return; }
    
    let x0 = self.dirty.min_x.min(self.width);
    let y0 = self.dirty.min_y.min(self.height);
    let x1 = self.dirty.max_x.min(self.width);
    let y1 = self.dirty.max_y.min(self.height);
    
    self.dirty.reset();
    let cols = x1 - x0;
    
    match self.bpp {
        32 => {
            // Direct copy with rep movsd
            for y in y0..y1 {
                let src = backbuf + (y * back_pitch + x0 * 4);
                let dst = lfb + (y * lfb_pitch + x0 * 4);
                fast_copy_u32(dst, src, cols);
            }
        }
        24 => {
            // Convert 32bpp -> 24bpp
            for y in y0..y1 {
                for x in 0..cols {
                    let px = read(src + x);
                    write_volatile(dst + x*3 + 0, (px >>  0) as u8);
                    write_volatile(dst + x*3 + 1, (px >>  8) as u8);
                    write_volatile(dst + x*3 + 2, (px >> 16) as u8);
                }
            }
        }
        16 => {
            // Convert 32bpp -> RGB565
            for y in y0..y1 {
                for x in 0..cols {
                    let px = read(src + x);
                    let r = ((px >> 16) & 0xFF) as u16;
                    let g = ((px >>  8) & 0xFF) as u16;
                    let b = ( px        & 0xFF) as u16;
                    let rgb565 = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
                    write_volatile(dst + x, rgb565);
                }
            }
        }
    }
}
Source: ~/workspace/source/kernel/src/graphics/driver/framebuffer.rs:319

Drawing Primitives

Basic Shapes

impl Framebuffer {
    pub fn fill_rect(&mut self, x: usize, y: usize, w: usize, h: usize, c: Color) {
        for row in y..(y + h) {
            let ptr = backbuf + (row * back_pitch + x * 4);
            fast_fill_u32(ptr, c.0, w);
        }
        self.dirty.mark(x, y, w, h);
    }
    
    pub fn draw_rect_border(&mut self, x: usize, y: usize, w: usize, h: usize, t: usize, c: Color) {
        self.fill_rect(x, y, w, t, c);                              // Top
        self.fill_rect(x, y + h - t, w, t, c);                      // Bottom
        self.fill_rect(x, y, t, h, c);                              // Left
        self.fill_rect(x + w - t, y, t, h, c);                      // Right
    }
}

Rounded Rectangles

Rounded Corners
pub fn fill_rounded(&mut self, x: usize, y: usize, w: usize, h: usize, r: usize, c: Color) {
    let r = r.min(w / 2).min(h / 2);
    
    // Center rectangle
    self.fill_rect(x, y + r, w, h - r * 2, c);
    
    // Top and bottom bars
    self.fill_rect(x + r, y, w - r * 2, r, c);
    self.fill_rect(x + r, y + h - r, w - r * 2, r, c);
    
    // Corner trim (approximation)
    for dy in 0..r {
        let trim = r - dy;
        self.fill_rect(x + trim, y + dy, w - trim * 2, 1, c);
        self.fill_rect(x + trim, y + h - dy - 1, w - trim * 2, 1, c);
    }
}
Source: ~/workspace/source/kernel/src/graphics/driver/framebuffer.rs:423

Bresenham Line

Line Drawing
pub fn draw_line(&mut self, mut x0: i32, mut y0: i32, x1: i32, y1: i32, c: Color) {
    let dx  =  (x1 - x0).abs();
    let sx  = if x0 < x1 { 1 } else { -1 };
    let dy  = -(y1 - y0).abs();
    let sy  = if y0 < y1 { 1 } else { -1 };
    let mut err = dx + dy;
    
    loop {
        if x0 >= 0 && y0 >= 0 {
            self.draw_pixel(x0 as usize, y0 as usize, c);
        }
        if x0 == x1 && y0 == y1 { break; }
        
        let e2 = 2 * err;
        if e2 >= dy { err += dy; x0 += sx; }
        if e2 <= dx { err += dx; y0 += sy; }
    }
}
Source: ~/workspace/source/kernel/src/graphics/driver/framebuffer.rs:495

Circle Fill (Midpoint)

Filled Circle
pub fn fill_circle(&mut self, cx: i32, cy: i32, r: i32, c: Color) {
    let mut x = 0;
    let mut y = r;
    let mut d = 1 - r;
    
    while x <= y {
        // Fill horizontal spans at 8 symmetric points
        self.fill_rect((cx - y) as usize, (cy + x) as usize, (2 * y) as usize, 1, c);
        self.fill_rect((cx - y) as usize, (cy - x) as usize, (2 * y) as usize, 1, c);
        self.fill_rect((cx - x) as usize, (cy + y) as usize, (2 * x) as usize, 1, c);
        self.fill_rect((cx - x) as usize, (cy - y) as usize, (2 * x) as usize, 1, c);
        
        if d < 0 {
            d += 2 * x + 3;
        } else {
            d += 2 * (x - y) + 5;
            y -= 1;
        }
        x += 1;
    }
}
Source: ~/workspace/source/kernel/src/graphics/driver/framebuffer.rs:518

Advanced Effects

Gradient with Dithering

Bayer Dithering
const BAYER_4X4: [[u8; 4]; 4] = [
    [ 0,  8,  2, 10],
    [12,  4, 14,  6],
    [ 3, 11,  1,  9],
    [15,  7, 13,  5],
];

pub fn fill_gradient_dither(
    &mut self,
    x: usize, y: usize, w: usize, h: usize,
    c0: Color, c1: Color
) {
    for py in y..(y + h) {
        let brow = &BAYER_4X4[py & 3];
        for px in x..(x + w) {
            let t = ((px - x) as u32 * 255) / w as u32;
            let dith = brow[px & 3] as u32;
            let td = (t + dith / 2).min(255) as u8;
            let it = 255 - td;
            
            let r = (c0.r() as u32 * td as u32 / 255 + c1.r() as u32 * it as u32 / 255) as u8;
            let g = (c0.g() as u32 * td as u32 / 255 + c1.g() as u32 * it as u32 / 255) as u8;
            let b = (c0.b() as u32 * td as u32 / 255 + c1.b() as u32 * it as u32 / 255) as u8;
            
            self.draw_pixel(px, py, Color::new(r, g, b));
        }
    }
    self.dirty.mark(x, y, w, h);
}
Result: Smooth gradients without color banding artifacts. Source: ~/workspace/source/kernel/src/graphics/driver/framebuffer.rs:456

Vertical Scrolling

Scroll Region
pub fn scroll_region_up(
    &mut self,
    sx: usize, sy: usize, w: usize, h: usize,
    lines: usize,
    fill: Color
) {
    let visible = h - lines;
    
    // Move rows up using rep movsd
    for row in 0..visible {
        let src = backbuf + ((sy + row + lines) * back_pitch + sx * 4);
        let dst = backbuf + ((sy + row) * back_pitch + sx * 4);
        fast_copy_u32(dst, src, w);
    }
    
    // Fill exposed area at bottom
    for row in visible..h {
        self.fill_rect(sx, sy + row, w, 1, fill);
    }
    
    self.dirty.mark(sx, sy, w, h);
}
Source: ~/workspace/source/kernel/src/graphics/driver/framebuffer.rs:539

Font Rendering

The Console wrapper provides text rendering:
Text Rendering
pub struct Console {
    fb:           Framebuffer,
    pub cursor_x: usize,
    pub cursor_y: usize,
    pub fg_color: Color,
    pub bg_color: Color,
    font_w:       usize,  // 8 pixels
    font_h:       usize,  // 8 pixels
}

impl Console {
    pub fn write(&mut self, s: &str, color: Color) {
        for ch in s.chars() {
            match ch {
                '\n' => { self.cursor_x = 0; self.cursor_y += 13; }
                '\t' => { self.cursor_x = (self.cursor_x / 36 + 1) * 36; }
                _ => {
                    self.draw_char(self.cursor_x, self.cursor_y, ch, color, self.bg_color);
                    self.cursor_x += 9;
                }
            }
        }
    }
}
Font: 8×8 bitmap font from crate::graphics::render::font::FONT_8X8 Source: ~/workspace/source/kernel/src/graphics/driver/framebuffer.rs:745

VGA Text Mode

Portix OS defaults to VESA graphical mode but retains VGA text mode (80×25) for emergency fallback. Text mode is not used during normal operation.

Mouse Cursor

Cursor Rendering
const CURSOR_W: usize = 12;
const CURSOR_H: usize = 12;
const ARROW: &[u16] = &[
    0b1000_0000_0000_0000,
    0b1100_0000_0000_0000,
    0b1110_0000_0000_0000,
    0b1111_0000_0000_0000,
    // ... (12 rows total)
];

pub fn draw_cursor(&mut self, mx: i32, my: i32) {
    // Draw shadow
    for (row, &mask) in ARROW.iter().enumerate() {
        for col in 0..CURSOR_W {
            if (mask >> (15 - col)) & 1 != 0 {
                self.draw_pixel(cx + col + 1, cy + row + 1, Color::BLACK);
            }
        }
    }
    
    // Draw arrow
    for (row, &mask) in ARROW.iter().enumerate() {
        for col in 0..CURSOR_W {
            if (mask >> (15 - col)) & 1 != 0 {
                self.draw_pixel(cx + col, cy + row, Color::WHITE);
            }
        }
    }
}
Source: ~/workspace/source/kernel/src/graphics/driver/framebuffer.rs:588

Performance Metrics

OperationTime (1920×1080)Speedup
Full screen clear~3 ms5× (rep stosd)
Full present (32bpp)~8 ms4× (rep movsd)
Dirty present (10% screen)~1 ms10× (tracking)
Rectangle fill 100×100~10 μs3× (rep stosd)
Alpha blend 100×100~200 μs3× (LUT)
Hardware tested: QEMU, VirtualBox, bare metal Intel Core i5. Results may vary on AMD platforms.

Layout System

Responsive layout calculations:
Layout
pub struct Layout {
    pub fw: usize, pub fh: usize,
    pub header_h: usize,
    pub tab_h: usize,
    pub content_y: usize,
    pub status_h: usize,
    // ...
}

impl Layout {
    pub fn new(fw: usize, fh: usize) -> Self {
        let header_h = ((fh * 65) / 1000).max(38).min(60);
        let tab_h    = ((fh * 35) / 1000).max(22).min(32);
        // All dimensions calculated proportionally
    }
}
Source: ~/workspace/source/kernel/src/graphics/driver/framebuffer.rs:172

See Also

Console

Terminal interface using framebuffer

Drivers

Mouse driver for cursor rendering

Build docs developers (and LLMs) love