Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement video/pixel fill DEO #7

Merged
merged 3 commits into from
Jul 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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]
);
}