Created
June 8, 2022 12:33
-
-
Save FaberVitale/332798992cbf533cc8144329f15aacc0 to your computer and use it in GitHub Desktop.
Wasm4 framebuffer logic in rust
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use wasmer::Memory; | |
use crate::config::{self, memory::FRAMEBUFFER}; | |
use std::{cell::Cell, cmp}; | |
const SCREEN_WIDTH_I32: i32 = config::WIDTH as i32; | |
const SCREEN_HEIGHT_I32: i32 = config::HEIGHT as i32; | |
pub(crate) trait Extract<T> { | |
fn extract(&self) -> T; | |
} | |
impl<T: Copy> Extract<T> for Cell<T> { | |
fn extract(&self) -> T { | |
self.get() | |
} | |
} | |
impl<T: Copy> Extract<T> for std::cell::RefCell<T> { | |
fn extract(&self) -> T { | |
*self.borrow() | |
} | |
} | |
impl<T: Copy> Extract<T> for T { | |
fn extract(&self) -> T { | |
*self | |
} | |
} | |
#[inline] | |
fn clamp<T: PartialOrd>(val: T, min: T, max: T) -> T { | |
debug_assert!(min <= max, "min must be less than or equal to max"); | |
if val < min { | |
min | |
} else if val > max { | |
max | |
} else { | |
val | |
} | |
} | |
#[inline] | |
pub(crate) fn handle_framebuffer_memory_error(func_name: &str) { | |
log::error!(target: "framebuffer", "memory slice error: {}", func_name); | |
} | |
#[inline] | |
fn stroke_color(draw_colors: &[Cell<u8>]) -> u8 { | |
let dc0 = draw_colors[0].get() & 0xf; | |
match dc0 { | |
0 => 0, | |
_ => (dc0 - 1) & 0x3, | |
} | |
} | |
#[inline] | |
fn draw_hline_internal(framebuffer: &[Cell<u8>], color: u8, start_x: i32, y: i32, end_x: i32) { | |
for xx in start_x..end_x { | |
draw_point(framebuffer, color, xx, y); | |
} | |
} | |
pub(crate) fn draw_hline( | |
framebuffer: &[Cell<u8>], | |
draw_colors: &[Cell<u8>], | |
x: i32, | |
y: i32, | |
len: u32, | |
) { | |
let len_i32 = cmp::min(len, i32::MAX as u32) as i32; | |
let stroke_color = stroke_color(draw_colors); | |
if x + len_i32 <= 0 || y < 0 || y >= SCREEN_HEIGHT_I32 || stroke_color == 0 { | |
return; | |
} | |
let start_x = cmp::max(0, x); | |
let end_x = cmp::min(SCREEN_WIDTH_I32, x + len_i32); | |
draw_hline_internal(framebuffer, stroke_color, start_x, y, end_x); | |
} | |
pub(crate) fn draw_vline( | |
framebuffer: &[Cell<u8>], | |
draw_colors: &[Cell<u8>], | |
x: i32, | |
y: i32, | |
len: u32, | |
) { | |
let len_i32 = cmp::min(len, i32::MAX as u32) as i32; | |
let stroke_color = stroke_color(draw_colors); | |
if y + len_i32 <= 0 || x < 0 || x >= SCREEN_WIDTH_I32 || stroke_color == 0 { | |
return; | |
} | |
let start_y = cmp::max(0, y); | |
let end_y = cmp::min(SCREEN_HEIGHT_I32, y + len_i32); | |
for yy in start_y..end_y { | |
draw_point(framebuffer, stroke_color, x, yy); | |
} | |
} | |
pub(crate) fn clear(framebuffer: &[Cell<u8>]) { | |
for cell in framebuffer.into_iter() { | |
cell.set(0); | |
} | |
} | |
pub(crate) fn copy_and_clear(framebuffer: &[Cell<u8>]) -> [u8; FRAMEBUFFER.len] { | |
let mut output = [0u8; FRAMEBUFFER.len]; | |
for idx in 0..output.len() { | |
output[idx] = framebuffer[idx].replace(0u8); | |
} | |
output | |
} | |
pub(crate) fn draw_point(framebuffer: &[Cell<u8>], color: u8, x: i32, y: i32) { | |
let idx = (config::WIDTH * y as usize + x as usize) >> 2; | |
let shift = (x as u8 & 0x3) << 1; | |
let mask = 0x3 << shift; | |
let pixel = framebuffer[idx].get(); | |
framebuffer[idx].set((color << shift) | (pixel & !mask)); | |
} | |
pub(crate) fn draw_point_unclipped(framebuffer: &[Cell<u8>], color: u8, x: i32, y: i32) { | |
if x >= 0 && (x as usize) < config::WIDTH && y >= 0 && (y as usize) < config::HEIGHT { | |
draw_point(framebuffer, color, x, y); | |
} | |
} | |
pub(crate) fn draw_point_safe(memory: &Memory, color: u32, x: i32, y: i32) { | |
match FRAMEBUFFER.as_memory_slice::<u8>(memory) { | |
Some(framebuffer) => { | |
if x >= 0 && (x as usize) < config::WIDTH && y >= 0 && (y as usize) < config::HEIGHT { | |
draw_point(framebuffer, color as u8, x, y); | |
} | |
} | |
_ => handle_framebuffer_memory_error("draw_point_safe"), | |
} | |
} | |
pub(crate) fn draw_line( | |
framebuffer: &[Cell<u8>], | |
draw_colors: &[Cell<u8>], | |
mut x1: i32, | |
mut y1: i32, | |
mut x2: i32, | |
mut y2: i32, | |
) { | |
let dc0 = draw_colors[0].get(); | |
if dc0 > 0 { | |
let stroke_color = (dc0 - 1) & 0x3; | |
if y1 > y2 { | |
let mut swap = x1; | |
x1 = x2; | |
x2 = swap; | |
swap = y1; | |
y1 = y2; | |
y2 = swap; | |
} | |
let dx = (x2 - x1).abs(); | |
let sx = if x1 < x2 { 1 } else { -1 }; | |
let dy = y2 - y1; | |
let mut e2; | |
let mut err = if dx > dy { dx } else { -dy }; | |
loop { | |
draw_point_unclipped(framebuffer, stroke_color, x1, y1); | |
if x1 == x2 && y1 == y2 { | |
break; | |
} | |
e2 = err; | |
if e2 > -dx { | |
err -= dy; | |
x1 += sx; | |
} | |
if e2 < dy { | |
err += dx; | |
y1 += 1; | |
} | |
} | |
} | |
} | |
pub(crate) fn draw_rect( | |
framebuffer: &[Cell<u8>], | |
draw_colors: &[Cell<u8>], | |
mut x: i32, | |
mut y: i32, | |
width: u32, | |
height: u32, | |
) { | |
x = clamp(x, 0, config::WIDTH as i32); | |
y = clamp(y, 0, config::HEIGHT as i32); | |
let start_x = cmp::max(0, x); | |
let start_y = cmp::max(0, y); | |
let end_x = cmp::min(x as u32 + width, config::WIDTH as u32) as i32; | |
let end_y = cmp::min(y as u32 + height, config::HEIGHT as u32) as i32; | |
let dc0 = draw_colors[0].get(); | |
let dc1 = draw_colors[1].get(); | |
if dc0 != 0 { | |
let fill_color = (dc0 - 1) & 0x3; | |
for y_fill in start_y..end_y { | |
for x_fill in start_x..end_x { | |
draw_point(framebuffer, fill_color as u8, x_fill as i32, y_fill as i32); | |
} | |
} | |
} | |
if dc1 != 0 { | |
let stroke_color = (dc1 - 1) & 0x3; | |
// Left edge | |
if x >= 0 && x < SCREEN_WIDTH_I32 { | |
for y_left in start_y..end_y { | |
draw_point(framebuffer, stroke_color, x, y_left); | |
} | |
} | |
// Right edge | |
if end_x > 0 && end_x < (SCREEN_WIDTH_I32 + 1) { | |
for y_right in start_y..end_y { | |
draw_point(framebuffer, stroke_color, end_x - 1, y_right); | |
} | |
} | |
// Top edge | |
if y >= 0 && y < SCREEN_HEIGHT_I32 { | |
for x_top in start_x..end_x { | |
draw_point(framebuffer, stroke_color, x_top, y); | |
} | |
} | |
// Botton edge | |
if end_y > 0 && end_y < SCREEN_HEIGHT_I32 + 1 { | |
for x_bottom in start_x..end_x { | |
draw_point(framebuffer, stroke_color, x_bottom, end_y - 1); | |
} | |
} | |
} | |
} | |
pub(crate) fn draw_text<T>( | |
framebuffer: &[Cell<u8>], | |
draw_colors: &[Cell<u8>], | |
text: T, | |
x: i32, | |
mut y: i32, | |
) where | |
T: std::fmt::Debug + Iterator<Item = u8>, | |
{ | |
let mut temp_x = x; | |
for char_code in text { | |
match char_code { | |
0 => return, | |
10 => { | |
y += 8; | |
temp_x = x; | |
} | |
_ => { | |
if char_code >= 32 { | |
draw_blit( | |
framebuffer, | |
draw_colors, | |
&crate::config::font::FONT, | |
temp_x, | |
y, | |
8, | |
8, | |
0, | |
((char_code - 32) << 3).into(), | |
8, | |
0, | |
); | |
} | |
temp_x += 8; | |
} | |
} | |
} | |
} | |
pub(crate) fn draw_blit( | |
framebuffer: &[Cell<u8>], | |
draw_colors: &[Cell<u8>], | |
sprite: &[impl Extract<u8>], | |
dst_x: i32, | |
dst_y: i32, | |
width: u32, | |
height: u32, | |
src_x: u32, | |
src_y: u32, | |
src_stride: u32, | |
flags: u32, | |
) { | |
let i32_max = i32::MAX as u32; | |
let width_i32 = cmp::min(width, i32_max) as i32; | |
let height_i32 = cmp::min(height, i32_max) as i32; | |
let src_x_i32 = cmp::min(src_x, i32_max) as i32; | |
let src_y_i32 = cmp::min(src_y, i32_max) as i32; | |
let src_stride_i32 = cmp::min(src_stride, i32_max) as i32; | |
let clip_x_min = cmp::max(0, dst_x) - dst_x; | |
let clip_y_min = cmp::max(0, dst_y) - dst_y; | |
let clip_x_max = cmp::min(width_i32, SCREEN_WIDTH_I32 - dst_x); | |
let clip_y_max = cmp::min(height_i32, SCREEN_HEIGHT_I32 - dst_y); | |
let draw_color = draw_colors[0].get(); | |
let rotate = config::BlitFlags::ROTATE.bits() & flags != 0; | |
let flip_x = config::BlitFlags::FLIP_X.bits() & flags != 0; | |
let flip_x = if rotate { !flip_x } else { flip_x }; | |
let flip_y = config::BlitFlags::FLIP_Y.bits() & flags != 0; | |
let bpp2 = config::BlitFlags::BPP2.bits() & flags != 0; | |
for row in clip_y_min..clip_y_max { | |
for col in clip_x_min..clip_x_max { | |
let (mut sx, mut sy) = if rotate { (row, col) } else { (col, row) }; | |
if flip_x { | |
sx = clip_x_max - sx - 1; | |
} | |
if flip_y { | |
sy = clip_y_max - sy - 1; | |
} | |
let x = src_x_i32 + sx; | |
let y = src_y_i32 + sy; | |
// Sample the sprite to get a color index | |
let color_idx: u8 = if bpp2 { | |
let offset = (y * src_stride_i32 + x) >> 2; | |
let byte = sprite[offset as usize].extract(); | |
let shift = 6 - ((x & 0x03) << 1); | |
(byte >> shift) & 0b11 | |
} else { | |
let offset = (y * src_stride_i32 + x) >> 3; | |
let byte = sprite[offset as usize].extract(); | |
let shift = 7 - (x & 0x07); | |
(byte >> shift) & 0b1 | |
}; | |
let dc = (draw_color >> (color_idx << 2)) & 0x0f; | |
if dc != 0 { | |
draw_point(framebuffer, (dc - 1) & 0x03, dst_x + col, dst_y + row); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment