From 941c7935233009040e1eb327f25e7a04dc95bf9a Mon Sep 17 00:00:00 2001 From: transistor Date: Sat, 25 Mar 2023 10:51:46 -0700 Subject: [PATCH] Added pixel encoding, requested by frontend The hope was that this would reduce the amount of copying and bit shifting required by the frontend to get the data on screen, but it doesn't seem to offer much advantage, surprisingly. I'll leave it in though. There are a few other minor tweaks included here to try to improve the performance a bit --- docs/log.txt | 20 ++- emulator/core/src/host/gfx.rs | 129 +++++++------- emulator/core/src/host/traits.rs | 18 +- emulator/core/src/system.rs | 4 +- emulator/frontends/console/src/lib.rs | 7 +- emulator/frontends/minifb/src/lib.rs | 5 +- emulator/frontends/pixels/README.md | 5 + .../pixels/assets/moa-genesis/index.html | 4 +- .../pixels/assets/moa-genesis/interface.js | 6 +- emulator/frontends/pixels/src/frontend.rs | 22 ++- emulator/frontends/pixels/src/web.rs | 2 +- .../systems/genesis/src/peripherals/ym7101.rs | 157 +++++++++--------- .../macintosh/src/peripherals/video.rs | 2 +- .../systems/trs80/src/peripherals/model1.rs | 2 +- todo.txt | 7 +- 15 files changed, 206 insertions(+), 184 deletions(-) diff --git a/docs/log.txt b/docs/log.txt index a609ad2..778cd43 100644 --- a/docs/log.txt +++ b/docs/log.txt @@ -240,9 +240,6 @@ before 2021-10-25 to more properly implement the priority shadow/highlight modes -Audio ------ - 2021-12-12 - this is when I committed the audio support, but I'm not sure when I started. It was a little earlier - cpal uses a callback to get the next buffer of data, so the buffer needs to be assembled outside of @@ -262,7 +259,8 @@ Audio of minor issues have turned up 2022-01-18 -- some kind of buffer problem causing clicking, where the waveform resets, possibly related to circular buf +- some kind of buffer problem causing clicking, where the waveform resets, possibly related to circular + buf - a quick attempt at fixing it shows that the audio source buffer is only copied to the mixer buffer when it's written to the buffer (and overfills). Attempt to not write to the buffer means audio stops when the source buffer is full @@ -283,6 +281,20 @@ Audio so I don't think it's too far off +2023-03-25 +- trying to improve the performance. It seems to be at about 20 to 30 fps on firefox but 60 fps on + chrome and I'm not sure why the difference. +- I tried a change that allows the frontend to request a pixel encoding format so it doesn't have to + change it after the fact, but that doesn't seem to help much, or possibly even hurt performance a + bit, but it's hard to know with just flamegraph. + +- without knowing the tricks that games might use, it's hard to really optimize the ym7101 code, + which is taking up the most time per loop. The game might change the colour palette during the + update, for example. +- that said, I'm actually just updating the whole frame at a time instead of drawing lines + individually, with steps in between so the game can do those tricks... and I don't see a lot of + issues with colour glitches and stuff + --------------------------------------------------------------------------------------------------- diff --git a/emulator/core/src/host/gfx.rs b/emulator/core/src/host/gfx.rs index e7e108d..e85a627 100644 --- a/emulator/core/src/host/gfx.rs +++ b/emulator/core/src/host/gfx.rs @@ -1,4 +1,3 @@ -use std::mem; use std::sync::{Arc, Mutex}; use crate::host::traits::{BlitableSurface, ClockedQueue, WindowUpdater}; @@ -7,24 +6,61 @@ use crate::Error; pub const MASK_COLOUR: u32 = 0xFFFFFFFF; +#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)] +pub enum PixelEncoding { + #[default] + RGBA, + ARGB, + ABGR, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum Pixel { + Rgb(u8, u8, u8), + Rgba(u8, u8, u8, u8), + Mask, +} + +impl Pixel { + #[inline] + pub fn encode(self, encoding: PixelEncoding) -> u32 { + let (r, g, b, a) = match self { + Pixel::Rgb(r, g, b) => (r as u32, g as u32, b as u32, 255), + Pixel::Rgba(r, g, b, a) => (r as u32, g as u32, b as u32, a as u32), + Pixel::Mask => return MASK_COLOUR, + }; + + match encoding { + PixelEncoding::RGBA => + ((r as u32) << 24) | ((g as u32) << 16) | ((b as u32) << 8) | (a as u32), + PixelEncoding::ARGB => + ((a as u32) << 24) | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32), + PixelEncoding::ABGR => + ((a as u32) << 24) | ((b as u32) << 16) | ((g as u32) << 8) | (r as u32), + } + } +} + #[derive(Clone, Default)] pub struct Frame { pub width: u32, pub height: u32, + pub encoding: PixelEncoding, pub bitmap: Vec, } impl Frame { - pub fn new(width: u32, height: u32) -> Self { + pub fn new(width: u32, height: u32, encoding: PixelEncoding) -> Self { Self { width, height, + encoding, bitmap: vec![0; (width * height) as usize], } } - pub fn new_shared(width: u32, height: u32) -> Arc> { - Arc::new(Mutex::new(Frame::new(width, height))) + pub fn new_shared(width: u32, height: u32, encoding: PixelEncoding) -> Arc> { + Arc::new(Mutex::new(Frame::new(width, height, encoding))) } } @@ -35,31 +71,28 @@ impl BlitableSurface for Frame { self.bitmap.resize((width * height) as usize, 0); } - fn set_pixel(&mut self, pos_x: u32, pos_y: u32, pixel: u32) { + fn set_pixel(&mut self, pos_x: u32, pos_y: u32, pixel: Pixel) { match pixel { - MASK_COLOUR => {} + Pixel::Mask => {} value if pos_x < self.width && pos_y < self.height => { - self.bitmap[(pos_x + (pos_y * self.width)) as usize] = value; + self.bitmap[(pos_x + (pos_y * self.width)) as usize] = value.encode(self.encoding); } _ => {} } } - fn blit>(&mut self, pos_x: u32, pos_y: u32, mut bitmap: B, width: u32, height: u32) { - /* - (pos_y..(pos_y + height)) - .for_each(|y| { - self.bitmap[(y * self.width) as usize .. (y * self.width + self.width) as usize] - .iter_mut() - .for_each(|pixel| - match bitmap.next().unwrap() { - MASK_COLOUR => {}, - value => *pixel = value, - } - ) - }); - */ + #[inline] + fn set_encoded_pixel(&mut self, pos_x: u32, pos_y: u32, pixel: u32) { + match pixel { + MASK_COLOUR => { }, + value if pos_x < self.width && pos_y < self.height => { + self.bitmap[(pos_x + (pos_y * self.width)) as usize] = value; + }, + _ => { }, + } + } + fn blit>(&mut self, pos_x: u32, pos_y: u32, mut bitmap: B, width: u32, height: u32) { for y in pos_y..(pos_y + height) { for x in pos_x..(pos_x + width) { match bitmap.next().unwrap() { @@ -79,51 +112,10 @@ impl BlitableSurface for Frame { } } -#[derive(Clone)] -pub struct FrameSwapper { - pub current: Arc>, - pub previous: Arc>, -} - -impl FrameSwapper { - pub fn new(width: u32, height: u32) -> FrameSwapper { - FrameSwapper { - current: Arc::new(Mutex::new(Frame::new(width, height))), - previous: Arc::new(Mutex::new(Frame::new(width, height))), - } - } - - pub fn to_boxed(swapper: FrameSwapper) -> Box { - Box::new(swapper) - } - - pub fn swap(&mut self) { - std::mem::swap(&mut self.current.lock().unwrap().bitmap, &mut self.previous.lock().unwrap().bitmap); - } - - pub fn set_size(&mut self, width: u32, height: u32) { - self.previous.lock().unwrap().set_size(width, height); - self.current.lock().unwrap().set_size(width, height); - } -} - -impl WindowUpdater for FrameSwapper { - fn max_size(&mut self) -> (u32, u32) { - let frame = self.current.lock().unwrap(); - (frame.width, frame.height) - } - - fn take_frame(&mut self) -> Result { - let mut previous = self.previous.lock().map_err(|_| Error::new("Lock error"))?; - let mut frame = Frame::new(previous.width, previous.height); - mem::swap(&mut *previous, &mut frame); - Ok(frame) - } -} - #[derive(Clone)] pub struct FrameQueue { max_size: (u32, u32), + encoding: Arc>, queue: ClockedQueue, } @@ -131,10 +123,15 @@ impl FrameQueue { pub fn new(width: u32, height: u32) -> Self { Self { max_size: (width, height), + encoding: Arc::new(Mutex::new(PixelEncoding::RGBA)), queue: Default::default(), } } + pub fn encoding(&mut self) -> PixelEncoding { + *self.encoding.lock().unwrap() + } + pub fn add(&self, clock: Clock, frame: Frame) { self.queue.push(clock, frame); } @@ -145,10 +142,14 @@ impl FrameQueue { } impl WindowUpdater for FrameQueue { - fn max_size(&mut self) -> (u32, u32) { + fn max_size(&self) -> (u32, u32) { self.max_size } + fn request_encoding(&mut self, encoding: PixelEncoding) { + *self.encoding.lock().unwrap() = encoding; + } + fn take_frame(&mut self) -> Result { self.latest() .map(|(_, f)| f) diff --git a/emulator/core/src/host/traits.rs b/emulator/core/src/host/traits.rs index 11336dd..df74c57 100644 --- a/emulator/core/src/host/traits.rs +++ b/emulator/core/src/host/traits.rs @@ -3,7 +3,7 @@ use std::collections::VecDeque; use std::sync::{Arc, Mutex, MutexGuard}; use crate::{Clock, Error}; -use crate::host::gfx::Frame; +use crate::host::gfx::{PixelEncoding, Pixel, Frame}; use crate::host::keys::KeyEvent; use crate::host::controllers::{ControllerDevice, ControllerEvent}; use crate::host::mouse::MouseEvent; @@ -42,18 +42,9 @@ pub trait Tty { } pub trait WindowUpdater: Send { - fn max_size(&mut self) -> (u32, u32); + fn max_size(&self) -> (u32, u32); + fn request_encoding(&mut self, encoding: PixelEncoding); fn take_frame(&mut self) -> Result; - - fn update_frame(&mut self, width: u32, _height: u32, bitmap: &mut [u32]) { - if let Ok(frame) = self.take_frame() { - for y in 0..frame.height { - for x in 0..frame.width { - bitmap[(x + (y * width)) as usize] = frame.bitmap[(x + (y * frame.width)) as usize]; - } - } - } - } } pub trait ControllerUpdater: Send { @@ -77,7 +68,8 @@ pub trait Audio { pub trait BlitableSurface { fn set_size(&mut self, width: u32, height: u32); - fn set_pixel(&mut self, pos_x: u32, pos_y: u32, pixel: u32); + fn set_pixel(&mut self, pos_x: u32, pos_y: u32, pixel: Pixel); + fn set_encoded_pixel(&mut self, pos_x: u32, pos_y: u32, pixel: u32); fn blit>(&mut self, pos_x: u32, pos_y: u32, bitmap: B, width: u32, height: u32); fn clear(&mut self, value: u32); } diff --git a/emulator/core/src/system.rs b/emulator/core/src/system.rs index 126be06..da1bc9d 100644 --- a/emulator/core/src/system.rs +++ b/emulator/core/src/system.rs @@ -177,8 +177,8 @@ impl System { } fn queue_device(&mut self, device_step: NextStep) { - for i in (0..self.event_queue.len()).rev() { - if self.event_queue[i].next_clock > device_step.next_clock { + for (i, event) in self.event_queue.iter().enumerate().rev() { + if event.next_clock > device_step.next_clock { self.event_queue.insert(i + 1, device_step); return; } diff --git a/emulator/frontends/console/src/lib.rs b/emulator/frontends/console/src/lib.rs index a47c539..15fc2e8 100644 --- a/emulator/frontends/console/src/lib.rs +++ b/emulator/frontends/console/src/lib.rs @@ -1,8 +1,6 @@ use moa_core::Error; -use moa_core::host::{Host, Tty, WindowUpdater, ControllerDevice, ControllerUpdater, Audio}; - -use moa_common::audio::{AudioMixer, AudioSource}; +use moa_core::host::{Host, Tty, WindowUpdater, ControllerDevice, ControllerUpdater, Audio, DummyAudio}; pub struct ConsoleFrontend; @@ -24,8 +22,7 @@ impl Host for ConsoleFrontend { fn create_audio_source(&mut self) -> Result, Error> { println!("console: create_audio_source() is not supported from the console; returning dummy device..."); - let source = AudioSource::new(AudioMixer::with_default_rate()); - Ok(Box::new(source)) + Ok(Box::new(DummyAudio())) } } diff --git a/emulator/frontends/minifb/src/lib.rs b/emulator/frontends/minifb/src/lib.rs index 7709ce9..bf70024 100644 --- a/emulator/frontends/minifb/src/lib.rs +++ b/emulator/frontends/minifb/src/lib.rs @@ -9,7 +9,7 @@ use clap::{App, Arg, ArgMatches}; use moa_core::{System, Error}; use moa_core::host::{Host, ControllerUpdater, KeyboardUpdater, KeyEvent, MouseUpdater, MouseState, WindowUpdater, Audio, ControllerDevice}; -use moa_core::host::gfx::Frame; +use moa_core::host::gfx::{PixelEncoding, Frame}; use moa_common::{AudioMixer, AudioSource}; use moa_common::CpalAudioOutput; @@ -242,6 +242,7 @@ impl MiniFrontend { let mut size = (WIDTH, HEIGHT); if let Some(updater) = self.window.as_mut() { size = updater.max_size(); + updater.request_encoding(PixelEncoding::ARGB); } let mut window = minifb::Window::new( @@ -258,7 +259,7 @@ impl MiniFrontend { window.limit_update_rate(Some(Duration::from_micros(16600))); let mut update_timer = Instant::now(); - let mut last_frame = Frame::new(size.0, size.1); + let mut last_frame = Frame::new(size.0, size.1, PixelEncoding::ARGB); while window.is_open() && !window.is_key_down(Key::Escape) { let frame_time = update_timer.elapsed(); update_timer = Instant::now(); diff --git a/emulator/frontends/pixels/README.md b/emulator/frontends/pixels/README.md index bf0a043..df27cf0 100644 --- a/emulator/frontends/pixels/README.md +++ b/emulator/frontends/pixels/README.md @@ -5,3 +5,8 @@ Moa Frontend using Pixels This is a frontend for the moa emulator that uses the [pixels]() crate as the rendering library, and which can be compiled to wasm and run in a web browser. +To start the server with Sega Genesis emulation, run: +```sh +just serve moa-genesis +``` + diff --git a/emulator/frontends/pixels/assets/moa-genesis/index.html b/emulator/frontends/pixels/assets/moa-genesis/index.html index 2899ae8..23a1655 100644 --- a/emulator/frontends/pixels/assets/moa-genesis/index.html +++ b/emulator/frontends/pixels/assets/moa-genesis/index.html @@ -16,7 +16,7 @@ @@ -30,4 +30,4 @@ - + diff --git a/emulator/frontends/pixels/assets/moa-genesis/interface.js b/emulator/frontends/pixels/assets/moa-genesis/interface.js index a2a9a41..1ddc658 100644 --- a/emulator/frontends/pixels/assets/moa-genesis/interface.js +++ b/emulator/frontends/pixels/assets/moa-genesis/interface.js @@ -1,4 +1,4 @@ - + import * as Emulator from './moa-genesis.js'; function initialize_emulator() { @@ -38,14 +38,17 @@ reader.onloadend = function (e) { var file_input = document.getElementById("rom-file"); file_input.addEventListener("change", e => { + document.getElementById("video").focus(); reader.readAsArrayBuffer(file_input.files[0]) }); document.getElementById("reset").addEventListener("click", () => { + document.getElementById("video").focus(); Emulator.request_stop(); }); document.getElementById("power").addEventListener("click", () => { + document.getElementById("video").focus(); if (Emulator.is_running()) Emulator.request_stop(); else @@ -53,6 +56,7 @@ document.getElementById("power").addEventListener("click", () => { }); document.getElementById("speed").addEventListener("change", (e) => { + document.getElementById("video").focus(); Emulator.set_speed(e.target.value); }); diff --git a/emulator/frontends/pixels/src/frontend.rs b/emulator/frontends/pixels/src/frontend.rs index 6da3e92..0fac126 100644 --- a/emulator/frontends/pixels/src/frontend.rs +++ b/emulator/frontends/pixels/src/frontend.rs @@ -1,4 +1,6 @@ +use std::sync::{Arc, Mutex}; + use instant::Instant; use pixels::{Pixels, SurfaceTexture}; use winit::event::{Event, VirtualKeyCode, WindowEvent, ElementState}; @@ -6,7 +8,7 @@ use winit::event_loop::{ControlFlow, EventLoop}; use moa_core::{System, Error}; use moa_core::host::{Host, WindowUpdater, ControllerDevice, ControllerEvent, ControllerUpdater, Audio, DummyAudio}; -use moa_core::host::gfx::Frame; +use moa_core::host::gfx::{PixelEncoding, Frame}; use moa_common::{AudioMixer, AudioSource, CpalAudioOutput}; use crate::settings; @@ -59,7 +61,7 @@ impl Host for PixelsFrontend { //let source = AudioSource::new(self.mixer.clone()); //Ok(Box::new(source)) Ok(Box::new(DummyAudio())) - } + } } pub async fn run_loop(mut host: PixelsFrontend) { @@ -67,6 +69,10 @@ pub async fn run_loop(mut host: PixelsFrontend) { let window = create_window(&event_loop); + if let Some(updater) = host.updater.as_mut() { + updater.request_encoding(PixelEncoding::ABGR); + } + let mut pixels = { let window_size = window.inner_size(); let surface_texture = @@ -77,7 +83,7 @@ pub async fn run_loop(mut host: PixelsFrontend) { }; let mut last_size = (WIDTH, HEIGHT); - let mut last_frame = Frame::new(WIDTH, HEIGHT); + let mut last_frame = Frame::new(WIDTH, HEIGHT, PixelEncoding::ABGR); //let mut update_timer = Instant::now(); event_loop.run(move |event, _, control_flow| { @@ -99,15 +105,7 @@ pub async fn run_loop(mut host: PixelsFrontend) { } let buffer = pixels.get_frame(); - buffer - .chunks_mut(4) - .zip(last_frame.bitmap.iter()) - .for_each(|(dest, pixel)| { - dest[0] = (pixel >> 16) as u8; - dest[1] = (pixel >> 8) as u8; - dest[2] = *pixel as u8; - dest[3] = 255; - }); + buffer.copy_from_slice(unsafe { std::slice::from_raw_parts(last_frame.bitmap.as_ptr() as *const u8, last_frame.bitmap.len() * 4) }); } if pixels diff --git a/emulator/frontends/pixels/src/web.rs b/emulator/frontends/pixels/src/web.rs index 0d566b9..ca5e570 100644 --- a/emulator/frontends/pixels/src/web.rs +++ b/emulator/frontends/pixels/src/web.rs @@ -43,7 +43,7 @@ pub fn is_running() -> bool { #[wasm_bindgen] pub fn set_speed(speed: f32) { - settings::get().speed = speed; + //settings::get().speed = speed; } #[wasm_bindgen] diff --git a/emulator/systems/genesis/src/peripherals/ym7101.rs b/emulator/systems/genesis/src/peripherals/ym7101.rs index 091af2e..dad686f 100644 --- a/emulator/systems/genesis/src/peripherals/ym7101.rs +++ b/emulator/systems/genesis/src/peripherals/ym7101.rs @@ -2,7 +2,7 @@ use moa_core::{debug, warn, error}; use moa_core::{System, Error, EdgeSignal, Clock, ClockElapsed, Address, Addressable, Steppable, Inspectable, Transmutable, TransmutableBox, read_beu16, dump_slice}; use moa_core::host::{Host, BlitableSurface, HostData}; -use moa_core::host::gfx::{Frame, FrameQueue}; +use moa_core::host::gfx::{Pixel, PixelEncoding, Frame, FrameQueue}; const REG_MODE_SET_1: usize = 0x00; @@ -63,7 +63,7 @@ const DEV_NAME: &str = "ym7101"; #[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum DmaType { +enum DmaType { None, Memory, Fill, @@ -71,30 +71,30 @@ pub enum DmaType { } #[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum Memory { +enum Memory { Vram, Cram, Vsram, } -pub struct Ym7101Memory { - pub vram: [u8; 0x10000], - pub cram: [u8; 128], - pub vsram: [u8; 80], +struct Ym7101Memory { + vram: [u8; 0x10000], + cram: [u8; 128], + vsram: [u8; 80], - pub transfer_type: u8, - pub transfer_bits: u8, - pub transfer_count: u32, - pub transfer_remain: u32, - pub transfer_src_addr: u32, - pub transfer_dest_addr: u32, - pub transfer_auto_inc: u32, - pub transfer_fill_word: u16, - pub transfer_run: DmaType, - pub transfer_target: Memory, - pub transfer_dma_busy: bool, + transfer_type: u8, + transfer_bits: u8, + transfer_count: u32, + transfer_remain: u32, + transfer_src_addr: u32, + transfer_dest_addr: u32, + transfer_auto_inc: u32, + transfer_fill_word: u16, + transfer_run: DmaType, + transfer_target: Memory, + transfer_dma_busy: bool, - pub ctrl_port_buffer: Option, + ctrl_port_buffer: Option, } impl Default for Ym7101Memory { @@ -132,7 +132,7 @@ impl Ym7101Memory { read_beu16(addr) } - pub fn set_dma_mode(&mut self, mode: DmaType) { + fn set_dma_mode(&mut self, mode: DmaType) { match mode { DmaType::None => { //self.status &= !STATUS_DMA_BUSY; @@ -147,7 +147,7 @@ impl Ym7101Memory { } } - pub fn setup_transfer(&mut self, first: u16, second: u16) { + fn setup_transfer(&mut self, first: u16, second: u16) { self.ctrl_port_buffer = None; self.transfer_type = (((first & 0xC000) >> 14) | ((second & 0x00F0) >> 2)) as u8; self.transfer_dest_addr = ((first & 0x3FFF) | ((second & 0x0003) << 14)) as u32; @@ -166,7 +166,7 @@ impl Ym7101Memory { } } - pub fn get_transfer_target_mut(&mut self) -> &mut [u8] { + fn get_transfer_target_mut(&mut self) -> &mut [u8] { match self.transfer_target { Memory::Vram => &mut self.vram, Memory::Cram => &mut self.cram, @@ -174,7 +174,7 @@ impl Ym7101Memory { } } - pub fn read_data_port(&mut self, addr: Address, data: &mut [u8]) -> Result<(), Error> { + fn read_data_port(&mut self, addr: Address, data: &mut [u8]) -> Result<(), Error> { { let addr = self.transfer_dest_addr; let target = self.get_transfer_target_mut(); @@ -187,7 +187,7 @@ impl Ym7101Memory { Ok(()) } - pub fn write_data_port(&mut self, data: &[u8]) -> Result<(), Error> { + fn write_data_port(&mut self, data: &[u8]) -> Result<(), Error> { if (self.transfer_type & 0x30) == 0x20 { self.ctrl_port_buffer = None; self.transfer_fill_word = if data.len() >= 2 { read_beu16(data) } else { data[0] as u16 }; @@ -207,7 +207,7 @@ impl Ym7101Memory { Ok(()) } - pub fn write_control_port(&mut self, data: &[u8]) -> Result<(), Error> { + fn write_control_port(&mut self, data: &[u8]) -> Result<(), Error> { let value = read_beu16(data); match (data.len(), self.ctrl_port_buffer) { (2, None) => { self.ctrl_port_buffer = Some(value) }, @@ -218,7 +218,7 @@ impl Ym7101Memory { Ok(()) } - pub fn step_dma(&mut self, system: &System) -> Result<(), Error> { + fn step_dma(&mut self, system: &System) -> Result<(), Error> { if self.transfer_run != DmaType::None { // TODO we will just do the full dma transfer here, but it really should be stepped @@ -270,49 +270,58 @@ impl Ym7101Memory { #[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum ColourMode { +enum ColourMode { Normal, Shadow, Highlight, } #[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum Scroll { +enum Scroll { ScrollA, ScrollB, } -pub struct Ym7101State { - pub status: u16, - pub memory: Ym7101Memory, - pub mode_1: u8, - pub mode_2: u8, - pub mode_3: u8, - pub mode_4: u8, - pub h_int_lines: u8, - pub screen_size: (usize, usize), - pub scroll_size: (usize, usize), - pub window_pos: ((usize, usize), (usize, usize)), - pub window_values: (u8, u8), - pub background: u8, - pub scroll_a_addr: usize, - pub scroll_b_addr: usize, - pub window_addr: usize, - pub sprites_addr: usize, - pub hscroll_addr: usize, +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +enum Priority { + Sprite, + ScrollA, + ScrollB, + Background, +} - pub sprites: Vec, - pub sprites_by_line: Vec>, +struct Ym7101State { + status: u16, + memory: Ym7101Memory, - pub last_clock: Clock, - pub p_clock: u32, - pub h_clock: u32, - pub v_clock: u32, - pub h_scanlines: u8, + mode_1: u8, + mode_2: u8, + mode_3: u8, + mode_4: u8, + h_int_lines: u8, + screen_size: (usize, usize), + scroll_size: (usize, usize), + window_pos: ((usize, usize), (usize, usize)), + window_values: (u8, u8), + background: u8, + scroll_a_addr: usize, + scroll_b_addr: usize, + window_addr: usize, + sprites_addr: usize, + hscroll_addr: usize, - pub current_x: i32, - pub current_y: i32, + sprites: Vec, + sprites_by_line: Vec>, + + last_clock: Clock, + p_clock: u32, + h_clock: u32, + v_clock: u32, + h_scanlines: u8, + + current_x: i32, + current_y: i32, } impl Default for Ym7101State { @@ -368,13 +377,13 @@ impl Ym7101State { (self.mode_3 & MODE3_BF_EXTERNAL_INTERRUPT) != 0 } - pub fn update_screen_size(&mut self) { + fn update_screen_size(&mut self) { let h_cells = if (self.mode_4 & MODE4_BF_H_CELL_MODE) == 0 { 32 } else { 40 }; let v_cells = if (self.mode_2 & MODE2_BF_V_CELL_MODE) == 0 { 28 } else { 30 }; self.screen_size = (h_cells, v_cells); } - pub fn update_window_position(&mut self) { + fn update_window_position(&mut self) { let win_h = ((self.window_values.0 & 0x1F) << 1) as usize; let win_v = (self.window_values.1 & 0x1F) as usize; let right = (self.window_values.0 & 0x80) != 0; @@ -393,14 +402,14 @@ impl Ym7101State { x >= self.window_pos.0.0 && x <= self.window_pos.1.0 && y >= self.window_pos.0.1 && y <= self.window_pos.1.1 } - fn get_palette_colour(&self, palette: u8, colour: u8, mode: ColourMode) -> u32 { + fn get_palette_colour(&self, palette: u8, colour: u8, mode: ColourMode, encoding: PixelEncoding) -> u32 { let shift_enabled = (self.mode_4 & MODE4_BF_SHADOW_HIGHLIGHT) != 0; let rgb = self.memory.read_beu16(Memory::Cram, (((palette * 16) + colour) * 2) as usize); if !shift_enabled || mode == ColourMode::Normal { - (((rgb & 0xF00) as u32) >> 4) | (((rgb & 0x0F0) as u32) << 8) | (((rgb & 0x00F) as u32) << 20) + Pixel::Rgb(((rgb & 0x00F) << 4) as u8, (rgb & 0x0F0) as u8, ((rgb & 0xF00) >> 4) as u8).encode(encoding) } else { - let offset = if mode == ColourMode::Highlight { 0x808080 } else { 0x00 }; - (((rgb & 0xF00) as u32) >> 5) | (((rgb & 0x0F0) as u32) << 7) | (((rgb & 0x00F) as u32) << 19) | offset + let offset = if mode == ColourMode::Highlight { 0x80 } else { 0x00 }; + Pixel::Rgb(((rgb & 0x00F) << 3) as u8 | offset, ((rgb & 0x0F0) >> 1) as u8 | offset, ((rgb & 0xF00) >> 5) as u8 | offset).encode(encoding) } } @@ -480,7 +489,7 @@ impl Ym7101State { } } - pub fn draw_frame(&mut self, frame: &mut Frame) { + fn draw_frame(&mut self, frame: &mut Frame) { self.build_sprites_lists(); for y in 0..(self.screen_size.1 * 8) { @@ -488,7 +497,7 @@ impl Ym7101State { } } - pub fn draw_frame_line(&mut self, frame: &mut Frame, y: usize) { + fn draw_frame_line(&mut self, frame: &mut Frame, y: usize) { let bg_colour = ((self.background & 0x30) >> 4, self.background & 0x0f); let (hscrolling_a, hscrolling_b) = self.get_hscroll(y / 8, y % 8); @@ -509,7 +518,7 @@ impl Ym7101State { let mut priority_a = (pattern_a_word & 0x8000) != 0; let mut pixel_a = self.get_pattern_pixel(pattern_a_word, pixel_a_x % 8, pixel_a_y % 8); - if self.window_addr != 0 && self.is_inside_window(x, y) { + if self.window_addr != 0 && self.is_inside_window(x, y) { let pixel_win_x = x - self.window_pos.0.0 * 8; let pixel_win_y = y - self.window_pos.0.1 * 8; let pattern_win_addr = self.get_pattern_addr(self.window_addr, pixel_win_x / 8, pixel_win_y / 8); @@ -556,7 +565,7 @@ impl Ym7101State { ColourMode::Normal }; - frame.set_pixel(x as u32, y as u32, self.get_palette_colour(pixel.0, pixel.1, mode)); + frame.set_encoded_pixel(x as u32, y as u32, self.get_palette_colour(pixel.0, pixel.1, mode, frame.encoding)); break; } } @@ -564,16 +573,16 @@ impl Ym7101State { } } -pub struct Sprite { - pub pos: (i16, i16), - pub size: (u16, u16), - pub rev: (bool, bool), - pub pattern: u16, - pub link: u8, +struct Sprite { + pos: (i16, i16), + size: (u16, u16), + rev: (bool, bool), + pattern: u16, + link: u8, } impl Sprite { - pub fn new(sprite_data: &[u8]) -> Self { + fn new(sprite_data: &[u8]) -> Self { let v_pos = read_beu16(&sprite_data[0..]); let size = sprite_data[2]; let link = sprite_data[3]; @@ -593,7 +602,7 @@ impl Sprite { } } - pub fn calculate_pattern(&self, cell_x: usize, cell_y: usize) -> u16 { + fn calculate_pattern(&self, cell_x: usize, cell_y: usize) -> u16 { let (h, v) = (if !self.rev.0 { cell_x } else { self.size.0 as usize - 1 - cell_x }, if !self.rev.1 { cell_y } else { self.size.1 as usize - 1 - cell_y }); (self.pattern & 0xF800) | ((self.pattern & 0x07FF) + (h as u16 * self.size.1) + v as u16) } @@ -649,7 +658,7 @@ impl Steppable for Ym7101 { } if (self.state.mode_1 & MODE1_BF_DISABLE_DISPLAY) == 0 { - let mut frame = Frame::new(self.state.screen_size.0 as u32 * 8, self.state.screen_size.1 as u32 * 8); + let mut frame = Frame::new(self.state.screen_size.0 as u32 * 8, self.state.screen_size.1 as u32 * 8, self.queue.encoding()); self.state.draw_frame(&mut frame); self.queue.add(system.clock, frame); } diff --git a/emulator/systems/macintosh/src/peripherals/video.rs b/emulator/systems/macintosh/src/peripherals/video.rs index 63ec748..dd017df 100644 --- a/emulator/systems/macintosh/src/peripherals/video.rs +++ b/emulator/systems/macintosh/src/peripherals/video.rs @@ -59,7 +59,7 @@ impl Iterator for BitIter { impl Steppable for MacVideo { fn step(&mut self, system: &System) -> Result { let mut memory = system.get_bus(); - let mut frame = Frame::new(SCRN_SIZE.0, SCRN_SIZE.1); + let mut frame = Frame::new(SCRN_SIZE.0, SCRN_SIZE.1, self.frame_queue.encoding()); for y in 0..SCRN_SIZE.1 { for x in 0..(SCRN_SIZE.0 / 16) { let word = memory.read_beu16((SCRN_BASE + (x * 2) + (y * (SCRN_SIZE.0 / 8))) as Address)?; diff --git a/emulator/systems/trs80/src/peripherals/model1.rs b/emulator/systems/trs80/src/peripherals/model1.rs index 00a34db..a945c8a 100644 --- a/emulator/systems/trs80/src/peripherals/model1.rs +++ b/emulator/systems/trs80/src/peripherals/model1.rs @@ -45,7 +45,7 @@ impl KeyboardUpdater for Model1KeyboardUpdater { impl Steppable for Model1Peripherals { fn step(&mut self, system: &System) -> Result { - let mut frame = Frame::new(SCREEN_SIZE.0, SCREEN_SIZE.1); + let mut frame = Frame::new(SCREEN_SIZE.0, SCREEN_SIZE.1, self.frame_queue.encoding()); for y in 0..16 { for x in 0..64 { let ch = self.video_mem[x + (y * 64)]; diff --git a/todo.txt b/todo.txt index 2d4ce9d..6f2e2c0 100644 --- a/todo.txt +++ b/todo.txt @@ -1,6 +1,9 @@ -* if index access always does a bounds check, you should try to convert some of the copy loops to use iterators - instead, such as frame updating, audio copying, etc +* improve performance +* should it be possible to reschedule multiple events at different intervals to reduce the times a given step function is called? Some have + multiple clocks, or multiple things at different clocks, and making them each an event would mean they could be smaller and faster, but at + the cost of having more events on the queue when re-scheduling. There needs to be a mechanism to avoid the event queue ballooning due to + an error * can you refactor the update timeout to put it in rust? Would that make it faster? (the tricky part is the closure) * re-enable sound on webassembly, see if it works (it does not. Very lagged and jittery with what sounds like repeated frames)