Skip to content

Instantly share code, notes, and snippets.

@FaberVitale
Created June 8, 2022 12:33
Show Gist options
  • Save FaberVitale/332798992cbf533cc8144329f15aacc0 to your computer and use it in GitHub Desktop.
Save FaberVitale/332798992cbf533cc8144329f15aacc0 to your computer and use it in GitHub Desktop.
Wasm4 framebuffer logic in rust
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