Skip to content

Commit

Permalink
Implement video/pixel fill DEO (#7)
Browse files Browse the repository at this point in the history
* Implement fill mode for pixel deo

* Refactor video device so it returns a single buffer, instead of layers

* Implement flip x and flip y for pixel DEO
  • Loading branch information
belen-albeza authored Jul 18, 2024
1 parent 93b1129 commit 86e8f7b
Show file tree
Hide file tree
Showing 8 changed files with 167 additions and 44 deletions.
3 changes: 2 additions & 1 deletion coco-ui/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ <h1>👻-8</h1>
<select name="rom" id="coco-rom-selector">
<option value="empty.rom">Empty</option>
<option value="deo_system_debug.rom">Debug</option>
<option value="put_pixel.rom">Pixel</option>
<option value="put_pixel.rom">Pixel (single)</option>
<option value="pixel_fill.rom">Pixel (fill)</option>
</select>

<div>
Expand Down
2 changes: 1 addition & 1 deletion coco-ui/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ async function main() {
const _ = await initWasm("./vendor/coco_ui_bg.wasm");
const romSelector = document.querySelector("#coco-rom-selector");

const defaultRom = "put_pixel.rom";
const defaultRom = "pixel_fill.rom";
setupRomSelector(romSelector, defaultRom);
setupControls();

Expand Down
Binary file added coco-ui/roms/pixel_fill.rom
Binary file not shown.
Binary file modified coco-ui/roms/put_pixel.rom
Binary file not shown.
31 changes: 12 additions & 19 deletions coco-ui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::rc::Rc;
use wasm_bindgen::prelude::*;

use coco_core::Cpu;
use coco_vm::{Vm, SCREEN_HEIGHT, SCREEN_WIDTH};
use coco_vm::{VideoBuffer, Vm, SCREEN_HEIGHT, SCREEN_WIDTH, VIDEO_BUFFER_LEN};

#[wasm_bindgen(getter_with_clone)]
#[derive(Debug)]
Expand All @@ -13,8 +13,8 @@ pub struct Output {

pub type Result<T> = core::result::Result<T, JsValue>;

pub type DisplayBuffer = [u8; SCREEN_WIDTH * SCREEN_HEIGHT * 4];
pub type DeviceBuffer = [u8; SCREEN_WIDTH * SCREEN_HEIGHT];
pub type DisplayBuffer = [u8; VIDEO_BUFFER_LEN * 4];
pub type DeviceBuffer = VideoBuffer;
pub type RGB = (u8, u8, u8);

const THEME: [RGB; 0x10] = [
Expand All @@ -38,13 +38,6 @@ const THEME: [RGB; 0x10] = [

#[wasm_bindgen(js_name=runRom)]
pub fn run_rom(rom: &[u8]) -> Result<Output> {
web_sys::console::log_1(&JsValue::from(
rom.iter()
.map(|x| format!("{:02x}", x))
.collect::<Vec<String>>()
.join(" "),
));

let cpu = Rc::new(RefCell::new(Cpu::new(&rom)));
let vm = Rc::new(RefCell::new(Vm::new()));

Expand All @@ -56,12 +49,12 @@ pub fn run_rom(rom: &[u8]) -> Result<Output> {
let g = f.clone();

let ctx = canvas_context();
let mut canvas_buffer: DisplayBuffer = [0; SCREEN_WIDTH * SCREEN_HEIGHT * 4];
render(&vm.borrow(), &ctx, &mut canvas_buffer);
let mut canvas_buffer: DisplayBuffer = [0; VIDEO_BUFFER_LEN * 4];
render(&mut vm.borrow_mut(), &ctx, &mut canvas_buffer);

*g.borrow_mut() = Some(Closure::new(move || {
vm.borrow_mut().on_video(&mut cpu.borrow_mut());
render(&vm.borrow(), &ctx, &mut canvas_buffer);
render(&mut vm.borrow_mut(), &ctx, &mut canvas_buffer);
request_animation_frame(f.borrow().as_ref().unwrap())
}));

Expand All @@ -72,19 +65,19 @@ pub fn run_rom(rom: &[u8]) -> Result<Output> {
})
}

fn render(vm: &Vm, ctx: &web_sys::CanvasRenderingContext2d, buffer: &mut DisplayBuffer) {
let (bg, fg) = vm.pixels();
fn render(vm: &mut Vm, ctx: &web_sys::CanvasRenderingContext2d, buffer: &mut DisplayBuffer) {
let pixels = vm.pixels();

// update buffer and copy its pixel to the canvas
update_display_buffer(buffer, bg, fg);
update_display_buffer(buffer, pixels);
let image_data = image_data(buffer.as_slice(), SCREEN_WIDTH as u32, SCREEN_HEIGHT as u32);
ctx.put_image_data(&image_data, 0_f64, 0_f64)
.expect("Could not copy pixels to canvas");
}

fn update_display_buffer(buffer: &mut DisplayBuffer, bg: &DeviceBuffer, fg: &DeviceBuffer) {
for i in 0..(SCREEN_WIDTH * SCREEN_HEIGHT) {
let raw_color = if fg[i] == 0x00 { bg[i] } else { fg[i] };
fn update_display_buffer(buffer: &mut DisplayBuffer, pixels: &DeviceBuffer) {
for i in 0..VIDEO_BUFFER_LEN {
let raw_color = pixels[i];
let color = THEME[raw_color as usize];

let j = i * 4;
Expand Down
8 changes: 4 additions & 4 deletions coco-vm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ mod video;

use coco_core::{Cpu, Machine, Ports};
use system::{SystemDevice, SystemPorts};
use video::{VideoBuffer, VideoDevice, VideoPorts};
use video::{VideoDevice, VideoPorts};

pub use video::{SCREEN_HEIGHT, SCREEN_WIDTH};
pub use video::{VideoBuffer, SCREEN_HEIGHT, SCREEN_WIDTH, VIDEO_BUFFER_LEN};

trait Device {
#[allow(dead_code)]
Expand Down Expand Up @@ -65,8 +65,8 @@ impl Vm {
self.output()
}

pub fn pixels(&self) -> (&VideoBuffer, &VideoBuffer) {
(&self.video.background, &self.video.foreground)
pub fn pixels(&mut self) -> &VideoBuffer {
&self.video.pixels()
}

pub fn output(&mut self) -> DeviceOutput {
Expand Down
77 changes: 60 additions & 17 deletions coco-vm/src/video.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,54 +18,97 @@ impl VideoPorts {
const PIXEL: u8 = 0x04;
}

pub const SCREEN_WIDTH: usize = 192;
pub const SCREEN_HEIGHT: usize = 144;
const VIDEO_BUFFER_LEN: usize = SCREEN_WIDTH * SCREEN_HEIGHT;
pub const SCREEN_WIDTH: u8 = 192;
pub const SCREEN_HEIGHT: u8 = 144;
pub const VIDEO_BUFFER_LEN: usize = SCREEN_WIDTH as usize * SCREEN_HEIGHT as usize;

pub type Pixel = u8;
pub type VideoBuffer = [Pixel; VIDEO_BUFFER_LEN];

#[derive(Debug)]
pub struct VideoDevice {
pub background: [Pixel; VIDEO_BUFFER_LEN],
pub foreground: [Pixel; VIDEO_BUFFER_LEN],
pub layers: [VideoBuffer; 2],
is_dirty: bool,
buffer: VideoBuffer,
}

impl VideoDevice {
pub fn new() -> Self {
Self {
background: [0x00; VIDEO_BUFFER_LEN],
foreground: [0x00; VIDEO_BUFFER_LEN],
layers: [[0x00; VIDEO_BUFFER_LEN]; 2],
is_dirty: true,
buffer: [0x00; VIDEO_BUFFER_LEN],
}
}

pub fn pixels(&mut self) -> &VideoBuffer {
if std::mem::take(&mut self.is_dirty) {
self.refresh_buffer();
}

return &self.buffer;
}

fn refresh_buffer(&mut self) {
for i in 0..self.buffer.len() {
self.buffer[i] = if self.layers[0x01][i] != 0x00 {
self.layers[0x01][i]
} else {
self.layers[0x00][i]
}
}
}

#[inline]
fn xy(&self, ports: &mut [u8]) -> (u8, u8) {
(ports[VideoPorts::X as usize], ports[VideoPorts::Y as usize])
let x = cmp::min(ports[VideoPorts::X as usize], (SCREEN_WIDTH - 1) as u8);
let y = cmp::min(ports[VideoPorts::Y as usize], (SCREEN_HEIGHT - 1) as u8);
(x, y)
}

fn deo_pixel(&mut self, cpu: &mut Cpu) {
self.is_dirty = true;

let ports = cpu.device_page::<VideoPorts>();
let pixel = ports[VideoPorts::PIXEL as usize];

let (x, y) = self.xy(ports);
let color = pixel & 0x0f;
let layer = (pixel & 0b0001_0000) >> 4;
let is_fill = ((pixel & 0b0010_0000) >> 5) == 0x01;

self.put_pixel(x, y, color, layer == 0x01);
if is_fill {
let is_flip_x = ((pixel & 0b1000_0000) >> 7) == 0x01;
let is_flip_y = ((pixel & 0b0100_0000) >> 6) == 0x01;
self.fill(x, y, color, layer, is_flip_x, is_flip_y);
} else {
self.put_pixel(x, y, color, layer);
}
}

fn put_pixel(&mut self, x: u8, y: u8, color: u8, is_foreground: bool) {
let x = cmp::min(x as usize, SCREEN_WIDTH - 1);
let y = cmp::min(y as usize, SCREEN_HEIGHT - 1);
let i = y * SCREEN_WIDTH + x;
fn fill(&mut self, x: u8, y: u8, color: Pixel, layer: u8, is_flip_x: bool, is_flip_y: bool) {
let start_x = if is_flip_x { 0 } else { x };
let end_x = if is_flip_x { x } else { SCREEN_WIDTH - 1 };
let start_y = if is_flip_y { 0 } else { y };
let end_y = if is_flip_y { y } else { SCREEN_HEIGHT - 1 };

if is_foreground {
self.foreground[i] = color;
} else {
self.background[i] = color;
for col in start_x..=end_x {
for row in start_y..=end_y {
self.put_pixel(col, row, color, layer);
}
}
}

#[inline]
fn put_pixel(&mut self, x: u8, y: u8, color: u8, layer: u8) {
let i = y as usize * SCREEN_WIDTH as usize + x as usize;
self.layer(layer)[i] = color;
}

#[inline]
fn layer(&mut self, i: u8) -> &mut VideoBuffer {
&mut self.layers[i as usize]
}
}

impl Device for VideoDevice {
Expand Down
90 changes: 88 additions & 2 deletions coco-vm/tests/vm_cpu_test.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use coco_core::opcodes::*;
use coco_core::Cpu;
use coco_vm::SCREEN_HEIGHT;
use coco_vm::VIDEO_BUFFER_LEN;
use coco_vm::{Vm, SCREEN_WIDTH};

#[test]
Expand All @@ -23,7 +25,91 @@ fn test_deo_video_pixel_put() {
let mut vm = Vm::new();

let _ = vm.on_reset(&mut cpu);
let (bg, _) = vm.pixels();
let buffer = vm.pixels();

assert_eq!(bg[0x01 * SCREEN_WIDTH + 0x01], 0x08);
assert_eq!(buffer[0x01 * SCREEN_WIDTH as usize + 0x01], 0x08);
}

#[test]
fn test_deo_video_pixel_fill() {
let rom = [
PUSH,
0x60,
PUSH,
0x12,
DEO, // x = 96
PUSH,
0x48,
PUSH,
0x13,
DEO, // y = 72
PUSH,
0b0010_0001,
PUSH,
0x14,
DEO, // fill bg with color 0x01
BRK,
];
let mut cpu = Cpu::new(&rom);
let mut vm = Vm::new();

let _ = vm.on_reset(&mut cpu);
let buffer = vm.pixels();

assert_eq!(
buffer[0x00..VIDEO_BUFFER_LEN / 2],
[0x00; VIDEO_BUFFER_LEN / 2]
);
let expected_slice = [
[0x00; SCREEN_WIDTH as usize / 2],
[0x01; SCREEN_WIDTH as usize / 2],
]
.concat();
for y in (SCREEN_HEIGHT / 2)..SCREEN_HEIGHT {
let i = y as usize * SCREEN_WIDTH as usize;
assert_eq!(buffer[i..(i + SCREEN_WIDTH as usize)], expected_slice);
}
}

#[test]
fn test_deo_video_pixel_fill_with_flip() {
let rom = [
PUSH,
0x60,
PUSH,
0x12,
DEO, // x = 96
PUSH,
0x48,
PUSH,
0x13,
DEO, // y = 72
PUSH,
0b1110_0001,
PUSH,
0x14,
DEO, // fill top left quadrant of bg with color 0x01
BRK,
];
let mut cpu = Cpu::new(&rom);
let mut vm = Vm::new();

let _ = vm.on_reset(&mut cpu);
let buffer = vm.pixels();

let expected_slice = [
vec![0x01_u8; 1 + SCREEN_WIDTH as usize / 2],
vec![0x00_u8; SCREEN_WIDTH as usize / 2 - 1],
]
.concat();
for y in 0..=SCREEN_HEIGHT / 2 {
let i = y as usize * SCREEN_WIDTH as usize;
assert_eq!(buffer[i..(i + SCREEN_WIDTH as usize)], expected_slice);
}

const IDX: usize = (1 + SCREEN_HEIGHT as usize / 2) * SCREEN_WIDTH as usize;
assert_eq!(
buffer[IDX..VIDEO_BUFFER_LEN],
[0x00; VIDEO_BUFFER_LEN - IDX]
);
}

0 comments on commit 86e8f7b

Please sign in to comment.