diff --git a/coco-ui/index.html b/coco-ui/index.html index 9d90628..677479a 100644 --- a/coco-ui/index.html +++ b/coco-ui/index.html @@ -16,6 +16,7 @@

👻-8

diff --git a/coco-ui/index.js b/coco-ui/index.js index 011d6d8..46c9320 100644 --- a/coco-ui/index.js +++ b/coco-ui/index.js @@ -37,7 +37,7 @@ async function main() { const _ = await initWasm("./vendor/coco_ui_bg.wasm"); const romSelector = document.querySelector("#coco-rom-selector"); - const defaultRom = "deo_system_debug.rom"; + const defaultRom = "put_pixel.rom"; await setupRomSelector(romSelector, defaultRom); const rom = await fetchRom(`/roms/${defaultRom}`); diff --git a/coco-ui/roms/put_pixel.rom b/coco-ui/roms/put_pixel.rom new file mode 100644 index 0000000..be32c9a Binary files /dev/null and b/coco-ui/roms/put_pixel.rom differ diff --git a/coco-ui/src/lib.rs b/coco-ui/src/lib.rs index 99d4a74..7d3a2e2 100644 --- a/coco-ui/src/lib.rs +++ b/coco-ui/src/lib.rs @@ -57,6 +57,8 @@ pub fn run_rom(rom: &[u8]) -> Result { let ctx = canvas_context(); let mut canvas_buffer: DisplayBuffer = [0; SCREEN_WIDTH * SCREEN_HEIGHT * 4]; + render(&vm.borrow(), &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); @@ -73,10 +75,6 @@ pub fn run_rom(rom: &[u8]) -> Result { fn render(vm: &Vm, ctx: &web_sys::CanvasRenderingContext2d, buffer: &mut DisplayBuffer) { let (bg, fg) = vm.pixels(); - // clear background - ctx.set_fill_style(&JsValue::from("#000000")); - ctx.fill_rect(0.0, 0.0, SCREEN_WIDTH as f64, SCREEN_HEIGHT as f64); - // update buffer and copy its pixel to the canvas update_display_buffer(buffer, bg, fg); let image_data = image_data(buffer.as_slice(), SCREEN_WIDTH as u32, SCREEN_HEIGHT as u32); diff --git a/coco-vm/README.md b/coco-vm/README.md new file mode 100644 index 0000000..c2043cc --- /dev/null +++ b/coco-vm/README.md @@ -0,0 +1,81 @@ +# coco-vm + +The COCO-8 virtual machine is built on top of a COCO-8 CPU and uses devices in the fashion of [Varvara](https://wiki.xxiivv.com/site/varvara.html) and NES. + +The COCO-8 CPU has a 256-byte device page, that contains 16 devices with 16 bytes for ports. Some of the ports take just one byte, but others take a short (2 bytes). + +| Address | Device | +| ------- | ------------------------ | +| `0x00` | [System](#system-device) | +| `0x10` | Video | + +## System device + + + + + +
0x00unused*
0x01
0x02debug
+ +Instead of a customizable vector, the system is always to be assumed to have `0x100` as the address of its vector (which is the reset vector and it's called when the ROM is booted). + +Sending a non-zero byte to the **`debug` port** will ouput CPU debug information. + +## Video device + + + + + + + + + + +
0x10vector*0x18address*
0x110x19
0x12x0x1asprite
0x13y0x1b--
0x14pixel0x1c--
0x15read0x1d--
0x16--0x1e--
0x17--0x1f--
+ +The **screen vector\*** is called at a rate of 60 fps, and it's meant to run any drawing operations. + +The ports `x*` and `y*` contain the X and Y coordinates used by the drawing or buffer reading operations: `pixel`, `read` and `sprite`. + +The **`pixel` port** is used to put pixels into the video buffer. It follows this layout: + + + + + + + + + + + + + + + + + + + +
76543210
flip xflip yfilllayercolor
+ +- `color` is an index in the 16-color COCO-8 palette, and can range from `0` to `f`. +- `layer` indicates the layer to put the pixel in; background is `0` and foreground is `1`. Color `0` in the foreground will be considered transparent. +- `fill` sets wether to draw a single pixel, `0`, or to use the `x` and `y` coordinates as the position of a rectangle to fill, `1`. By default it takes the bottom right quadrant. +- `flip x` will use a quadrant on the left for filling. It has no effect when `fill` is `0`. +- `flip y` will use a quadrant on the top for filling. It has no effect when `fill` is `0`. + +Some examples: + +```uxn +PUSH 60 PUSH 12 DEO # x = 0x60 +PUSH 48 PUSH 13 DEO # y = 0x48 +PUSH 18 PUSH DEO # put pixel with color 0x8 in the foreground +``` + +```uxn +PUSH 00 PUSH 12 DEO # x = 0x00 +PUSH 00 PUSH 13 DEO # y = 0x00 +PUSH 30 PUSH 13 DEO # fills the foreground with transparent color +``` diff --git a/coco-vm/src/lib.rs b/coco-vm/src/lib.rs index bc55f2d..1e491b0 100644 --- a/coco-vm/src/lib.rs +++ b/coco-vm/src/lib.rs @@ -1,13 +1,14 @@ mod system; mod video; -use coco_core::{Cpu, Machine}; -use system::SystemDevice; -use video::{VideoBuffer, VideoDevice}; +use coco_core::{Cpu, Machine, Ports}; +use system::{SystemDevice, SystemPorts}; +use video::{VideoBuffer, VideoDevice, VideoPorts}; pub use video::{SCREEN_HEIGHT, SCREEN_WIDTH}; trait Device { + #[allow(dead_code)] fn dei(&mut self, cpu: &mut Cpu, target: u8); fn deo(&mut self, cpu: &mut Cpu, target: u8); } @@ -34,10 +35,12 @@ pub struct Vm { } impl Machine for Vm { - fn dei(&mut self, cpu: &mut Cpu, target: u8) {} + fn dei(&mut self, _: &mut Cpu, _: u8) {} fn deo(&mut self, cpu: &mut Cpu, target: u8) { + let offset = target & 0x0f; match target & 0xf0 { - 0x00 => self.system.deo(cpu, target), + SystemPorts::BASE => self.system.deo(cpu, offset), + VideoPorts::BASE => self.video.deo(cpu, offset), _ => unimplemented!(), } } diff --git a/coco-vm/src/system.rs b/coco-vm/src/system.rs index d72ce2c..2f9a876 100644 --- a/coco-vm/src/system.rs +++ b/coco-vm/src/system.rs @@ -51,9 +51,9 @@ impl Device for SystemDevice { match target { SystemPorts::VECTOR => {} SystemPorts::DEBUG => self.debug(cpu), - _ => panic!("Unimplemented device port {}", target), + _ => {} } } - fn dei(&mut self, cpu: &mut coco_core::Cpu, target: u8) {} + fn dei(&mut self, _: &mut coco_core::Cpu, _: u8) {} } diff --git a/coco-vm/src/video.rs b/coco-vm/src/video.rs index f1b7c2a..9b0fe6b 100644 --- a/coco-vm/src/video.rs +++ b/coco-vm/src/video.rs @@ -1,3 +1,23 @@ +use core::cmp; + +use super::Device; +use coco_core::{Cpu, Ports}; + +#[derive(Debug)] +pub struct VideoPorts {} + +impl Ports for VideoPorts { + const BASE: u8 = 0x10; +} + +impl VideoPorts { + #[allow(dead_code)] + const VECTOR: u8 = 0x00; + const X: u8 = 0x02; + const Y: u8 = 0x03; + 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; @@ -13,13 +33,50 @@ pub struct VideoDevice { impl VideoDevice { pub fn new() -> Self { - let mut buffer = [0x00 as Pixel; VIDEO_BUFFER_LEN]; - for i in 0..VIDEO_BUFFER_LEN { - buffer[i] = (i % 0x10) as Pixel; - } Self { background: [0x00; VIDEO_BUFFER_LEN], - foreground: buffer, + foreground: [0x00; VIDEO_BUFFER_LEN], + } + } + + #[inline] + fn xy(&self, ports: &mut [u8]) -> (u8, u8) { + (ports[VideoPorts::X as usize], ports[VideoPorts::Y as usize]) + } + + fn deo_pixel(&mut self, cpu: &mut Cpu) { + let ports = cpu.device_page::(); + let pixel = ports[VideoPorts::PIXEL as usize]; + + let (x, y) = self.xy(ports); + let color = pixel & 0x0f; + let layer = (pixel & 0b0001_0000) >> 4; + + self.put_pixel(x, y, color, layer == 0x01); + } + + 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; + + if is_foreground { + self.foreground[i] = color; + } else { + self.background[i] = color; + } + } +} + +impl Device for VideoDevice { + fn deo(&mut self, cpu: &mut Cpu, target: u8) { + match target { + VideoPorts::X => {} + VideoPorts::Y => {} + VideoPorts::PIXEL => self.deo_pixel(cpu), + _ => {} } } + + fn dei(&mut self, _: &mut Cpu, _: u8) {} } diff --git a/coco-vm/tests/vm_cpu_test.rs b/coco-vm/tests/vm_cpu_test.rs index 3436c56..0bb1123 100644 --- a/coco-vm/tests/vm_cpu_test.rs +++ b/coco-vm/tests/vm_cpu_test.rs @@ -1,6 +1,6 @@ use coco_core::opcodes::*; use coco_core::Cpu; -use coco_vm::Vm; +use coco_vm::{Vm, SCREEN_WIDTH}; #[test] fn test_deo_system_debug() { @@ -13,3 +13,17 @@ fn test_deo_system_debug() { let expected_sys_output = "WRK: [ff]\nRET: []".to_string(); assert_eq!(output.sys_stdout, expected_sys_output); } + +#[test] +fn test_deo_video_pixel_put() { + let rom = [ + PUSH, 0x01, PUSH, 0x12, DEO, PUSH, 0x01, PUSH, 0x13, DEO, PUSH, 0x08, PUSH, 0x14, DEO, BRK, + ]; + let mut cpu = Cpu::new(&rom); + let mut vm = Vm::new(); + + let _ = vm.on_reset(&mut cpu); + let (bg, _) = vm.pixels(); + + assert_eq!(bg[0x01 * SCREEN_WIDTH + 0x01], 0x08); +}