use core::{fmt, slice};
use bitfield_struct::bitfield;
use embedded_graphics::draw_target::DrawTarget;
use embedded_graphics::geometry::{OriginDimensions, Point};
use embedded_graphics::pixelcolor::raw::ToBytes;
use embedded_graphics::pixelcolor::{Rgb565, Rgb888};
use embedded_graphics::prelude::Size;
use embedded_graphics::primitives::Rectangle;
use embedded_graphics::Pixel;
use crate::util::InlineStr;
use crate::{multiboot as mb, MBOOT_PTR, SCREEN_HEIGHT, SCREEN_WIDTH};
#[repr(C, align(0x1000))]
struct Buffer([u8; 0x800_000]);
static mut BUFFER: Buffer = Buffer([0; 0x800_000]);
pub struct Display {
addr: usize,
width: usize,
height: usize,
color: ColorMode,
}
impl Display {
pub fn info() {
let mboot = unsafe { mb::Info::from_ptr(MBOOT_PTR as _) };
if let Some(vbe) = mboot.vbe() {
let ctrl = unsafe { VbeCtrlInfo::from_ptr(vbe.control_info) };
serial!("{ctrl:x?}");
let info = unsafe { VbeModeInfo::from_ptr(vbe.mode_info) };
serial!("{info:x?}");
}
}
pub fn new() -> Result<Self, ()> {
let mboot = unsafe { mb::Info::from_ptr(MBOOT_PTR as _) };
let fb = if let Some(fb) = mboot.framebuffer() {
serial!("{fb:?}");
fb.clone()
} else if let Some(vbe) = mboot.vbe()
&& vbe.mode_info != 0
{
let info = unsafe { VbeModeInfo::from_ptr(vbe.mode_info) };
match info.framebuffer() {
Some(fb) => fb,
None => return Err(()),
}
} else {
serial!("No framebuffer!");
return Err(());
};
if (fb.width * fb.height) as usize > (SCREEN_WIDTH * SCREEN_HEIGHT) {
serial!("FB too large");
return Err(());
}
if (fb.addr + (fb.width * fb.height * 4) as u64) > u32::MAX as u64 {
serial!("FB outside of 32bit range");
return Err(());
}
let Some(mb::ColorInfo::Rgb(rgb)) = fb.color_info() else {
serial!("No color info");
return Err(());
};
let Ok(color) = ColorMode::try_from((fb.bpp, rgb)) else {
serial!("Unsupported color mode");
return Err(());
};
Ok(Self {
addr: fb.addr as _,
width: fb.width as _,
height: fb.height as _,
color,
})
}
pub fn buffer<T>(&mut self) -> &mut [T] {
let slice = unsafe { &mut BUFFER.0 };
unsafe {
slice::from_raw_parts_mut(slice.as_mut_ptr().cast(), slice.len() / size_of::<T>())
}
}
pub fn hw_buffer(&mut self) -> &mut [u8] {
unsafe {
slice::from_raw_parts_mut(self.addr as *mut u8, (4 * self.width * self.height) as _)
}
}
pub fn render(&mut self) {
let hw_buffer = self.hw_buffer();
let buffer = &unsafe { &BUFFER.0 };
hw_buffer.copy_from_slice(&buffer[0..hw_buffer.len()]);
}
pub fn set(&mut self, off: usize, color: Rgb888) {
match self.color {
ColorMode::Rgb32 => self.buffer()[off] = color,
ColorMode::Rgb24 => self.buffer::<[u8; 3]>()[off] = color.to_ne_bytes(),
ColorMode::Rgb16 => self.buffer()[off] = Rgb565::from(color),
}
}
pub fn fill(&mut self, off: usize, len: usize, color: Rgb888) {
match self.color {
ColorMode::Rgb32 => self.buffer()[off..off + len].fill(color),
ColorMode::Rgb24 => self.buffer::<[u8; 3]>()[off..off + len].fill(color.to_ne_bytes()),
ColorMode::Rgb16 => self.buffer()[off..off + len].fill(Rgb565::from(color)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ColorMode {
Rgb32,
Rgb24,
Rgb16,
}
impl TryFrom<(u8, mb::ColorInfoRgb)> for ColorMode {
type Error = ();
fn try_from((bpp, mode): (u8, mb::ColorInfoRgb)) -> Result<Self, Self::Error> {
match (bpp, mode) {
(
32,
mb::ColorInfoRgb {
red_offset: 16,
red_bits: 8,
green_offset: 8,
green_bits: 8,
blue_offset: 0,
blue_bits: 8,
},
) => Ok(ColorMode::Rgb32),
(
24,
mb::ColorInfoRgb {
red_offset: 16,
red_bits: 8,
green_offset: 8,
green_bits: 8,
blue_offset: 0,
blue_bits: 8,
},
) => Ok(ColorMode::Rgb24),
(
16,
mb::ColorInfoRgb {
red_offset: 11,
red_bits: 5,
green_offset: 5,
green_bits: 6,
blue_offset: 0,
blue_bits: 5,
},
) => Ok(ColorMode::Rgb16),
_ => Err(()),
}
}
}
impl DrawTarget for Display {
type Color = Rgb888;
type Error = ();
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Pixel<Self::Color>>,
{
let (width, height) = (self.width, self.height);
for Pixel(coord, color) in pixels.into_iter() {
if 0 <= coord.x && coord.x < width as i32 && 0 <= coord.y && coord.y <= height as i32 {
let (x, y) = (coord.x as usize, coord.y as usize);
self.set(x + y * width, color);
}
}
Ok(())
}
fn fill_solid(&mut self, area: &Rectangle, color: Self::Color) -> Result<(), Self::Error> {
let (width, height) = (self.width, self.height);
let area = area.intersection(&Rectangle::new(
Point::zero(),
Size::new(width as _, height as _),
));
for y in area.rows() {
self.fill(
y as usize * width + area.top_left.x as usize,
area.size.width as _,
color,
);
}
Ok(())
}
}
impl OriginDimensions for Display {
fn size(&self) -> Size {
Size::new(self.width as _, self.height as _)
}
}
#[repr(C, packed)]
#[derive(Debug)]
struct VbeCtrlInfo {
signature: InlineStr<4>,
version: u16,
oem_string: SegOff,
capabilities: u32,
video_mode: SegOff,
total_memory: u16,
oem_software_rev: u16,
oem_vendor_name: SegOff,
oem_product_name: SegOff,
oem_product_rev: SegOff,
}
impl VbeCtrlInfo {
unsafe fn from_ptr<'a>(ptr: u32) -> &'a Self {
&*(ptr as *const Self)
}
}
#[derive(Clone, Copy)]
struct SegOff {
offset: u16,
segment: u16,
}
impl fmt::Debug for SegOff {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:x}:{:x}", self.offset, self.segment)
}
}
#[derive(Debug)]
#[repr(C, packed)]
struct VbeModeInfo {
mode_attributes: ModeAttributes,
_win: [u16; 7],
pitch: u16,
width: u16,
height: u16,
char_width: u8,
char_height: u8,
planes: u8,
bpp: u8,
banks: u8,
memory_model: MemoryModel,
bank_size: u8,
image_pages: u8,
reserved: u8,
bits_red: u8,
offset_red: u8,
bits_green: u8,
offset_green: u8,
bits_blue: u8,
offset_blue: u8,
bits_rsv: u8,
offset_rsv: u8,
directcolor_attributes: u8,
address: u32,
offscreen_memory_offset: u32,
offscreen_memory_size: u16,
}
impl VbeModeInfo {
unsafe fn from_ptr<'a>(ptr: u32) -> &'a Self {
&*(ptr as *const Self)
}
fn framebuffer(&self) -> Option<mb::FramebufferTable> {
let attr = self.mode_attributes;
if !attr.lfb() {
serial!("LFB not set");
}
if self.address == 0 {
serial!("No framebuffer address");
return None;
}
let color_info = match self.memory_model {
MemoryModel::TextMode => Some(mb::ColorInfo::Text),
MemoryModel::DirectColor => Some(mb::ColorInfo::Rgb(mb::ColorInfoRgb {
red_offset: self.offset_red,
red_bits: self.bits_red,
green_offset: self.offset_green,
green_bits: self.bits_green,
blue_offset: self.offset_blue,
blue_bits: self.bits_blue,
})),
_ => None,
};
Some(mb::FramebufferTable::new(
self.address as _,
self.pitch as _,
self.width as _,
self.height as _,
self.bpp,
color_info,
))
}
}
#[bitfield(u16)]
struct ModeAttributes {
text_mode: bool,
supported: bool,
tty: bool,
color: bool,
graphics: bool,
vga: bool,
vga_paged: bool,
lfb: bool,
__: u8,
}
#[derive(Debug, Clone, Copy)]
#[repr(u8)]
#[allow(unused)]
enum MemoryModel {
TextMode = 0,
CGA = 1,
Hercules = 2,
Planar = 3,
Packed = 4,
NonChain4 = 5,
DirectColor = 6,
YUV = 7,
}