rust-osdev / bootloader

An experimental pure-Rust x86 bootloader
Apache License 2.0
1.38k stars 210 forks source link

Double buffering / page flipping for the framebuffer #234

Open floppydiskette opened 2 years ago

floppydiskette commented 2 years ago

I was wondering if there was any easy way to implement a double buffer for the framebuffer that this crate provides? Forgive me if this seems obvious as I am very new to low-level video programming

phil-opp commented 2 years ago

As far as I know, there is no hardware-supported way to do double buffering (i.e. switching the frame buffer pointer). So you have to do a manual copy instead. For example, you could allocate a separate buffer somewhere and render your output there, and then periodically copy that buffer to the real framebuffer (e.g. 60 or 30 times a second).

The UEFI framebuffer support a BLT operation for fast block-level transfers, which can make the copying more efficent. However, this operation is not available anymore after exiting the UEFI boot services. So it's only available to the bootloader, but not your kernel unfortunately.

Other than that, you can of course write your own GPU driver, but this is quite difficult.

Spleeshmicannon commented 2 years ago

I actually did a basic implementation of this but get serious screen tearing, although that may be solved by controlling how often I write to the frame buffer. I'm curious about whether or not what I've done here is okay. I found the for loop implementation and .fills were too slow so I tried using a .copy_from_slice which is a lot faster.

const BUFFER_SIZE: usize = 10_000_000;

const CLEAR_FRAME_BUFFER: [u8; BUFFER_SIZE] = [0x00; BUFFER_SIZE];
static BACK_BUFFER: [u8; BUFFER_SIZE] = [0x90; BUFFER_SIZE];

#[inline]
fn clear_screen(framebuffer: &mut FrameBuffer, byte_len: usize) {
    framebuffer
        .buffer_mut()
        .copy_from_slice(&CLEAR_FRAME_BUFFER[0..byte_len]);
}

#[inline]
fn show_screen(framebuffer: &mut FrameBuffer, byte_len: usize) {
    framebuffer
        .buffer_mut()
        .copy_from_slice(&BACK_BUFFER[0..byte_len]);
}

fn kernel_main(boot_info: &'static mut BootInfo) -> ! {
    let framebuffer = boot_info.framebuffer.as_mut().unwrap();

    let byte_len = framebuffer.info().byte_len;

    loop {
        clear_screen(framebuffer, byte_len);
        show_screen(framebuffer, byte_len);
    }
}

Just thought anyone reading through this might find what I wrote useful. Good luck with you're own implementations! :)

NOTE: make sure to a mutex or something if you want to write to the back buffer :)