From 2d6044084d0f5020bb80f98b21802ffe4421d130 Mon Sep 17 00:00:00 2001 From: transistor Date: Sat, 1 Oct 2022 12:12:25 -0700 Subject: [PATCH] Modified how frames are updated It turns out to not be too much of a performance issue to allocate a new frame each time one is produced, so to reduce lock contention I added a queue where frames are added to and taken from without locking the frame for the whole update. I'm hoping this will give more flexibility to frontend implementations, which can simply skip or repeat frames if needed. --- emulator/core/src/host/gfx.rs | 84 ++++++++----------- emulator/core/src/host/traits.rs | 15 +++- emulator/frontends/minifb/src/lib.rs | 12 ++- .../systems/genesis/src/peripherals/ym7101.rs | 18 ++-- .../macintosh/src/peripherals/video.rs | 25 +++--- .../systems/trs80/src/peripherals/model1.rs | 19 +++-- 6 files changed, 84 insertions(+), 89 deletions(-) diff --git a/emulator/core/src/host/gfx.rs b/emulator/core/src/host/gfx.rs index aeb3ede..cee9dfe 100644 --- a/emulator/core/src/host/gfx.rs +++ b/emulator/core/src/host/gfx.rs @@ -1,7 +1,9 @@ use std::mem; use std::sync::{Arc, Mutex}; +use std::collections::VecDeque; +use crate::Clock; use crate::Error; use crate::host::traits::{WindowUpdater, BlitableSurface}; @@ -23,10 +25,6 @@ impl Frame { pub fn new_shared(width: u32, height: u32) -> Arc> { Arc::new(Mutex::new(Frame::new(width, height))) } - - pub fn new_updater(frame: Arc>) -> Box { - Box::new(FrameUpdateWrapper(frame)) - } } @@ -67,35 +65,6 @@ impl BlitableSurface for Frame { } } -pub struct FrameUpdateWrapper(Arc>); - -impl WindowUpdater for FrameUpdateWrapper { - fn get_size(&mut self) -> (u32, u32) { - match self.0.lock() { - Ok(frame) => (frame.width, frame.height), - _ => (0, 0), - } - } - - fn get_frame(&mut self) -> Result { - let mut current = self.0.lock().map_err(|_| Error::new("Lock error"))?; - let mut frame = Frame::new(current.width, current.height); - mem::swap(&mut *current, &mut frame); - Ok(frame) - } - - fn update_frame(&mut self, width: u32, _height: u32, bitmap: &mut [u32]) { - if let Ok(frame) = self.0.lock() { - 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]; - } - } - } - } -} - - #[derive(Clone)] pub struct FrameSwapper { pub current: Arc>, @@ -125,29 +94,50 @@ impl FrameSwapper { } impl WindowUpdater for FrameSwapper { - fn get_size(&mut self) -> (u32, u32) { - if let Ok(frame) = self.current.lock() { - (frame.width, frame.height) - } else { - (0, 0) - } + fn max_size(&mut self) -> (u32, u32) { + let frame = self.current.lock().unwrap(); + (frame.width, frame.height) } - fn get_frame(&mut self) -> Result { + 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) } +} - fn update_frame(&mut self, width: u32, _height: u32, bitmap: &mut [u32]) { - if let Ok(frame) = self.previous.lock() { - 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]; - } - } + +#[derive(Clone)] +pub struct FrameQueue { + max_size: (u32, u32), + queue: Arc>>, +} + +impl FrameQueue { + pub fn new(width: u32, height: u32) -> Self { + Self { + max_size: (width, height), + queue: Arc::new(Mutex::new(VecDeque::new())), } } + + pub fn add(&self, clock: Clock, frame: Frame) { + self.queue.lock().unwrap().push_back((clock, frame)); + } + + pub fn latest(&self) -> Option<(Clock, Frame)> { + self.queue.lock().unwrap().drain(..).last() + } +} + +impl WindowUpdater for FrameQueue { + fn max_size(&mut self) -> (u32, u32) { + self.max_size + } + + fn take_frame(&mut self) -> Result { + self.latest().map(|(_, f)| f).ok_or_else(|| Error::new("No frame available")) + } } diff --git a/emulator/core/src/host/traits.rs b/emulator/core/src/host/traits.rs index 4f9c89b..7df949e 100644 --- a/emulator/core/src/host/traits.rs +++ b/emulator/core/src/host/traits.rs @@ -36,9 +36,18 @@ pub trait Tty { } pub trait WindowUpdater: Send { - fn get_size(&mut self) -> (u32, u32); - fn get_frame(&mut self) -> Result; - fn update_frame(&mut self, width: u32, height: u32, bitmap: &mut [u32]); + fn max_size(&mut self) -> (u32, u32); + 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 { diff --git a/emulator/frontends/minifb/src/lib.rs b/emulator/frontends/minifb/src/lib.rs index a4e4eee..3bcfe10 100644 --- a/emulator/frontends/minifb/src/lib.rs +++ b/emulator/frontends/minifb/src/lib.rs @@ -1,8 +1,8 @@ use std::thread; use std::str::FromStr; -use std::time::Duration; use std::sync::{Arc, Mutex}; +use std::time::{Duration, Instant}; use minifb::{self, Key}; use clap::{App, Arg, ArgMatches}; @@ -162,7 +162,6 @@ impl Host for MiniFrontendBuilder { pub struct MiniFrontend { - pub buffer: Vec, pub modifiers: u16, pub window: Option>, pub controller: Option>, @@ -174,7 +173,6 @@ pub struct MiniFrontend { impl MiniFrontend { pub fn new(window: Option>, controller: Option>, keyboard: Option>, mixer: HostData) -> Self { Self { - buffer: vec![0; (WIDTH * HEIGHT) as usize], modifiers: 0, window, controller, @@ -210,8 +208,7 @@ impl MiniFrontend { let mut size = (WIDTH, HEIGHT); if let Some(updater) = self.window.as_mut() { - size = updater.get_size(); - self.buffer = vec![0; (size.0 * size.1) as usize]; + size = updater.max_size(); } let mut window = minifb::Window::new( @@ -251,8 +248,9 @@ impl MiniFrontend { } if let Some(updater) = self.window.as_mut() { - updater.update_frame(size.0, size.1, &mut self.buffer); - window.update_with_buffer(&self.buffer, size.0 as usize, size.1 as usize).unwrap(); + if let Ok(frame) = updater.take_frame() { + window.update_with_buffer(&frame.bitmap, frame.width as usize, frame.height as usize).unwrap(); + } } } } diff --git a/emulator/systems/genesis/src/peripherals/ym7101.rs b/emulator/systems/genesis/src/peripherals/ym7101.rs index f2c768c..52969eb 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, warning, 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, FrameSwapper}; +use moa_core::host::gfx::{Frame, FrameQueue}; const REG_MODE_SET_1: usize = 0x00; @@ -647,10 +647,10 @@ impl Steppable for Ym7101 { system.get_interrupt_controller().set(true, 6, 30)?; } - self.swapper.swap(); if (self.state.mode_1 & MODE1_BF_DISABLE_DISPLAY) == 0 { - let mut frame = self.swapper.current.lock().unwrap(); + let mut frame = Frame::new(self.state.screen_size.0 as u32 * 8, self.state.screen_size.1 as u32 * 8); self.state.draw_frame(&mut frame); + self.queue.add(system.clock, frame); } self.frame_complete.signal(); @@ -669,9 +669,8 @@ impl Steppable for Ym7101 { } - pub struct Ym7101 { - swapper: FrameSwapper, + queue: FrameQueue, state: Ym7101State, sn_sound: TransmutableBox, @@ -681,12 +680,11 @@ pub struct Ym7101 { impl Ym7101 { pub fn new(host: &mut H, external_interrupt: HostData, sn_sound: TransmutableBox) -> Ym7101 { - let swapper = FrameSwapper::new(320, 224); - - host.add_window(FrameSwapper::to_boxed(swapper.clone())).unwrap(); + let queue = FrameQueue::new(320, 224); + host.add_window(Box::new(queue.clone())).unwrap(); Ym7101 { - swapper, + queue, state: Ym7101State::new(), sn_sound, external_interrupt, @@ -707,7 +705,6 @@ impl Ym7101 { REG_MODE_SET_2 => { self.state.mode_2 = data; self.state.update_screen_size(); - self.swapper.set_size(self.state.screen_size.0 as u32 * 8, self.state.screen_size.1 as u32 * 8); }, REG_SCROLL_A_ADDR => { self.state.scroll_a_addr = (data as usize) << 10; }, REG_WINDOW_ADDR => { self.state.window_addr = (data as usize) << 10; }, @@ -719,7 +716,6 @@ impl Ym7101 { REG_MODE_SET_4 => { self.state.mode_4 = data; self.state.update_screen_size(); - self.swapper.set_size(self.state.screen_size.0 as u32 * 8, self.state.screen_size.1 as u32 * 8); }, REG_HSCROLL_ADDR => { self.state.hscroll_addr = (data as usize) << 10; }, REG_AUTO_INCREMENT => { self.state.memory.transfer_auto_inc = data as u32; }, diff --git a/emulator/systems/macintosh/src/peripherals/video.rs b/emulator/systems/macintosh/src/peripherals/video.rs index 50e6664..63ec748 100644 --- a/emulator/systems/macintosh/src/peripherals/video.rs +++ b/emulator/systems/macintosh/src/peripherals/video.rs @@ -1,25 +1,24 @@ -use std::sync::{Arc, Mutex}; - use moa_core::{System, Error, ClockElapsed, Address, Addressable, Steppable, Transmutable}; -use moa_core::host::gfx::Frame; +use moa_core::host::gfx::{Frame, FrameQueue}; use moa_core::host::{Host, BlitableSurface}; -const SCRN_BASE: u32 = 0x07A700; +const SCRN_BASE: u32 = 0x07A700; +const SCRN_SIZE: (u32, u32) = (512, 342); pub struct MacVideo { - frame: Arc>, + frame_queue: FrameQueue, } impl MacVideo { pub fn create(host: &mut H) -> Result { - let frame = Frame::new_shared(512, 342); + let frame_queue = FrameQueue::new(SCRN_SIZE.0, SCRN_SIZE.1); - host.add_window(Frame::new_updater(frame.clone()))?; + host.add_window(Box::new(frame_queue.clone()))?; Ok(Self { - frame, + frame_queue, }) } } @@ -60,13 +59,15 @@ impl Iterator for BitIter { impl Steppable for MacVideo { fn step(&mut self, system: &System) -> Result { let mut memory = system.get_bus(); - let mut frame = self.frame.lock().unwrap(); - for y in 0..342 { - for x in 0..(512 / 16) { - let word = memory.read_beu16((SCRN_BASE + (x * 2) + (y * (512 / 8))) as Address)?; + let mut frame = Frame::new(SCRN_SIZE.0, SCRN_SIZE.1); + 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)?; frame.blit(x * 16, y, BitIter::new(word), 16, 1); } } + + self.frame_queue.add(system.clock, frame); Ok(16_600_000) } } diff --git a/emulator/systems/trs80/src/peripherals/model1.rs b/emulator/systems/trs80/src/peripherals/model1.rs index 739cca0..711c901 100644 --- a/emulator/systems/trs80/src/peripherals/model1.rs +++ b/emulator/systems/trs80/src/peripherals/model1.rs @@ -2,31 +2,32 @@ use std::sync::{Arc, Mutex}; use moa_core::{System, Error, ClockElapsed, Address, Addressable, Steppable, Transmutable, debug, warning}; -use moa_core::host::gfx::Frame; +use moa_core::host::gfx::{Frame, FrameQueue}; use moa_core::host::{Host, BlitableSurface, KeyboardUpdater, Key}; use super::keymap; use super::charset::CharacterGenerator; -const DEV_NAME: &'static str = "model1"; +const DEV_NAME: &'static str = "model1"; +const SCREEN_SIZE: (u32, u32) = (384, 128); pub struct Model1Peripherals { - frame: Arc>, + frame_queue: FrameQueue, keyboard_mem: Arc>, video_mem: [u8; 1024], } impl Model1Peripherals { pub fn create(host: &mut H) -> Result { - let frame = Frame::new_shared(384, 128); + let frame_queue = FrameQueue::new(SCREEN_SIZE.0, SCREEN_SIZE.1); let keyboard_mem = Arc::new(Mutex::new([0; 8])); - host.add_window(Frame::new_updater(frame.clone()))?; + host.add_window(Box::new(frame_queue.clone()))?; host.register_keyboard(Box::new(Model1KeyboardUpdater(keyboard_mem.clone())))?; Ok(Self { - frame, + frame_queue, keyboard_mem, video_mem: [0; 1024], }) @@ -43,9 +44,8 @@ impl KeyboardUpdater for Model1KeyboardUpdater { } impl Steppable for Model1Peripherals { - fn step(&mut self, _system: &System) -> Result { - let mut frame = self.frame.lock().unwrap(); - frame.clear(0); + fn step(&mut self, system: &System) -> Result { + let mut frame = Frame::new(SCREEN_SIZE.0, SCREEN_SIZE.1); for y in 0..16 { for x in 0..64 { let ch = self.video_mem[x + (y * 64)]; @@ -53,6 +53,7 @@ impl Steppable for Model1Peripherals { frame.blit((x * 6) as u32, (y * 8) as u32, iter, 6, 8); } } + self.frame_queue.add(system.clock, frame); Ok(16_630_000) }