use sender instead of trait for renderer - still a bit of lag though!!

This commit is contained in:
Alex Janka 2023-10-18 11:50:00 +11:00
parent 68bb36d92c
commit e2bacf06df
20 changed files with 500 additions and 512 deletions

View file

@ -12,14 +12,13 @@ use gb_emu_lib::{
}, },
EmulatorCore, EmulatorCore,
}; };
use gilrs::Gilrs;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{ use std::{
path::PathBuf, path::PathBuf,
sync::{mpsc::channel, OnceLock}, sync::{mpsc::channel, OnceLock},
time::{Duration, Instant}, time::{Duration, Instant},
}; };
use window::{WindowManager, WindowRenderer}; use window::WindowManager;
mod audio; mod audio;
#[cfg(feature = "camera")] #[cfg(feature = "camera")]
@ -253,26 +252,16 @@ fn run(args: Args) -> ! {
let mut window_manager = WindowManager::new(sender, stream, args.record); let mut window_manager = WindowManager::new(sender, stream, args.record);
let window = window_manager.add_main( let window = window_manager.add_main(scale_override, shader_path, resizable);
scale_override,
Some(Gilrs::new().unwrap()),
shader_path,
resizable,
);
let tile_window: Option<WindowRenderer> = if args.tile_window { let tile_window = if args.tile_window {
Some(window_manager.add(configs.standalone_config.scale_factor, None, None, false)) Some(window_manager.add(configs.standalone_config.scale_factor, None, false))
} else { } else {
None None
}; };
let layer_window: Option<WindowRenderer> = if args.layer_window { let layer_window = if args.layer_window {
Some(window_manager.add( Some(window_manager.add(configs.standalone_config.scale_factor.min(2), None, false))
configs.standalone_config.scale_factor.min(2),
None,
None,
false,
))
} else { } else {
None None
}; };

View file

@ -1,18 +1,18 @@
use std::{ use std::{
collections::HashMap, collections::HashMap,
path::PathBuf, path::PathBuf,
sync::{mpsc::Sender, Arc, Mutex, RwLock}, sync::{
mpsc::{self, Receiver, Sender},
Arc,
},
}; };
use cpal::Stream; use cpal::Stream;
use gb_emu_lib::{ use gb_emu_lib::{
connect::{EmulatorMessage, JoypadState, Renderer, ResolutionData}, connect::{EmulatorMessage, JoypadState, RendererMessage, ResolutionData},
renderer::{RendererBackend, RendererBackendManager, WindowOptions}, renderer::{RendererBackend, RendererBackendManager, WindowOptions},
}; };
use gilrs::{ use gilrs::{Button, Gilrs};
ff::{BaseEffect, BaseEffectType, EffectBuilder, Replay, Ticks},
Button, Gilrs,
};
use image::ImageBuffer; use image::ImageBuffer;
#[cfg(feature = "vulkan")] #[cfg(feature = "vulkan")]
use raw_window_handle::HasRawDisplayHandle; use raw_window_handle::HasRawDisplayHandle;
@ -27,17 +27,7 @@ use winit_input_helper::WinitInputHelper;
use crate::access_config; use crate::access_config;
pub struct WindowInfo { type RendererChannel = Sender<RendererMessage<[u8; 4]>>;
id: WindowId,
data: Arc<WindowData>,
}
struct WindowData {
renderer: Mutex<RendererBackend>,
window: Window,
rendered_size: RwLock<(u32, u32)>,
last_buf: Mutex<Vec<[u8; 4]>>,
}
pub struct WindowManager { pub struct WindowManager {
event_loop: EventLoop<()>, event_loop: EventLoop<()>,
@ -47,10 +37,12 @@ pub struct WindowManager {
struct WindowManagerData { struct WindowManagerData {
main_window: Option<WindowId>, main_window: Option<WindowId>,
windows: HashMap<WindowId, Arc<WindowData>>, windows: HashMap<WindowId, WindowRenderer>,
window_data_manager: Arc<RendererBackendManager>, window_data_manager: Arc<RendererBackendManager>,
input: Arc<Mutex<WinitInputHelper>>, input: WinitInputHelper,
sender: Sender<EmulatorMessage>, sender: Sender<EmulatorMessage>,
gamepad_handler: Gilrs,
joypad_state: JoypadState,
_stream: Stream, _stream: Stream,
} }
@ -68,9 +60,10 @@ impl WindowManager {
main_window: None, main_window: None,
windows: HashMap::new(), windows: HashMap::new(),
window_data_manager, window_data_manager,
input: Arc::new(Mutex::new(WinitInputHelper::new())), input: WinitInputHelper::new(),
sender, sender,
gamepad_handler: Gilrs::new().unwrap(),
joypad_state: Default::default(),
_stream, _stream,
}, },
record_main, record_main,
@ -80,13 +73,11 @@ impl WindowManager {
pub(crate) fn add_main( pub(crate) fn add_main(
&mut self, &mut self,
factor: usize, factor: usize,
gamepad_handler: Option<Gilrs>,
shader_path: Option<PathBuf>, shader_path: Option<PathBuf>,
resizable: bool, resizable: bool,
) -> WindowRenderer { ) -> RendererChannel {
self.data.add( self.data.add(
factor, factor,
gamepad_handler,
shader_path, shader_path,
resizable, resizable,
&self.event_loop, &self.event_loop,
@ -98,13 +89,11 @@ impl WindowManager {
pub(crate) fn add( pub(crate) fn add(
&mut self, &mut self,
factor: usize, factor: usize,
gamepad_handler: Option<Gilrs>,
shader_path: Option<PathBuf>, shader_path: Option<PathBuf>,
resizable: bool, resizable: bool,
) -> WindowRenderer { ) -> RendererChannel {
self.data.add( self.data.add(
factor, factor,
gamepad_handler,
shader_path, shader_path,
resizable, resizable,
&self.event_loop, &self.event_loop,
@ -119,7 +108,7 @@ impl WindowManager {
}); });
} }
pub fn run_events_blocking(self) -> ! { pub fn run_events_blocking(mut self) -> ! {
self.event_loop.run(move |event, target, control_flow| { self.event_loop.run(move |event, target, control_flow| {
self.data.handler(false, event, target, control_flow) self.data.handler(false, event, target, control_flow)
}) })
@ -127,61 +116,54 @@ impl WindowManager {
} }
impl WindowManagerData { impl WindowManagerData {
#[allow(clippy::too_many_arguments)]
fn add( fn add(
&mut self, &mut self,
factor: usize, factor: usize,
gamepad_handler: Option<Gilrs>,
shader_path: Option<PathBuf>, shader_path: Option<PathBuf>,
resizable: bool, resizable: bool,
event_loop: &EventLoop<()>, event_loop: &EventLoop<()>,
is_main: bool, is_main: bool,
record: bool, record: bool,
) -> WindowRenderer { ) -> RendererChannel {
let (r, info) = WindowRenderer::new( let (r, sender) = WindowRenderer::new(
factor, factor,
gamepad_handler,
self.input.clone(),
event_loop, event_loop,
self.window_data_manager.clone(), self.window_data_manager.clone(),
shader_path, shader_path,
resizable, resizable,
record, record,
); );
self.windows.insert(info.id, info.data); let id = r.window.id();
self.windows.insert(id, r);
if is_main { if is_main {
self.main_window = Some(info.id); self.main_window = Some(id);
} }
r sender
} }
fn handler( fn handler(
&self, &mut self,
run_return: bool, run_return: bool,
event: Event<'_, ()>, event: Event<'_, ()>,
_target: &EventLoopWindowTarget<()>, _target: &EventLoopWindowTarget<()>,
control_flow: &mut ControlFlow, control_flow: &mut ControlFlow,
) { ) {
control_flow.set_wait(); control_flow.set_poll();
if let Ok(mut i) = self.input.lock() { if self.input.update(&event) && self.input.key_pressed(VirtualKeyCode::Space) {
if i.update(&event) && i.key_pressed(VirtualKeyCode::Space) {
if let Some(window) = self if let Some(window) = self
.main_window .main_window
.as_ref() .as_ref()
.and_then(|id| self.windows.get(id)) .and_then(|id| self.windows.get(id))
{ {
if let Ok(buf) = window.last_buf.lock() {
if let Ok(size) = window.rendered_size.read() {
let image = ImageBuffer::<image::Rgba<u8>, _>::from_raw( let image = ImageBuffer::<image::Rgba<u8>, _>::from_raw(
size.0, window.width as u32,
size.1, window.height as u32,
bytemuck::cast_slice(buf.as_ref()), bytemuck::cast_slice(&window.queued_buf.buf),
) )
.unwrap(); .unwrap();
let configs = access_config(); let configs = access_config();
let screenshot_dir = let screenshot_dir = if configs.standalone_config.group_screenshots_by_rom {
if configs.standalone_config.group_screenshots_by_rom {
configs configs
.config_dir .config_dir
.join(format!("screenshots/{}", configs.rom_title)) .join(format!("screenshots/{}", configs.rom_title))
@ -194,9 +176,7 @@ impl WindowManagerData {
let screenshot_path = screenshot_dir.join(format!( let screenshot_path = screenshot_dir.join(format!(
"{} - {}.png", "{} - {}.png",
chrono::DateTime::<chrono::Local>::from( chrono::DateTime::<chrono::Local>::from(std::time::SystemTime::now())
std::time::SystemTime::now()
)
.to_rfc3339(), .to_rfc3339(),
configs.rom_title, configs.rom_title,
)); ));
@ -205,9 +185,8 @@ impl WindowManagerData {
.expect("Could not save screenshot!"); .expect("Could not save screenshot!");
} }
} }
}
} self.process_input();
}
match event { match event {
Event::Resumed => { Event::Resumed => {
@ -228,62 +207,108 @@ impl WindowManagerData {
} }
} }
Event::MainEventsCleared => { Event::MainEventsCleared => {
for window in self.windows.values_mut() {
window.process();
// if window.is_prepared {
window.render(&self.window_data_manager);
// }
}
if run_return { if run_return {
control_flow.set_exit(); control_flow.set_exit();
} }
} }
Event::RedrawRequested(window_id) => { Event::RedrawRequested(window_id) => {
if let Some(w) = self.windows.get(&window_id) { if let Some(w) = self.windows.get_mut(&window_id) {
if let Ok(mut renderer) = w.renderer.lock() { w.render(&self.window_data_manager);
let inner_size = w.window.inner_size();
if let Ok(rendered_size) = w.rendered_size.read() {
renderer.render(
ResolutionData {
real_width: inner_size.width,
real_height: inner_size.height,
scaled_width: rendered_size.0,
scaled_height: rendered_size.1,
},
&self.window_data_manager,
);
}
}
} }
} }
_ => {} _ => {}
} }
} }
fn process_input(&mut self) {
self.joypad_state.reset();
while let Some(event) = self.gamepad_handler.next_event() {
if let gilrs::EventType::ButtonPressed(button, _) = event.event {
match button {
Button::DPadDown => self.joypad_state.down = true,
Button::DPadUp => self.joypad_state.up = true,
Button::DPadLeft => self.joypad_state.left = true,
Button::DPadRight => self.joypad_state.right = true,
Button::Start => self.joypad_state.start = true,
Button::Select => self.joypad_state.select = true,
Button::East => self.joypad_state.a = true,
Button::South => self.joypad_state.b = true,
_ => {}
}
}
for (_id, pad) in self.gamepad_handler.gamepads() {
self.joypad_state.down |= pad.is_pressed(Button::DPadDown);
self.joypad_state.up |= pad.is_pressed(Button::DPadUp);
self.joypad_state.left |= pad.is_pressed(Button::DPadLeft);
self.joypad_state.right |= pad.is_pressed(Button::DPadRight);
self.joypad_state.start |= pad.is_pressed(Button::Start);
self.joypad_state.select |= pad.is_pressed(Button::Select);
self.joypad_state.a |= pad.is_pressed(Button::East);
self.joypad_state.b |= pad.is_pressed(Button::South);
}
}
self.joypad_state.down |=
self.input.key_held(VirtualKeyCode::Down) || self.input.key_held(VirtualKeyCode::S);
self.joypad_state.up |=
self.input.key_held(VirtualKeyCode::Up) || self.input.key_held(VirtualKeyCode::W);
self.joypad_state.left |=
self.input.key_held(VirtualKeyCode::Left) || self.input.key_held(VirtualKeyCode::A);
self.joypad_state.right |=
self.input.key_held(VirtualKeyCode::Right) || self.input.key_held(VirtualKeyCode::D);
self.joypad_state.start |= self.input.key_held(VirtualKeyCode::Equals);
self.joypad_state.select |= self.input.key_held(VirtualKeyCode::Minus);
self.joypad_state.a |= self.input.key_held(VirtualKeyCode::Apostrophe);
self.joypad_state.b |= self.input.key_held(VirtualKeyCode::Semicolon);
self.sender
.send(EmulatorMessage::JoypadUpdate(self.joypad_state))
.expect("error sending joypad state");
}
} }
pub struct WindowRenderer { pub struct WindowRenderer {
data: Arc<WindowData>, receiver: Receiver<RendererMessage<[u8; 4]>>,
input: Arc<Mutex<WinitInputHelper>>, renderer: RendererBackend,
window: Window,
width: usize, width: usize,
height: usize, height: usize,
factor: usize, factor: usize,
gamepad_handler: Option<Gilrs>, queued_buf: QueuedBuf,
joypad_state: JoypadState,
current_rumble: bool,
recording: Option<RecordInfo>, recording: Option<RecordInfo>,
} }
struct RecordInfo { #[derive(Default)]
dir: PathBuf, struct QueuedBuf {
frame_num: usize, buf: Vec<[u8; 4]>,
displayed: bool,
}
impl QueuedBuf {
fn update(&mut self, new: Vec<[u8; 4]>) {
self.buf = new;
self.displayed = false;
}
} }
impl WindowRenderer { impl WindowRenderer {
#[allow(clippy::too_many_arguments, unused_variables)] fn new(
pub fn new(
factor: usize, factor: usize,
gamepad_handler: Option<Gilrs>,
input: Arc<Mutex<WinitInputHelper>>,
event_loop: &EventLoop<()>, event_loop: &EventLoop<()>,
manager: Arc<RendererBackendManager>, manager: Arc<RendererBackendManager>,
shader_path: Option<PathBuf>, shader_path: Option<PathBuf>,
resizable: bool, resizable: bool,
record: bool, record: bool,
) -> (Self, WindowInfo) { ) -> (Self, RendererChannel) {
let window = WindowBuilder::new() let window = WindowBuilder::new()
.with_title("Gameboy") .with_title("Gameboy")
.with_resizable(resizable) .with_resizable(resizable)
@ -304,20 +329,7 @@ impl WindowRenderer {
#[cfg(feature = "pixels")] #[cfg(feature = "pixels")]
let options = WindowOptions {}; let options = WindowOptions {};
let renderer = Mutex::new(RendererBackend::new(resolutions, &window, options, manager)); let renderer = RendererBackend::new(resolutions, &window, options, manager);
let id = window.id();
let data = Arc::new(WindowData {
renderer,
window,
rendered_size: RwLock::new((1, 1)),
last_buf: Mutex::new(Vec::new()),
});
let info = WindowInfo {
id,
data: data.clone(),
};
let recording = if record { let recording = if record {
let configs = access_config(); let configs = access_config();
@ -335,39 +347,63 @@ impl WindowRenderer {
None None
}; };
let (sender, receiver) = mpsc::channel();
( (
Self { Self {
data, receiver,
input, renderer,
width: 0, window,
height: 0, width: 1,
height: 1,
factor, factor,
gamepad_handler, queued_buf: Default::default(),
joypad_state: JoypadState::default(),
current_rumble: false,
recording, recording,
}, },
info, sender,
) )
} }
fn render(&mut self, manager: &RendererBackendManager) {
let inner_size = self.window.inner_size();
if !self.queued_buf.displayed {
self.renderer.new_frame(&self.queued_buf.buf);
self.queued_buf.displayed = true;
}
self.renderer.render(
ResolutionData {
real_width: inner_size.width,
real_height: inner_size.height,
scaled_width: self.width as u32,
scaled_height: self.height as u32,
},
manager,
);
}
fn process(&mut self) {
while let Ok(message) = self.receiver.try_recv() {
match message {
RendererMessage::Prepare { width, height } => self.prepare(width, height),
RendererMessage::Resize { width, height } => self.prepare(width, height),
RendererMessage::Display { buffer } => self.display(buffer),
RendererMessage::SetTitle { title } => self.window.set_title(&title),
RendererMessage::Rumble { rumble: _ } => todo!(),
}
}
} }
impl Renderer<[u8; 4]> for WindowRenderer {
fn prepare(&mut self, width: usize, height: usize) { fn prepare(&mut self, width: usize, height: usize) {
self.width = width; self.width = width;
self.height = height; self.height = height;
let real_factor = (self.data.window.scale_factor() * self.factor as f64) as u32; let real_factor = (self.window.scale_factor() * self.factor as f64) as u32;
let real_width = (width as u32) * real_factor; let real_width = (width as u32) * real_factor;
let real_height = (height as u32) * real_factor; let real_height = (height as u32) * real_factor;
if let Ok(mut f) = self.data.rendered_size.write() { self.window
*f = (width as u32, height as u32);
}
self.data
.window
.set_inner_size(PhysicalSize::new(real_width, real_height)); .set_inner_size(PhysicalSize::new(real_width, real_height));
let resolutions = ResolutionData { let resolutions = ResolutionData {
@ -377,27 +413,21 @@ impl Renderer<[u8; 4]> for WindowRenderer {
scaled_height: height as u32, scaled_height: height as u32,
}; };
if let Ok(mut data) = self.data.renderer.lock() { self.renderer.resize(resolutions, &self.window);
data.resize(resolutions, &self.data.window);
} self.window.request_redraw();
self.data.window.request_redraw();
} }
fn display(&mut self, buffer: &[[u8; 4]]) { fn display(&mut self, buffer: Vec<[u8; 4]>) {
if let Ok(mut data) = self.data.renderer.lock() { self.queued_buf.update(buffer);
data.new_frame(buffer);
} self.window.request_redraw();
self.data.window.request_redraw();
if let Ok(mut last_buf) = self.data.last_buf.lock() {
last_buf.resize(buffer.len(), [0; 4]);
last_buf.copy_from_slice(buffer);
}
if let Some(ref mut info) = self.recording { if let Some(ref mut info) = self.recording {
let image = ImageBuffer::<image::Rgba<u8>, _>::from_raw( let image = ImageBuffer::<image::Rgba<u8>, _>::from_raw(
self.width as u32, self.width as u32,
self.height as u32, self.height as u32,
bytemuck::cast_slice(buffer), bytemuck::cast_slice(&self.queued_buf.buf),
) )
.unwrap(); .unwrap();
@ -407,87 +437,39 @@ impl Renderer<[u8; 4]> for WindowRenderer {
info.frame_num += 1; info.frame_num += 1;
} }
} }
fn set_title(&mut self, title: String) {
self.data.window.set_title(&title);
} }
fn latest_joypad_state(&mut self) -> JoypadState { struct RecordInfo {
self.joypad_state.reset(); dir: PathBuf,
frame_num: usize,
if let Some(ref mut gamepad_handler) = self.gamepad_handler {
while let Some(event) = gamepad_handler.next_event() {
if let gilrs::EventType::ButtonPressed(button, _) = event.event {
match button {
Button::DPadDown => self.joypad_state.down = true,
Button::DPadUp => self.joypad_state.up = true,
Button::DPadLeft => self.joypad_state.left = true,
Button::DPadRight => self.joypad_state.right = true,
Button::Start => self.joypad_state.start = true,
Button::Select => self.joypad_state.select = true,
Button::East => self.joypad_state.a = true,
Button::South => self.joypad_state.b = true,
_ => {}
}
}
} }
for (_, pad) in gamepad_handler.gamepads() { // fn set_rumble(&mut self, rumbling: bool) {
self.joypad_state.down |= pad.is_pressed(Button::DPadDown); // if rumbling != self.current_rumble && let Some(ref mut gamepad_handler) = self.gamepad_handler {
self.joypad_state.up |= pad.is_pressed(Button::DPadUp); // self.current_rumble = rumbling;
self.joypad_state.left |= pad.is_pressed(Button::DPadLeft);
self.joypad_state.right |= pad.is_pressed(Button::DPadRight);
self.joypad_state.start |= pad.is_pressed(Button::Start);
self.joypad_state.select |= pad.is_pressed(Button::Select);
self.joypad_state.a |= pad.is_pressed(Button::East);
self.joypad_state.b |= pad.is_pressed(Button::South);
}
}
if let Ok(input) = self.input.lock() { // let ids = gamepad_handler
self.joypad_state.down |= // .gamepads()
input.key_held(VirtualKeyCode::Down) || input.key_held(VirtualKeyCode::S); // .filter_map(|(id, gp)| if gp.is_ff_supported() { Some(id) } else { None })
self.joypad_state.up |= // .collect::<Vec<_>>();
input.key_held(VirtualKeyCode::Up) || input.key_held(VirtualKeyCode::W); // if ids.is_empty() {
self.joypad_state.left |= // return;
input.key_held(VirtualKeyCode::Left) || input.key_held(VirtualKeyCode::A); // }
self.joypad_state.right |=
input.key_held(VirtualKeyCode::Right) || input.key_held(VirtualKeyCode::D);
self.joypad_state.start |= input.key_held(VirtualKeyCode::Equals);
self.joypad_state.select |= input.key_held(VirtualKeyCode::Minus);
self.joypad_state.a |= input.key_held(VirtualKeyCode::Apostrophe);
self.joypad_state.b |= input.key_held(VirtualKeyCode::Semicolon);
}
self.joypad_state
}
fn set_rumble(&mut self, rumbling: bool) { // let magnitude = if rumbling { 0xFF } else { 0x0 };
if rumbling != self.current_rumble && let Some(ref mut gamepad_handler) = self.gamepad_handler {
self.current_rumble = rumbling;
let ids = gamepad_handler // EffectBuilder::new()
.gamepads() // .add_effect(BaseEffect {
.filter_map(|(id, gp)| if gp.is_ff_supported() { Some(id) } else { None }) // kind: BaseEffectType::Strong { magnitude },
.collect::<Vec<_>>(); // scheduling: Replay {
if ids.is_empty() { // after: Ticks::from_ms(0),
return; // play_for: Ticks::from_ms(16),
} // with_delay: Ticks::from_ms(0),
// },
let magnitude = if rumbling { 0xFF } else { 0x0 }; // envelope: Default::default(),
// })
EffectBuilder::new() // .gamepads(&ids)
.add_effect(BaseEffect { // .finish(gamepad_handler)
kind: BaseEffectType::Strong { magnitude }, // .unwrap();
scheduling: Replay { // }
after: Ticks::from_ms(0), // }
play_for: Ticks::from_ms(16),
with_delay: Ticks::from_ms(0),
},
envelope: Default::default(),
})
.gamepads(&ids)
.finish(gamepad_handler)
.unwrap();
}
}
}

View file

@ -5,7 +5,7 @@ use gb_emu_lib::{
config::ConfigManager, config::ConfigManager,
connect::{ connect::{
AudioOutput, CgbRomType, DownsampleType, EmulatorCoreTrait, EmulatorMessage, AudioOutput, CgbRomType, DownsampleType, EmulatorCoreTrait, EmulatorMessage,
EmulatorOptions, JoypadButtons, NoCamera, RomFile, SerialTarget, EmulatorOptions, NoCamera, RendererMessage, RomFile, SerialTarget,
}, },
renderer::RendererBackendManager, renderer::RendererBackendManager,
EmulatorCore, HEIGHT, WIDTH, EmulatorCore, HEIGHT, WIDTH,
@ -21,7 +21,7 @@ use std::{
Arc, Mutex, OnceLock, RwLock, Arc, Mutex, OnceLock, RwLock,
}, },
}; };
use ui::{Emulator, EmulatorRenderer}; use ui::Emulator;
mod ui; mod ui;
@ -53,11 +53,15 @@ struct EmuParams {
struct EmuVars { struct EmuVars {
rx: AsyncHeapConsumer<[f32; 2]>, rx: AsyncHeapConsumer<[f32; 2]>,
sender: Sender<EmulatorMessage>, emulator_core: EmulatorCore<[u8; 4], NoCamera>,
emulator_core: EmulatorCore<[u8; 4], EmulatorRenderer, NoCamera>,
serial_tx: Sender<u8>, serial_tx: Sender<u8>,
} }
struct EmuComms {
sender: Sender<EmulatorMessage>,
receiver: Receiver<RendererMessage<[u8; 4]>>,
}
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(default)] #[serde(default)]
pub struct VstConfig { pub struct VstConfig {
@ -106,18 +110,11 @@ fn access_config<'a>() -> &'a Configs {
#[derive(Default)] #[derive(Default)]
pub struct GameboyEmu { pub struct GameboyEmu {
vars: Option<EmuVars>, vars: Option<EmuVars>,
frame_receiver: Arc<FrameReceiver>, emu_comms: Arc<Mutex<Option<EmuComms>>>,
key_handler: Arc<JoypadSender>,
params: Arc<EmuParams>, params: Arc<EmuParams>,
renderer_manager: Arc<Mutex<Option<Arc<RendererBackendManager>>>>, renderer_manager: Arc<Mutex<Option<Arc<RendererBackendManager>>>>,
} }
type Frame = Vec<[u8; 4]>;
type JoypadInfo = (JoypadButtons, bool);
type FrameReceiver = Mutex<Option<Receiver<Frame>>>;
type JoypadSender = Mutex<Option<Sender<JoypadInfo>>>;
const BUFFERS_PER_FRAME: usize = 1; const BUFFERS_PER_FRAME: usize = 1;
const DOWNSAMPLE_TYPE: DownsampleType = DownsampleType::Linear; const DOWNSAMPLE_TYPE: DownsampleType = DownsampleType::Linear;
@ -259,8 +256,7 @@ impl Plugin for GameboyEmu {
); );
Some(Box::new(Emulator::new( Some(Box::new(Emulator::new(
self.frame_receiver.clone(), self.emu_comms.clone(),
self.key_handler.clone(),
self.renderer_manager.clone(), self.renderer_manager.clone(),
size, size,
))) )))
@ -303,10 +299,12 @@ impl Plugin for GameboyEmu {
DOWNSAMPLE_TYPE, DOWNSAMPLE_TYPE,
); );
let (window, frame_receiver, key_handler) = EmulatorRenderer::new(); let (emu_sender, renderer_receiver) = mpsc::channel();
*self.frame_receiver.lock().unwrap() = Some(frame_receiver); *self.emu_comms.lock().unwrap() = Some(EmuComms {
*self.key_handler.lock().unwrap() = Some(key_handler); sender,
receiver: renderer_receiver,
});
let (serial_tx, gb_serial_rx) = mpsc::channel::<u8>(); let (serial_tx, gb_serial_rx) = mpsc::channel::<u8>();
let serial_target = SerialTarget::Custom { let serial_target = SerialTarget::Custom {
@ -321,7 +319,7 @@ impl Plugin for GameboyEmu {
let options = EmulatorOptions::new_with_config( let options = EmulatorOptions::new_with_config(
configs.emu_config.clone(), configs.emu_config.clone(),
configs.config_dir.clone(), configs.config_dir.clone(),
window, emu_sender,
rom, rom,
output, output,
) )
@ -336,7 +334,6 @@ impl Plugin for GameboyEmu {
self.vars = Some(EmuVars { self.vars = Some(EmuVars {
rx, rx,
sender,
emulator_core, emulator_core,
serial_tx, serial_tx,
}); });
@ -346,14 +343,16 @@ impl Plugin for GameboyEmu {
} }
fn deactivate(&mut self) { fn deactivate(&mut self) {
if let Some(ref mut vars) = self.vars { if let Ok(comms) = self.emu_comms.lock() {
match vars.sender.send(EmulatorMessage::Exit) { if let Some(ref comms) = *comms {
match comms.sender.send(EmulatorMessage::Exit) {
Ok(_) => self.vars = None, Ok(_) => self.vars = None,
Err(e) => nih_log!("error {e} sending message to emulator"), Err(e) => nih_log!("error {e} sending message to emulator"),
} }
} }
} }
} }
}
impl Vst3Plugin for GameboyEmu { impl Vst3Plugin for GameboyEmu {
const VST3_CLASS_ID: [u8; 16] = *b"alexjankagbemula"; const VST3_CLASS_ID: [u8; 16] = *b"alexjankagbemula";

View file

@ -1,38 +1,32 @@
use std::{ use std::{
path::PathBuf, path::PathBuf,
sync::{ sync::{Arc, Mutex},
mpsc::{self, Receiver, Sender},
Arc, Mutex,
},
}; };
use baseview::{Event, EventStatus, Size, Window, WindowEvent, WindowHandler, WindowOpenOptions}; use baseview::{Event, EventStatus, Size, Window, WindowEvent, WindowHandler, WindowOpenOptions};
use gb_emu_lib::{ use gb_emu_lib::{
connect::{JoypadButtons, JoypadState, Renderer, ResolutionData, HEIGHT, WIDTH}, connect::{JoypadButtons, JoypadState, RendererMessage, ResolutionData, HEIGHT, WIDTH},
renderer::{RendererBackend, RendererBackendManager, WindowOptions}, renderer::{RendererBackend, RendererBackendManager, WindowOptions},
}; };
use keyboard_types::{Code, KeyState}; use keyboard_types::{Code, KeyState};
use nih_plug::prelude::*; use nih_plug::prelude::*;
use crate::{access_config, Frame, FrameReceiver, JoypadInfo, JoypadSender, IS_CGB}; use crate::{access_config, EmuComms, IS_CGB};
pub struct Emulator { pub struct Emulator {
frame_receiver: Arc<FrameReceiver>, emu_comms: Arc<Mutex<Option<EmuComms>>>,
joypad_sender: Arc<JoypadSender>,
manager: Arc<Mutex<Option<Arc<RendererBackendManager>>>>, manager: Arc<Mutex<Option<Arc<RendererBackendManager>>>>,
size: Size, size: Size,
} }
impl Emulator { impl Emulator {
pub fn new( pub fn new(
frame_receiver: Arc<FrameReceiver>, emu_comms: Arc<Mutex<Option<EmuComms>>>,
joypad_sender: Arc<JoypadSender>,
manager: Arc<Mutex<Option<Arc<RendererBackendManager>>>>, manager: Arc<Mutex<Option<Arc<RendererBackendManager>>>>,
size: Size, size: Size,
) -> Self { ) -> Self {
Self { Self {
frame_receiver, emu_comms,
joypad_sender,
manager, manager,
size, size,
} }
@ -45,8 +39,7 @@ impl Editor for Emulator {
parent: ParentWindowHandle, parent: ParentWindowHandle,
_context: Arc<dyn GuiContext>, _context: Arc<dyn GuiContext>,
) -> Box<dyn std::any::Any + Send> { ) -> Box<dyn std::any::Any + Send> {
let fr_cloned = self.frame_receiver.clone(); let rr_cloned = self.emu_comms.clone();
let js_cloned = self.joypad_sender.clone();
// let (size, scale) = if cfg!(target_os = "macos") { // let (size, scale) = if cfg!(target_os = "macos") {
// ( // (
@ -89,8 +82,7 @@ impl Editor for Emulator {
move |w| { move |w| {
EmulatorWindow::new( EmulatorWindow::new(
w, w,
fr_cloned, rr_cloned,
js_cloned,
m, m,
size, size,
#[cfg(feature = "vulkan")] #[cfg(feature = "vulkan")]
@ -100,8 +92,7 @@ impl Editor for Emulator {
}, },
); );
Box::new(Self::new( Box::new(Self::new(
self.frame_receiver.clone(), self.emu_comms.clone(),
self.joypad_sender.clone(),
self.manager.clone(), self.manager.clone(),
size, size,
)) ))
@ -125,16 +116,16 @@ impl Editor for Emulator {
pub struct EmulatorWindow { pub struct EmulatorWindow {
renderer: RendererBackend, renderer: RendererBackend,
manager: Arc<RendererBackendManager>, manager: Arc<RendererBackendManager>,
frame_receiver: Arc<FrameReceiver>, emu_comms: Arc<Mutex<Option<EmuComms>>>,
joypad_sender: Arc<JoypadSender>, joypad_state: JoypadState,
latest_buf: Vec<[u8; 4]>,
current_resolution: ResolutionData, current_resolution: ResolutionData,
} }
impl EmulatorWindow { impl EmulatorWindow {
fn new( fn new(
window: &mut Window, window: &mut Window,
frame_receiver: Arc<FrameReceiver>, emu_comms: Arc<Mutex<Option<EmuComms>>>,
joypad_sender: Arc<JoypadSender>,
manager: Arc<Mutex<Option<Arc<RendererBackendManager>>>>, manager: Arc<Mutex<Option<Arc<RendererBackendManager>>>>,
size: Size, size: Size,
#[cfg(feature = "vulkan")] shader_path: Option<PathBuf>, #[cfg(feature = "vulkan")] shader_path: Option<PathBuf>,
@ -164,21 +155,47 @@ impl EmulatorWindow {
Self { Self {
renderer, renderer,
manager: m.clone(), manager: m.clone(),
frame_receiver, emu_comms,
joypad_sender, joypad_state: Default::default(),
latest_buf: Vec::new(),
current_resolution, current_resolution,
}, },
m.clone(), m.clone(),
) )
} }
fn process_events(&mut self) {
if let Ok(comms) = self.emu_comms.lock() {
if let Some(ref comms) = *comms {
while let Ok(e) = comms.receiver.try_recv() {
match e {
RendererMessage::Prepare {
width: _,
height: _,
} => {}
RendererMessage::Resize {
width: _,
height: _,
} => {}
RendererMessage::Display { buffer } => self.latest_buf = buffer,
RendererMessage::SetTitle { title: _ } => {}
RendererMessage::Rumble { rumble: _ } => {}
}
}
}
}
}
} }
impl WindowHandler for EmulatorWindow { impl WindowHandler for EmulatorWindow {
fn on_frame(&mut self, _window: &mut Window) { fn on_frame(&mut self, _window: &mut Window) {
if let Some(ref mut receiver) = *self.frame_receiver.lock().expect("failed to lock mutex") { self.process_events();
if let Some(ref buf) = receiver.try_iter().last() {
self.renderer.new_frame(buf); if self.latest_buf.len()
} == (self.current_resolution.scaled_height * self.current_resolution.scaled_width)
as usize
{
self.renderer.new_frame(&self.latest_buf);
} }
self.renderer.render(self.current_resolution, &self.manager); self.renderer.render(self.current_resolution, &self.manager);
@ -210,10 +227,18 @@ impl WindowHandler for EmulatorWindow {
Code::KeyD | Code::ArrowRight => Some(JoypadButtons::Right), Code::KeyD | Code::ArrowRight => Some(JoypadButtons::Right),
_ => None, _ => None,
} { } {
if let Some(ref mut sender) = self.joypad_state.set(button, status);
*self.joypad_sender.lock().expect("failed to lock mutex") if let Ok(comms) = self.emu_comms.lock() {
{ if let Some(ref comms) = *comms {
sender.send((button, status)).unwrap(); match comms.sender.send(
gb_emu_lib::connect::EmulatorMessage::JoypadUpdate(
self.joypad_state,
),
) {
Ok(_) => {}
Err(e) => println!("error sending joypad update: {e:#?}"),
}
}
} }
EventStatus::Captured EventStatus::Captured
} else { } else {
@ -224,49 +249,3 @@ impl WindowHandler for EmulatorWindow {
} }
} }
} }
pub struct EmulatorRenderer {
tx: Sender<Frame>,
joypad: JoypadState,
keys: Receiver<JoypadInfo>,
}
impl EmulatorRenderer {
pub(super) fn new() -> (Self, Receiver<Frame>, Sender<JoypadInfo>) {
let (tx, rx) = mpsc::channel::<Frame>();
let (keys_tx, keys) = mpsc::channel::<JoypadInfo>();
(
Self {
tx,
joypad: JoypadState::default(),
keys,
},
rx,
keys_tx,
)
}
}
impl Renderer<[u8; 4]> for EmulatorRenderer {
fn prepare(&mut self, _width: usize, _height: usize) {}
fn display(&mut self, buffer: &[[u8; 4]]) {
let _ = self.tx.send(buffer.to_vec());
}
fn latest_joypad_state(&mut self) -> JoypadState {
while let Ok((key, state)) = self.keys.try_recv() {
match key {
JoypadButtons::Down => self.joypad.down = state,
JoypadButtons::Up => self.joypad.up = state,
JoypadButtons::Left => self.joypad.left = state,
JoypadButtons::Right => self.joypad.right = state,
JoypadButtons::Start => self.joypad.start = state,
JoypadButtons::Select => self.joypad.select = state,
JoypadButtons::B => self.joypad.b = state,
JoypadButtons::A => self.joypad.a = state,
}
}
self.joypad
}
}

View file

@ -1,6 +1,7 @@
use std::fs; use std::fs;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::mpsc::Sender;
use std::sync::{Arc, Mutex, RwLock}; use std::sync::{Arc, Mutex, RwLock};
pub use crate::processor::memory::mmio::gpu::Colour; pub use crate::processor::memory::mmio::gpu::Colour;
@ -17,6 +18,7 @@ pub enum EmulatorMessage {
Start, Start,
Pause, Pause,
Exit, Exit,
JoypadUpdate(JoypadState),
} }
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
@ -74,20 +76,18 @@ impl RomFile {
} }
} }
pub trait Renderer<Format: From<Colour>> { pub enum RendererMessage<Format: From<Colour>> {
fn prepare(&mut self, width: usize, height: usize); Prepare { width: usize, height: usize },
Resize { width: usize, height: usize },
fn resize(&mut self, width: usize, height: usize) { Display { buffer: Vec<Format> },
self.prepare(width, height) SetTitle { title: String },
Rumble { rumble: bool },
} }
fn display(&mut self, buffer: &[Format]); impl<Format: From<Colour>> RendererMessage<Format> {
pub fn display_message(buffer: Vec<Format>) -> Self {
fn set_title(&mut self, _title: String) {} Self::Display { buffer }
}
fn latest_joypad_state(&mut self) -> JoypadState;
fn set_rumble(&mut self, _rumbling: bool) {}
} }
#[cfg(any(feature = "vulkan-renderer", feature = "pixels-renderer"))] #[cfg(any(feature = "vulkan-renderer", feature = "pixels-renderer"))]
@ -220,15 +220,14 @@ pub enum SramType {
} }
#[non_exhaustive] #[non_exhaustive]
pub struct EmulatorOptions<ColourFormat, R, C> pub struct EmulatorOptions<ColourFormat, C>
where where
ColourFormat: From<Colour> + Copy, ColourFormat: From<Colour> + Copy,
R: Renderer<ColourFormat>,
C: PocketCamera + Send + 'static, C: PocketCamera + Send + 'static,
{ {
pub(crate) window: R, pub(crate) window: Sender<RendererMessage<ColourFormat>>,
pub(crate) tile_window: Option<R>, pub(crate) tile_window: Option<Sender<RendererMessage<ColourFormat>>>,
pub(crate) layer_window: Option<R>, pub(crate) layer_window: Option<Sender<RendererMessage<ColourFormat>>>,
pub(crate) rom: Rom<C>, pub(crate) rom: Rom<C>,
pub(crate) output: AudioOutput, pub(crate) output: AudioOutput,
pub(crate) save: Option<SramType>, pub(crate) save: Option<SramType>,
@ -242,13 +241,16 @@ where
_spooky: PhantomData<ColourFormat>, _spooky: PhantomData<ColourFormat>,
} }
impl<ColourFormat, R, C> EmulatorOptions<ColourFormat, R, C> impl<ColourFormat, C> EmulatorOptions<ColourFormat, C>
where where
ColourFormat: From<Colour> + Copy, ColourFormat: From<Colour> + Copy,
R: Renderer<ColourFormat>,
C: PocketCamera + Send + 'static, C: PocketCamera + Send + 'static,
{ {
pub fn new(window: R, rom: Rom<C>, output: AudioOutput) -> Self { pub fn new(
window: Sender<RendererMessage<ColourFormat>>,
rom: Rom<C>,
output: AudioOutput,
) -> Self {
Self { Self {
window, window,
tile_window: None, tile_window: None,
@ -270,7 +272,7 @@ where
pub fn new_with_config( pub fn new_with_config(
config: crate::config::Config, config: crate::config::Config,
config_dir: PathBuf, config_dir: PathBuf,
window: R, window: Sender<RendererMessage<ColourFormat>>,
rom: Rom<C>, rom: Rom<C>,
output: AudioOutput, output: AudioOutput,
) -> Self { ) -> Self {
@ -337,12 +339,18 @@ where
self self
} }
pub fn with_tile_window(mut self, window: Option<R>) -> Self { pub fn with_tile_window(
mut self,
window: Option<Sender<RendererMessage<ColourFormat>>>,
) -> Self {
self.tile_window = window; self.tile_window = window;
self self
} }
pub fn with_layer_window(mut self, window: Option<R>) -> Self { pub fn with_layer_window(
mut self,
window: Option<Sender<RendererMessage<ColourFormat>>>,
) -> Self {
self.layer_window = window; self.layer_window = window;
self self
} }

View file

@ -3,7 +3,7 @@
use crate::processor::{memory::Memory, Flags}; use crate::processor::{memory::Memory, Flags};
use connect::{ use connect::{
AudioOutput, CameraWrapper, EmulatorCoreTrait, EmulatorMessage, EmulatorOptions, PocketCamera, AudioOutput, CameraWrapper, EmulatorCoreTrait, EmulatorMessage, EmulatorOptions, PocketCamera,
Renderer, RomFile, RomFile,
}; };
use processor::{ use processor::{
memory::{mmio::gpu::Colour, rom::CgbRomType, OutputTargets}, memory::{mmio::gpu::Colour, rom::CgbRomType, OutputTargets},
@ -34,28 +34,26 @@ pub mod util;
pub const WIDTH: usize = 160; pub const WIDTH: usize = 160;
pub const HEIGHT: usize = 144; pub const HEIGHT: usize = 144;
pub struct EmulatorCore<ColourFormat, R, C> pub struct EmulatorCore<ColourFormat, C>
where where
ColourFormat: From<Colour> + Copy, ColourFormat: From<Colour> + Copy,
R: Renderer<ColourFormat>,
C: PocketCamera + Send + 'static, C: PocketCamera + Send + 'static,
{ {
receiver: Receiver<EmulatorMessage>, receiver: Receiver<EmulatorMessage>,
cpu: Cpu<ColourFormat, R, C>, cpu: Cpu<ColourFormat, C>,
paused: bool, paused: bool,
spooky: PhantomData<C>, spooky: PhantomData<C>,
} }
impl<ColourFormat, R, C> EmulatorCore<ColourFormat, R, C> impl<ColourFormat, C> EmulatorCore<ColourFormat, C>
where where
ColourFormat: From<Colour> + Copy, ColourFormat: From<Colour> + Copy,
R: Renderer<ColourFormat>,
C: PocketCamera + Send + 'static, C: PocketCamera + Send + 'static,
{ {
pub fn init( pub fn init(
paused: bool, paused: bool,
receiver: Receiver<EmulatorMessage>, receiver: Receiver<EmulatorMessage>,
mut options: EmulatorOptions<ColourFormat, R, C>, options: EmulatorOptions<ColourFormat, C>,
camera: Arc<Mutex<CameraWrapper<C>>>, camera: Arc<Mutex<CameraWrapper<C>>>,
) -> Self { ) -> Self {
let rom = options.rom; let rom = options.rom;
@ -73,13 +71,24 @@ where
.load_data() .load_data()
.expect("Error loading bootrom!"); .expect("Error loading bootrom!");
options.window.prepare(WIDTH, HEIGHT); options
options.window.set_title(format!( .window
.send(connect::RendererMessage::Prepare {
width: WIDTH,
height: HEIGHT,
})
.expect("message error");
options
.window
.send(connect::RendererMessage::SetTitle {
title: format!(
"{} on {} on {}", "{} on {} on {}",
rom.get_title(), rom.get_title(),
rom.mbc_type(), rom.mbc_type(),
if is_cgb_mode { "CGB" } else { "DMG" } if is_cgb_mode { "CGB" } else { "DMG" }
)); ),
})
.expect("message error");
Self::new( Self::new(
paused, paused,
@ -103,11 +112,7 @@ where
) )
} }
fn new( fn new(paused: bool, receiver: Receiver<EmulatorMessage>, cpu: Cpu<ColourFormat, C>) -> Self {
paused: bool,
receiver: Receiver<EmulatorMessage>,
cpu: Cpu<ColourFormat, R, C>,
) -> Self {
Self { Self {
receiver, receiver,
cpu, cpu,
@ -166,14 +171,16 @@ where
} }
EmulatorMessage::Start => self.paused = false, EmulatorMessage::Start => self.paused = false,
EmulatorMessage::Pause => self.paused = true, EmulatorMessage::Pause => self.paused = true,
EmulatorMessage::JoypadUpdate(new_state) => {
self.cpu.next_joypad_state = Some(new_state)
}
} }
} }
} }
impl<ColourFormat, R, C> EmulatorCoreTrait for EmulatorCore<ColourFormat, R, C> impl<ColourFormat, C> EmulatorCoreTrait for EmulatorCore<ColourFormat, C>
where where
ColourFormat: From<Colour> + Copy, ColourFormat: From<Colour> + Copy,
R: Renderer<ColourFormat>,
C: PocketCamera + Send + 'static, C: PocketCamera + Send + 'static,
{ {
fn replace_output(&mut self, new: AudioOutput) { fn replace_output(&mut self, new: AudioOutput) {

View file

@ -1,15 +1,14 @@
pub mod primitives; pub mod primitives;
use crate::{ use crate::{
connect::{PocketCamera, Renderer}, connect::PocketCamera,
processor::{memory::mmio::gpu::Colour, Cpu, Direction, Flags, Reg8, SplitRegister}, processor::{memory::mmio::gpu::Colour, Cpu, Direction, Flags, Reg8, SplitRegister},
util::{clear_bit, get_bit, set_bit}, util::{clear_bit, get_bit, set_bit},
}; };
impl<ColourFormat, R, C> Cpu<ColourFormat, R, C> impl<ColourFormat, C> Cpu<ColourFormat, C>
where where
ColourFormat: From<Colour> + Copy, ColourFormat: From<Colour> + Copy,
R: Renderer<ColourFormat>,
C: PocketCamera + Send + 'static, C: PocketCamera + Send + 'static,
{ {
pub(crate) fn and(&mut self, first: u8, second: u8) -> u8 { pub(crate) fn and(&mut self, first: u8, second: u8) -> u8 {

View file

@ -1,14 +1,13 @@
use crate::{ use crate::{
connect::{PocketCamera, Renderer}, connect::PocketCamera,
processor::{memory::mmio::gpu::Colour, Cpu, Direction, Flags, SplitRegister}, processor::{memory::mmio::gpu::Colour, Cpu, Direction, Flags, SplitRegister},
util::{as_signed, get_bit, get_rotation_carry, rotate, Nibbles}, util::{as_signed, get_bit, get_rotation_carry, rotate, Nibbles},
}; };
use std::ops::{BitAnd, BitOr}; use std::ops::{BitAnd, BitOr};
impl<ColourFormat, R, C> Cpu<ColourFormat, R, C> impl<ColourFormat, C> Cpu<ColourFormat, C>
where where
ColourFormat: From<Colour> + Copy, ColourFormat: From<Colour> + Copy,
R: Renderer<ColourFormat>,
C: PocketCamera + Send + 'static, C: PocketCamera + Send + 'static,
{ {
pub(crate) fn pop_word(&mut self) -> u16 { pub(crate) fn pop_word(&mut self) -> u16 {

View file

@ -1,4 +1,4 @@
use std::marker::PhantomData; use std::{marker::PhantomData, sync::mpsc::Sender};
pub use self::rom::Rom; pub use self::rom::Rom;
use self::{ use self::{
@ -10,7 +10,9 @@ use self::{
}, },
}; };
use crate::{ use crate::{
connect::{AudioOutput, CameraWrapperRef, JoypadState, PocketCamera, Renderer, SerialTarget}, connect::{
AudioOutput, CameraWrapperRef, JoypadState, PocketCamera, RendererMessage, SerialTarget,
},
Cpu, Cpu,
}; };
@ -85,10 +87,9 @@ struct CgbPeripherals {
double_speed: DoubleSpeed, double_speed: DoubleSpeed,
} }
pub struct Memory<ColourFormat, R, C> pub struct Memory<ColourFormat, C>
where where
ColourFormat: From<Colour> + Copy, ColourFormat: From<Colour> + Copy,
R: Renderer<ColourFormat>,
C: PocketCamera + Send + 'static, C: PocketCamera + Send + 'static,
{ {
bootrom: Option<Vec<u8>>, bootrom: Option<Vec<u8>>,
@ -101,7 +102,7 @@ where
pub(super) user_mode: bool, pub(super) user_mode: bool,
oam_dma: OamDma, oam_dma: OamDma,
joypad: Joypad, joypad: Joypad,
gpu: Gpu<ColourFormat, R>, gpu: Gpu<ColourFormat>,
apu: Apu, apu: Apu,
serial: Serial, serial: Serial,
timers: Timer, timers: Timer,
@ -109,33 +110,31 @@ where
cgb_peripherals: Option<CgbPeripherals>, cgb_peripherals: Option<CgbPeripherals>,
} }
pub(crate) struct OutputTargets<ColourFormat, R, C> pub(crate) struct OutputTargets<ColourFormat, C>
where where
ColourFormat: From<Colour> + Copy, ColourFormat: From<Colour> + Copy,
R: Renderer<ColourFormat>,
C: PocketCamera + Send + 'static, C: PocketCamera + Send + 'static,
{ {
window: R, window: Sender<RendererMessage<ColourFormat>>,
audio: AudioOutput, audio: AudioOutput,
serial_target: SerialTarget, serial_target: SerialTarget,
tile_window: Option<R>, tile_window: Option<Sender<RendererMessage<ColourFormat>>>,
layer_window: Option<R>, layer_window: Option<Sender<RendererMessage<ColourFormat>>>,
camera: CameraWrapperRef<C>, camera: CameraWrapperRef<C>,
_phantom: PhantomData<ColourFormat>, _phantom: PhantomData<ColourFormat>,
} }
impl<ColourFormat, R, C> OutputTargets<ColourFormat, R, C> impl<ColourFormat, C> OutputTargets<ColourFormat, C>
where where
ColourFormat: From<Colour> + Copy, ColourFormat: From<Colour> + Copy,
R: Renderer<ColourFormat>,
C: PocketCamera + Send + 'static, C: PocketCamera + Send + 'static,
{ {
pub(crate) fn new( pub(crate) fn new(
window: R, window: Sender<RendererMessage<ColourFormat>>,
audio: AudioOutput, audio: AudioOutput,
serial_target: SerialTarget, serial_target: SerialTarget,
tile_window: Option<R>, tile_window: Option<Sender<RendererMessage<ColourFormat>>>,
layer_window: Option<R>, layer_window: Option<Sender<RendererMessage<ColourFormat>>>,
camera: CameraWrapperRef<C>, camera: CameraWrapperRef<C>,
) -> Self { ) -> Self {
Self { Self {
@ -150,17 +149,16 @@ where
} }
} }
impl<ColourFormat, R, C> Memory<ColourFormat, R, C> impl<ColourFormat, C> Memory<ColourFormat, C>
where where
ColourFormat: From<Colour> + Copy, ColourFormat: From<Colour> + Copy,
R: Renderer<ColourFormat>,
C: PocketCamera + Send + 'static, C: PocketCamera + Send + 'static,
{ {
pub(crate) fn init( pub(crate) fn init(
cgb: bool, cgb: bool,
bootrom: Vec<u8>, bootrom: Vec<u8>,
rom: Rom<C>, rom: Rom<C>,
output: OutputTargets<ColourFormat, R, C>, output: OutputTargets<ColourFormat, C>,
) -> Self { ) -> Self {
Self { Self {
bootrom: Some(bootrom), bootrom: Some(bootrom),
@ -255,7 +253,12 @@ where
self.rom.set(address, data); self.rom.set(address, data);
if self.rom.can_rumble() { if self.rom.can_rumble() {
// rumble // rumble
self.gpu.window.set_rumble(self.rom.is_rumbling()) self.gpu
.window
.send(RendererMessage::Rumble {
rumble: self.rom.is_rumbling(),
})
.expect("message error");
} }
} }
Address::Vram(address) => self.gpu.set_vram(address, data), Address::Vram(address) => self.gpu.set_vram(address, data),
@ -416,10 +419,9 @@ where
} }
} }
impl<ColourFormat, R, C> Cpu<ColourFormat, R, C> impl<ColourFormat, C> Cpu<ColourFormat, C>
where where
ColourFormat: From<Colour> + Copy, ColourFormat: From<Colour> + Copy,
R: Renderer<ColourFormat>,
C: PocketCamera + Send + 'static, C: PocketCamera + Send + 'static,
{ {
pub fn increment_timers(&mut self, machine_cycles: usize) { pub fn increment_timers(&mut self, machine_cycles: usize) {
@ -465,15 +467,17 @@ where
.interrupts .interrupts
.set_interrupt(Interrupt::LcdStat, gpu_interrupts.lcd_stat); .set_interrupt(Interrupt::LcdStat, gpu_interrupts.lcd_stat);
if gpu_interrupts.vblank { if let Some(next_joypad_state) = self.next_joypad_state.take() {
self.memory let joypad_interrupt = self.memory.update_pressed_keys(next_joypad_state);
.interrupts
.set_interrupt(Interrupt::Vblank, true);
let latest_state = self.memory.gpu.window.latest_joypad_state();
let joypad_interrupt = self.memory.update_pressed_keys(latest_state);
self.memory self.memory
.interrupts .interrupts
.set_interrupt(Interrupt::Joypad, joypad_interrupt); .set_interrupt(Interrupt::Joypad, joypad_interrupt);
} }
if gpu_interrupts.vblank {
self.memory
.interrupts
.set_interrupt(Interrupt::Vblank, true);
}
} }
} }

View file

@ -1,5 +1,5 @@
use crate::{ use crate::{
connect::{PocketCamera, Renderer}, connect::PocketCamera,
processor::memory::{mmio::gpu::Colour, Memory}, processor::memory::{mmio::gpu::Colour, Memory},
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -20,10 +20,9 @@ impl DoubleSpeed {
} }
} }
impl<ColourFormat, R, C> Memory<ColourFormat, R, C> impl<ColourFormat, C> Memory<ColourFormat, C>
where where
ColourFormat: From<Colour> + Copy, ColourFormat: From<Colour> + Copy,
R: Renderer<ColourFormat>,
C: PocketCamera + Send + 'static, C: PocketCamera + Send + 'static,
{ {
pub(crate) fn is_double_speed(&self) -> bool { pub(crate) fn is_double_speed(&self) -> bool {

View file

@ -1,5 +1,5 @@
use crate::{ use crate::{
connect::{PocketCamera, Renderer}, connect::PocketCamera,
processor::{ processor::{
memory::{ memory::{
addresses::{AddressMarker, VramDmaAddress}, addresses::{AddressMarker, VramDmaAddress},
@ -80,10 +80,9 @@ impl VramDma {
} }
} }
impl<ColourFormat, R, C> Memory<ColourFormat, R, C> impl<ColourFormat, C> Memory<ColourFormat, C>
where where
ColourFormat: From<Colour> + Copy, ColourFormat: From<Colour> + Copy,
R: Renderer<ColourFormat>,
C: PocketCamera + Send + 'static, C: PocketCamera + Send + 'static,
{ {
pub(crate) fn vram_dma_tick(&mut self) -> usize { pub(crate) fn vram_dma_tick(&mut self) -> usize {

View file

@ -1,3 +1,5 @@
use std::sync::mpsc::Sender;
pub(crate) use self::types::DrawMode; pub(crate) use self::types::DrawMode;
use self::{ use self::{
cgb::CgbData, cgb::CgbData,
@ -8,7 +10,7 @@ use self::{
}, },
}; };
use crate::{ use crate::{
connect::Renderer, connect::RendererMessage,
processor::memory::addresses::{OamAddress, VramAddress}, processor::memory::addresses::{OamAddress, VramAddress},
util::{clear_bit, get_bit}, util::{clear_bit, get_bit},
HEIGHT, WIDTH, HEIGHT, WIDTH,
@ -23,15 +25,14 @@ mod types;
const TILE_WINDOW_WIDTH: usize = 16 * 8; const TILE_WINDOW_WIDTH: usize = 16 * 8;
const TILE_WINDOW_HEIGHT: usize = 24 * 8; const TILE_WINDOW_HEIGHT: usize = 24 * 8;
pub struct Gpu<ColourFormat, R> pub struct Gpu<ColourFormat>
where where
ColourFormat: From<Colour> + Copy, ColourFormat: From<Colour> + Copy,
R: Renderer<ColourFormat>,
{ {
pub buffer: [ColourFormat; WIDTH * HEIGHT], pub buffer: [ColourFormat; WIDTH * HEIGHT],
pub vram: Vram, pub vram: Vram,
pub oam: Oam, pub oam: Oam,
pub window: R, pub window: Sender<RendererMessage<ColourFormat>>,
is_bg_zero: [bool; WIDTH], is_bg_zero: [bool; WIDTH],
is_bg_priority: [bool; WIDTH], is_bg_priority: [bool; WIDTH],
lcdc: Lcdc, lcdc: Lcdc,
@ -39,8 +40,11 @@ where
mode_clock: usize, mode_clock: usize,
scanline: u8, scanline: u8,
lyc: u8, lyc: u8,
tile_window: Option<TileWindow<ColourFormat, R>>, tile_window: Option<TileWindow<ColourFormat>>,
layer_window: Option<(R, [ColourFormat; WIDTH * HEIGHT * 3])>, layer_window: Option<(
Sender<RendererMessage<ColourFormat>>,
[ColourFormat; WIDTH * HEIGHT * 3],
)>,
window_lc: u8, window_lc: u8,
has_window_been_enabled: bool, has_window_been_enabled: bool,
bg_palette: Palette, bg_palette: Palette,
@ -54,24 +58,28 @@ where
cgb_data: Option<CgbData>, cgb_data: Option<CgbData>,
} }
impl<ColourFormat, R> Gpu<ColourFormat, R> impl<ColourFormat> Gpu<ColourFormat>
where where
ColourFormat: From<Colour> + Copy, ColourFormat: From<Colour> + Copy,
R: Renderer<ColourFormat>,
{ {
pub fn new( pub fn new(
cgb: bool, cgb: bool,
window: R, window: Sender<RendererMessage<ColourFormat>>,
tile_window_renderer: Option<R>, tile_window_renderer: Option<Sender<RendererMessage<ColourFormat>>>,
layer_window_renderer: Option<R>, layer_window_renderer: Option<Sender<RendererMessage<ColourFormat>>>,
) -> Self { ) -> Self {
let tile_window = tile_window_renderer let tile_window = tile_window_renderer
.map(|tile_window_renderer| TileWindow::new(tile_window_renderer, cgb)); .map(|tile_window_renderer| TileWindow::new(tile_window_renderer, cgb));
let buffer = [get_blank_colour(cgb); WIDTH * HEIGHT]; let buffer = [get_blank_colour(cgb); WIDTH * HEIGHT];
let layer_window = layer_window_renderer.map(|mut layer_window_renderer| { let layer_window = layer_window_renderer.map(|layer_window_renderer| {
layer_window_renderer.prepare(WIDTH, HEIGHT * 3); layer_window_renderer
.send(RendererMessage::Prepare {
width: WIDTH,
height: HEIGHT * 3,
})
.expect("message error");
( (
layer_window_renderer, layer_window_renderer,
[get_blank_colour(cgb); WIDTH * HEIGHT * 3], [get_blank_colour(cgb); WIDTH * HEIGHT * 3],
@ -484,9 +492,18 @@ where
} }
fn render_window(&mut self) { fn render_window(&mut self) {
self.window.display(&self.buffer); let mut buffer = Vec::new();
buffer.extend_from_slice(&self.buffer);
self.window
.send(RendererMessage::display_message(buffer))
.expect("message error");
if let Some((ref mut window, ref mut buffer)) = self.layer_window { if let Some((ref mut window, ref mut buffer)) = self.layer_window {
window.display(buffer); let mut new_buffer = Vec::new();
new_buffer.extend_from_slice(buffer);
window
.send(RendererMessage::display_message(new_buffer))
.expect("message error");
for val in buffer { for val in buffer {
*val = get_blank_colour(self.cgb_data.is_some()); *val = get_blank_colour(self.cgb_data.is_some());

View file

@ -1,17 +1,13 @@
use crate::{ use crate::util::{get_bit, set_or_clear_bit};
connect::Renderer,
util::{get_bit, set_or_clear_bit},
};
use super::{ use super::{
types::{DrawMode, ObjSize, Palette, TiledataArea, TilemapArea}, types::{DrawMode, ObjSize, Palette, TiledataArea, TilemapArea},
Colour, Gpu, Colour, Gpu,
}; };
impl<ColourFormat, R> Gpu<ColourFormat, R> impl<ColourFormat> Gpu<ColourFormat>
where where
ColourFormat: From<Colour> + Copy, ColourFormat: From<Colour> + Copy,
R: Renderer<ColourFormat>,
{ {
pub fn update_lcdc(&mut self, data: u8) { pub fn update_lcdc(&mut self, data: u8) {
self.lcdc.enable = get_bit(data, 7); self.lcdc.enable = get_bit(data, 7);

View file

@ -1,7 +1,6 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{ use crate::{
connect::Renderer,
processor::memory::addresses::{AddressMarker, CgbPaletteAddress}, processor::memory::addresses::{AddressMarker, CgbPaletteAddress},
util::get_bit, util::get_bit,
}; };
@ -78,10 +77,9 @@ impl CgbPalette {
} }
} }
impl<ColourFormat, R> Gpu<ColourFormat, R> impl<ColourFormat> Gpu<ColourFormat>
where where
ColourFormat: From<Colour> + Copy, ColourFormat: From<Colour> + Copy,
R: Renderer<ColourFormat>,
{ {
pub(crate) fn get_cgb_palette(&self, address: CgbPaletteAddress) -> u8 { pub(crate) fn get_cgb_palette(&self, address: CgbPaletteAddress) -> u8 {
if let Some(cgb_data) = &self.cgb_data { if let Some(cgb_data) = &self.cgb_data {

View file

@ -1,5 +1,7 @@
use std::sync::mpsc::Sender;
use crate::{ use crate::{
connect::Renderer, connect::RendererMessage,
processor::memory::mmio::gpu::{Palette, TILE_WINDOW_HEIGHT, TILE_WINDOW_WIDTH}, processor::memory::mmio::gpu::{Palette, TILE_WINDOW_HEIGHT, TILE_WINDOW_WIDTH},
util::get_bit, util::get_bit,
}; };
@ -9,28 +11,31 @@ use super::{
Colour, Colour,
}; };
pub(super) struct TileWindow<ColourFormat, R> pub(super) struct TileWindow<ColourFormat>
where where
ColourFormat: From<Colour> + Copy, ColourFormat: From<Colour> + Copy,
R: Renderer<ColourFormat>,
{ {
sprite_buffer: Vec<ColourFormat>, sprite_buffer: Vec<ColourFormat>,
sprite_renderer: R, sprite_renderer: Sender<RendererMessage<ColourFormat>>,
currently_cgb: bool, currently_cgb: bool,
} }
impl<ColourFormat, R> TileWindow<ColourFormat, R> impl<ColourFormat> TileWindow<ColourFormat>
where where
ColourFormat: From<Colour> + Copy, ColourFormat: From<Colour> + Copy,
R: Renderer<ColourFormat>,
{ {
pub(super) fn new(mut window: R, cgb: bool) -> Self { pub(super) fn new(window: Sender<RendererMessage<ColourFormat>>, cgb: bool) -> Self {
let current_width = if cgb { let current_width = if cgb {
TILE_WINDOW_WIDTH * 2 TILE_WINDOW_WIDTH * 2
} else { } else {
TILE_WINDOW_WIDTH TILE_WINDOW_WIDTH
}; };
window.prepare(current_width, TILE_WINDOW_HEIGHT); window
.send(RendererMessage::Prepare {
width: current_width,
height: TILE_WINDOW_HEIGHT,
})
.expect("message error");
Self { Self {
sprite_buffer: vec![ sprite_buffer: vec![
ColourInner::Error.rgb_bytes(None).into(); ColourInner::Error.rgb_bytes(None).into();
@ -55,7 +60,11 @@ where
TILE_WINDOW_WIDTH TILE_WINDOW_WIDTH
}; };
self.sprite_renderer self.sprite_renderer
.resize(current_width, TILE_WINDOW_HEIGHT); .send(RendererMessage::Resize {
width: current_width,
height: TILE_WINDOW_HEIGHT,
})
.expect("message error");
self.sprite_buffer = self.sprite_buffer =
vec![ColourInner::Error.rgb_bytes(None).into(); current_width * TILE_WINDOW_HEIGHT]; vec![ColourInner::Error.rgb_bytes(None).into(); current_width * TILE_WINDOW_HEIGHT];
} }
@ -63,7 +72,9 @@ where
self.draw_row(tile_y, data_begin, palette, memory, is_cgb_mode); self.draw_row(tile_y, data_begin, palette, memory, is_cgb_mode);
} }
self.sprite_renderer.display(&self.sprite_buffer); self.sprite_renderer
.send(RendererMessage::display_message(self.sprite_buffer.clone()))
.expect("message error");
} }
fn draw_row( fn draw_row(

View file

@ -44,6 +44,19 @@ impl JoypadState {
self.a = false; self.a = false;
self.b = false; self.b = false;
} }
pub fn set(&mut self, button: JoypadButtons, state: bool) {
*match button {
JoypadButtons::Down => &mut self.down,
JoypadButtons::Up => &mut self.up,
JoypadButtons::Left => &mut self.left,
JoypadButtons::Right => &mut self.right,
JoypadButtons::Start => &mut self.start,
JoypadButtons::Select => &mut self.select,
JoypadButtons::B => &mut self.b,
JoypadButtons::A => &mut self.a,
} = state;
}
} }
impl Joypad { impl Joypad {

View file

@ -1,6 +1,6 @@
use super::gpu::Colour; use super::gpu::Colour;
use crate::{ use crate::{
connect::{PocketCamera, Renderer}, connect::PocketCamera,
processor::{memory::Memory, SplitRegister}, processor::{memory::Memory, SplitRegister},
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -35,10 +35,9 @@ impl OamDma {
} }
} }
impl<ColourFormat, R, C> Memory<ColourFormat, R, C> impl<ColourFormat, C> Memory<ColourFormat, C>
where where
ColourFormat: From<Colour> + Copy, ColourFormat: From<Colour> + Copy,
R: Renderer<ColourFormat>,
C: PocketCamera + Send + 'static, C: PocketCamera + Send + 'static,
{ {
pub(crate) fn oam_dma_tick(&mut self, steps: usize) { pub(crate) fn oam_dma_tick(&mut self, steps: usize) {

View file

@ -1,7 +1,7 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use self::memory::{mmio::gpu::Colour, Interrupt, Memory}; use self::memory::{mmio::gpu::Colour, Interrupt, Memory};
use crate::connect::{PocketCamera, Renderer}; use crate::connect::{JoypadState, PocketCamera};
mod instructions; mod instructions;
pub mod memory; pub mod memory;
@ -20,13 +20,12 @@ pub(crate) enum Direction {
Right, Right,
} }
pub struct Cpu<ColourFormat, R, C> pub struct Cpu<ColourFormat, C>
where where
ColourFormat: From<Colour> + Copy, ColourFormat: From<Colour> + Copy,
R: Renderer<ColourFormat>,
C: PocketCamera + Send + 'static, C: PocketCamera + Send + 'static,
{ {
pub memory: Memory<ColourFormat, R, C>, pub memory: Memory<ColourFormat, C>,
pub reg: Registers, pub reg: Registers,
pub last_instruction: u8, pub last_instruction: u8,
pub last_instruction_addr: u16, pub last_instruction_addr: u16,
@ -34,15 +33,15 @@ where
should_halt_bug: bool, should_halt_bug: bool,
pub(super) cycle_count: usize, pub(super) cycle_count: usize,
pub(crate) is_skipping: bool, pub(crate) is_skipping: bool,
pub(super) next_joypad_state: Option<JoypadState>,
} }
impl<ColourFormat, R, C> Cpu<ColourFormat, R, C> impl<ColourFormat, C> Cpu<ColourFormat, C>
where where
ColourFormat: From<Colour> + Copy, ColourFormat: From<Colour> + Copy,
R: Renderer<ColourFormat>,
C: PocketCamera + Send + 'static, C: PocketCamera + Send + 'static,
{ {
pub(crate) fn new(memory: Memory<ColourFormat, R, C>, run_bootrom: bool) -> Self { pub(crate) fn new(memory: Memory<ColourFormat, C>, run_bootrom: bool) -> Self {
Self { Self {
memory, memory,
reg: Registers::init(), reg: Registers::init(),
@ -52,6 +51,7 @@ where
should_halt_bug: false, should_halt_bug: false,
cycle_count: 0, cycle_count: 0,
is_skipping: !run_bootrom, is_skipping: !run_bootrom,
next_joypad_state: None,
} }
} }

View file

@ -1,5 +1,5 @@
use crate::{ use crate::{
connect::{PocketCamera, Renderer}, connect::PocketCamera,
processor::{ processor::{
instructions::{res, set}, instructions::{res, set},
Cpu, Flags, Reg8, SplitRegister, Cpu, Flags, Reg8, SplitRegister,
@ -9,10 +9,9 @@ use crate::{
use super::memory::mmio::gpu::Colour; use super::memory::mmio::gpu::Colour;
impl<ColourFormat, R, C> Cpu<ColourFormat, R, C> impl<ColourFormat, C> Cpu<ColourFormat, C>
where where
ColourFormat: From<Colour> + Copy, ColourFormat: From<Colour> + Copy,
R: Renderer<ColourFormat>,
C: PocketCamera + Send + 'static, C: PocketCamera + Send + 'static,
{ {
pub fn run_opcode(&mut self, opcode: u8) -> usize { pub fn run_opcode(&mut self, opcode: u8) -> usize {

View file

@ -225,6 +225,7 @@ struct VulkanWindowInner {
swapchain: SwapchainData, swapchain: SwapchainData,
surface: SurfaceData, surface: SurfaceData,
framebuffers: FramebufferData, framebuffers: FramebufferData,
image_slice: Align<u8>,
frame_counter: usize, frame_counter: usize,
} }
@ -435,6 +436,22 @@ impl VulkanWindowInner {
.device .device
.create_graphics_pipelines(vk::PipelineCache::null(), &[graphic_pipeline_infos], None) .create_graphics_pipelines(vk::PipelineCache::null(), &[graphic_pipeline_infos], None)
.unwrap(); .unwrap();
let image_ptr = vulkan_data
.device
.map_memory(
swapchain.shader_input_image_buffer_memory,
0,
swapchain.shader_input_image_buffer_memory_req.size,
vk::MemoryMapFlags::empty(),
)
.unwrap();
let image_slice: Align<u8> = Align::new(
image_ptr,
std::mem::align_of::<u8>() as u64,
swapchain.shader_input_image_buffer_memory_req.size,
);
Self { Self {
vertex_input_buffer, vertex_input_buffer,
renderpass, renderpass,
@ -446,6 +463,7 @@ impl VulkanWindowInner {
surface, surface,
framebuffers, framebuffers,
vulkan_data, vulkan_data,
image_slice,
frame_counter: 0, frame_counter: 0,
} }
} }
@ -464,35 +482,8 @@ impl VulkanWindowInner {
} }
unsafe fn new_frame(&mut self, buffer: &[[u8; 4]]) { unsafe fn new_frame(&mut self, buffer: &[[u8; 4]]) {
let image_ptr = self self.image_slice
.vulkan_data .copy_from_slice(bytemuck::cast_slice(buffer));
.device
.map_memory(
self.swapchain.shader_input_image_buffer_memory,
0,
self.swapchain.shader_input_image_buffer_memory_req.size,
vk::MemoryMapFlags::empty(),
)
.unwrap();
let mut image_slice: Align<u8> = Align::new(
image_ptr,
std::mem::align_of::<u8>() as u64,
self.swapchain.shader_input_image_buffer_memory_req.size,
);
image_slice.copy_from_slice(bytemuck::cast_slice(buffer));
self.vulkan_data
.device
.unmap_memory(self.swapchain.shader_input_image_buffer_memory);
self.vulkan_data
.device
.bind_buffer_memory(
self.swapchain.shader_input_image_buffer,
self.swapchain.shader_input_image_buffer_memory,
0,
)
.unwrap();
record_submit_commandbuffer( record_submit_commandbuffer(
&self.vulkan_data.device, &self.vulkan_data.device,