use ref_thread_local::{ref_thread_local, refmanager::RefMut, RefThreadLocal}; use std::convert::TryInto; use std::io::{Read, Seek, SeekFrom, Write}; use wasmer_wasi::state::{WasiFile, WasiFs, ALL_RIGHTS, VIRTUAL_ROOT_FD}; use minifb::{Key, Scale, Window, WindowOptions}; ref_thread_local! { pub(crate) static managed FRAMEBUFFER_STATE: FrameBufferState = FrameBufferState::new(); } fn get_fb_state<'a>() -> RefMut<'a, FrameBufferState> { FRAMEBUFFER_STATE.borrow_mut() } pub const MAX_X: u32 = 8192; pub const MAX_Y: u32 = 4320; #[derive(Debug)] pub enum FrameBufferFileType { Buffer, Resolution, IndexDisplay, Input, } #[derive(Debug)] pub(crate) struct FrameBufferState { // double buffered pub data_1: Vec, pub data_2: Vec, pub x_size: u32, pub y_size: u32, pub front_buffer: bool, pub window: Window, } impl FrameBufferState { pub fn new() -> Self { let x = 100; let y = 200; let window = Self::create_window(x, y); Self { data_1: vec![0; x * y], data_2: vec![0; x * y], x_size: x as u32, y_size: y as u32, front_buffer: true, window, } } fn create_window(x: usize, y: usize) -> Window { Window::new( "Wasmer Experimental FrameBuffer", x, y, WindowOptions { resize: true, scale: Scale::X1, ..WindowOptions::default() }, ) .unwrap() } pub fn resize(&mut self, x: u32, y: u32) -> Option<()> { if x >= MAX_X || y >= MAX_Y { return None; } self.x_size = x; self.y_size = x; self.data_1.resize((x * y) as usize, 0); self.data_2.resize((x * y) as usize, 0); self.window = Self::create_window(x as usize, y as usize); Some(()) } pub fn draw(&mut self) { self.window.update_with_buffer(if self.front_buffer { &self.data_1[..] } else { &self.data_2[..] }); } pub fn get_buffer(&self) -> &[u32] { if self.front_buffer { &self.data_1[..] } else { &self.data_2[..] } } pub fn get_buffer_mut(&mut self) -> &mut [u32] { if self.front_buffer { &mut self.data_1[..] } else { &mut self.data_2[..] } } #[inline] // the real index into u32s and whether to use the front buffer or the back buffer fn get_idx_info(&self, idx: usize) -> Option<(usize, bool)> { let mut base_idx = idx / 4; let mut front_buffer = true; if base_idx >= self.data_1.len() { base_idx -= self.data_1.len(); front_buffer = false; if base_idx >= self.data_2.len() { return None; } } Some((base_idx, front_buffer)) } pub fn get_byte(&self, idx: usize) -> Option { let (base_idx, front_buffer) = self.get_idx_info(idx)?; let shift = idx % 4; let shift_amt = 8 * shift; if front_buffer { Some((self.data_1[base_idx] >> shift_amt) as u8) } else { Some((self.data_2[base_idx] >> shift_amt) as u8) } } pub fn set_byte(&mut self, idx: usize, val: u8) -> Option<()> { let (base_idx, front_buffer) = self.get_idx_info(idx)?; let shift = idx % 4; let shift_amt = 8 * shift; if front_buffer { self.data_1[base_idx] &= !(0xFF << shift_amt); self.data_1[base_idx] |= ((val as u32) << shift_amt) & (0xFF << shift_amt); } else { self.data_2[base_idx] &= !(0xFF << shift_amt); self.data_2[base_idx] |= ((val as u32) << shift_amt) & (0xFF << shift_amt); } Some(()) } } #[derive(Debug)] pub struct FrameBuffer { fb_type: FrameBufferFileType, cursor: u32, } impl Read for FrameBuffer { fn read(&mut self, buf: &mut [u8]) -> std::io::Result { let fb_state = get_fb_state(); let cursor = self.cursor as usize; match self.fb_type { FrameBufferFileType::Buffer => { let mut bytes_copied = 0; for i in 0..buf.len() { if let Some(byte) = fb_state.get_byte(cursor + i) { buf[i] = byte; bytes_copied += 1; } else { break; } } self.cursor += bytes_copied; Ok(bytes_copied as usize) } FrameBufferFileType::Resolution => { let resolution_data = format!("{}x{}", fb_state.x_size, fb_state.y_size); let mut bytes = resolution_data.bytes().skip(cursor); let bytes_to_copy = std::cmp::min(buf.len(), bytes.clone().count()); for i in 0..bytes_to_copy { buf[i] = bytes.next().unwrap(); } self.cursor += bytes_to_copy as u32; Ok(bytes_to_copy) } FrameBufferFileType::IndexDisplay => { if buf.len() == 0 { Ok(0) } else { buf[0] = fb_state.front_buffer as u8 + b'0'; Ok(1) } } FrameBufferFileType::Input => { // do input unimplemented!() } } } fn read_to_end(&mut self, _buf: &mut Vec) -> std::io::Result { unimplemented!() } fn read_to_string(&mut self, _buf: &mut String) -> std::io::Result { unimplemented!() } fn read_exact(&mut self, _buf: &mut [u8]) -> std::io::Result<()> { unimplemented!() } } impl Seek for FrameBuffer { fn seek(&mut self, pos: SeekFrom) -> std::io::Result { match pos { SeekFrom::Current(offset) => { let result: std::io::Result = (self.cursor as i64) .checked_add(offset) .and_then(|v| v.try_into().ok()) .ok_or_else(|| std::io::Error::from(std::io::ErrorKind::InvalidInput)); if let Ok(n) = result { self.cursor = n as u32; } result } SeekFrom::Start(offset) => { self.cursor = offset as u32; Ok(offset) } SeekFrom::End(_) => unimplemented!("Seek from end not yet implemented"), } } } impl Write for FrameBuffer { fn write(&mut self, buf: &[u8]) -> std::io::Result { let mut fb_state = get_fb_state(); let cursor = self.cursor as usize; match self.fb_type { FrameBufferFileType::Buffer => { let mut bytes_copied = 0; for i in 0..buf.len() { if fb_state.set_byte(cursor + i, buf[i]).is_none() { // TODO: check if we should return an error here break; } bytes_copied += 1; } self.cursor += bytes_copied; Ok(bytes_copied as usize) } FrameBufferFileType::Resolution => { let resolution_data = format!("{}x{}", fb_state.x_size, fb_state.y_size); let mut byte_vec: Vec = resolution_data.bytes().collect(); let upper_limit = std::cmp::min(buf.len(), byte_vec.len() - cursor as usize); for i in 0..upper_limit { byte_vec[i] = buf[i]; } let mut parse_str = String::new(); for b in byte_vec.iter() { parse_str.push(*b as char); } let result: Vec<&str> = parse_str.split('x').collect(); if result.len() != 2 { return Ok(0); } if let Ok((n1, n2)) = result[0] .parse::() .and_then(|n1| result[1].parse::().map(|n2| (n1, n2))) { if fb_state.resize(n1, n2).is_some() { return Ok(upper_limit); } } Ok(0) } FrameBufferFileType::IndexDisplay => { if buf.len() == 0 { Ok(0) } else { match buf[0] { b'0' => fb_state.front_buffer = true, b'1' => fb_state.front_buffer = false, _ => (), } fb_state.draw(); Ok(1) } } FrameBufferFileType::Input => Ok(0), } } fn flush(&mut self) -> std::io::Result<()> { Ok(()) } fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> { self.write(buf).map(|_| ()) } fn write_fmt(&mut self, _fmt: std::fmt::Arguments) -> std::io::Result<()> { unimplemented!() } } impl WasiFile for FrameBuffer { fn last_accessed(&self) -> u64 { 0 } fn last_modified(&self) -> u64 { 0 } fn created_time(&self) -> u64 { 0 } fn size(&self) -> u64 { 0 } } pub fn initialize(fs: &mut WasiFs) -> Result<(), String> { let frame_buffer_file = Box::new(FrameBuffer { fb_type: FrameBufferFileType::Buffer, cursor: 0, }); let resolution_file = Box::new(FrameBuffer { fb_type: FrameBufferFileType::Resolution, cursor: 0, }); let index_file = Box::new(FrameBuffer { fb_type: FrameBufferFileType::IndexDisplay, cursor: 0, }); let input_file = Box::new(FrameBuffer { fb_type: FrameBufferFileType::Input, cursor: 0, }); let dev_fd = unsafe { fs.open_dir_all( VIRTUAL_ROOT_FD, "dev".to_string(), ALL_RIGHTS, ALL_RIGHTS, 0, ) .map_err(|e| format!("fb: Failed to create dev folder {:?}", e))? }; let fb_fd = unsafe { fs.open_dir_all( VIRTUAL_ROOT_FD, "sys/class/graphics/wasmerfb".to_string(), ALL_RIGHTS, ALL_RIGHTS, 0, ) .map_err(|e| format!("fb: Failed to create dev folder {:?}", e))? }; let fd = fs .open_file_at( dev_fd, input_file, "input".to_string(), ALL_RIGHTS, ALL_RIGHTS, 0, ) .map_err(|e| format!("fb: Failed to init framebuffer {:?}", e))?; println!("Input open on fd {}", fd); let fd = fs .open_file_at( dev_fd, frame_buffer_file, "wasmerfb0".to_string(), ALL_RIGHTS, ALL_RIGHTS, 0, ) .map_err(|e| format!("fb: Failed to init framebuffer {:?}", e))?; println!("Framebuffer open on fd {}", fd); let fd = fs .open_file_at( fb_fd, resolution_file, "virtual_size".to_string(), ALL_RIGHTS, ALL_RIGHTS, 0, ) .map_err(|e| format!("fb_resolution: Failed to init framebuffer {:?}", e))?; println!("Framebuffer resolution open on fd {}", fd); let fd = fs .open_file_at( fb_fd, index_file, "buffer_index_display".to_string(), ALL_RIGHTS, ALL_RIGHTS, 0, ) .map_err(|e| format!("fb_index_display: Failed to init framebuffer {:?}", e))?; println!("Framebuffer draw open on fd {}", fd); Ok(()) }