From 279774d4f9b19f1b6b520f3e0a3be275a86c49a8 Mon Sep 17 00:00:00 2001 From: Thomas Date: Sat, 11 Nov 2023 22:41:42 +0100 Subject: [PATCH] Implement joypad input --- src/bin/souper/main.rs | 65 +++++++++++++++++++++++-- src/frontend/mod.rs | 7 --- src/frontend/sdl.rs | 19 ++++---- src/frontend/test.rs | 4 -- src/snes/bus/mainbus.rs | 40 ++++++++++++++-- src/snes/joypad.rs | 102 ++++++++++++++++++++++++++++++++++++++++ src/snes/mod.rs | 1 + src/test/mod.rs | 4 +- 8 files changed, 213 insertions(+), 29 deletions(-) create mode 100644 src/snes/joypad.rs diff --git a/src/bin/souper/main.rs b/src/bin/souper/main.rs index 9ac0c5e..1277709 100644 --- a/src/bin/souper/main.rs +++ b/src/bin/souper/main.rs @@ -3,14 +3,34 @@ use std::io::{stdin, Read}; use anyhow::Result; use clap::Parser; +use sdl2::event::Event; +use sdl2::keyboard::Keycode; -use souper::frontend::sdl::SDLRenderer; +use souper::frontend::sdl::{SDLEventPump, SDLRenderer}; use souper::frontend::Renderer; use souper::snes::bus::mainbus::{BusTrace, Mainbus}; use souper::snes::bus::Bus; use souper::snes::cpu_65816::cpu::Cpu65816; +use souper::snes::joypad::{Button, Joypad, JoypadEvent}; use souper::snes::ppu::{SCREEN_HEIGHT, SCREEN_WIDTH}; +fn map_keycode(keycode: Keycode) -> Option<(usize, Button)> { + match keycode { + Keycode::A => Some((0, Button::A)), + Keycode::B => Some((0, Button::B)), + Keycode::X => Some((0, Button::X)), + Keycode::Y => Some((0, Button::Y)), + Keycode::Space => Some((0, Button::Start)), + Keycode::Period => Some((0, Button::Select)), + Keycode::Up => Some((0, Button::Up)), + Keycode::Down => Some((0, Button::Down)), + Keycode::Left => Some((0, Button::Left)), + Keycode::Right => Some((0, Button::Right)), + + _ => None, + } +} + #[derive(Parser)] #[command( about = "SNES Emulator", @@ -54,13 +74,16 @@ fn main() -> Result<()> { _ => panic!("Illogical cartridge file size: 0x{:08X}", f.len()), }; + let (joypads, joypad_senders) = Joypad::new_channel_all(); let display = SDLRenderer::new(SCREEN_WIDTH, SCREEN_HEIGHT)?; - let bus = Mainbus::::new(&f[load_offset..], args.bustrace, display); + let eventpump = SDLEventPump::new(); + let bus = Mainbus::::new(&f[load_offset..], args.bustrace, display, joypads); let reset = bus.read16(0xFFFC); let mut cpu = Cpu65816::>::new(bus, reset); - loop { + let mut eventpoll = 0; + 'mainloop: loop { if args.verbose { println!("{}", cpu.dump_state()); } @@ -70,5 +93,41 @@ fn main() -> Result<()> { } cpu.step()?; + + eventpoll += 1; + // Polling SDL events is too expensive to do every step.. + // TODO do this once per VBlank or something.. + if eventpoll == 1000 { + eventpoll = 0; + while let Some(event) = eventpump.poll() { + match event { + // Application exit + Event::KeyDown { + keycode: Some(Keycode::Escape), + .. + } + | Event::Quit { .. } => break 'mainloop, + + // Controller input + Event::KeyDown { + keycode: Some(k), .. + } => { + if let Some((padidx, button)) = map_keycode(k) { + joypad_senders[padidx].send(JoypadEvent::Down(button))?; + } + } + Event::KeyUp { + keycode: Some(k), .. + } => { + if let Some((padidx, button)) = map_keycode(k) { + joypad_senders[padidx].send(JoypadEvent::Up(button))?; + } + } + _ => (), + } + } + } } + + Ok(()) } diff --git a/src/frontend/mod.rs b/src/frontend/mod.rs index bc11db4..1115f46 100644 --- a/src/frontend/mod.rs +++ b/src/frontend/mod.rs @@ -17,9 +17,6 @@ pub trait Renderer { /// Renders changes to screen fn update(&mut self) -> Result<()>; - - /// TODO move to input component - fn poll(&mut self) -> bool; } pub struct NullRenderer {} @@ -33,8 +30,4 @@ impl Renderer for NullRenderer { fn update(&mut self) -> Result<()> { Ok(()) } - - fn poll(&mut self) -> bool { - true - } } diff --git a/src/frontend/sdl.rs b/src/frontend/sdl.rs index 0a7b915..827b86d 100644 --- a/src/frontend/sdl.rs +++ b/src/frontend/sdl.rs @@ -93,19 +93,18 @@ impl Renderer for SDLRenderer { Ok(()) } +} + +pub struct SDLEventPump {} +impl SDLEventPump { + pub fn new() -> Self { + Self {} + } - /// TODO move to input component - fn poll(&mut self) -> bool { + pub fn poll(&self) -> Option { SDL.with(|cell| { let mut sdls = cell.borrow_mut(); - for ev in sdls.pump.poll_iter() { - match ev { - Event::Quit { .. } => return false, - _ => (), - } - } - - true + sdls.pump.poll_event() }) } } diff --git a/src/frontend/test.rs b/src/frontend/test.rs index 60a8e76..30b5913 100644 --- a/src/frontend/test.rs +++ b/src/frontend/test.rs @@ -91,8 +91,4 @@ impl Renderer for TestRenderer { }); Ok(()) } - - fn poll(&mut self) -> bool { - true - } } diff --git a/src/snes/bus/mainbus.rs b/src/snes/bus/mainbus.rs index 5669626..5906d60 100644 --- a/src/snes/bus/mainbus.rs +++ b/src/snes/bus/mainbus.rs @@ -5,6 +5,7 @@ use dbg_hex::dbg_hex; use crate::frontend::Renderer; use crate::snes::bus::{Address, Bus, BusMember}; +use crate::snes::joypad::{Joypad, JOYPAD_COUNT}; use crate::snes::ppu::PPU; use crate::tickable::{Tickable, Ticks}; @@ -31,6 +32,9 @@ where wram: Vec, trace: BusTrace, + /// Controllers + joypads: [Joypad; JOYPAD_COUNT], + /// Picture Processing Unit ppu: PPU, @@ -209,13 +213,19 @@ impl Mainbus where TRenderer: Renderer, { - pub fn new(cartridge: &[u8], trace: BusTrace, renderer: TRenderer) -> Self { + pub fn new( + cartridge: &[u8], + trace: BusTrace, + renderer: TRenderer, + joypads: [Joypad; JOYPAD_COUNT], + ) -> Self { Self { cartridge: cartridge.to_owned(), wram: vec![0; WRAM_SIZE], trace, dma: [DMAChannel::new(); DMA_CHANNELS], hdmaen: 0, + joypads, ppu: PPU::::new(renderer), @@ -414,6 +424,10 @@ where } // WMADDL/M/H - WRAM Address 0x2181..=0x2183 => None, + // JOYA - Joypad Input Register A (R) + 0x4016 => Some(self.joypads[0].read() | self.joypads[2].read() << 1), + // JOYB - Joypad Input Register B (R) + 0x4017 => Some(self.joypads[1].read() | self.joypads[3].read() << 1 | 0x0C), // NMITIMEN - Interrupt Enable and Joypad Request (W) 0x4200 => None, // WRMPYA - Set unsigned 8bit Multiplicand (W) @@ -459,8 +473,18 @@ where 0x4216 => Some(self.rdmpy as u8), // RDMPYH - Unsigned Division Remainder / Multiply Product (up.8bit) (R) 0x4217 => Some((self.rdmpy >> 8) as u8), - // JOYxx - Joypads - 0x4218..=0x421F => Some(0), + // JOY1L/JOY1H - Joypad 1 (gameport 1, pin 4) (R) + 0x4218 => Some(self.joypads[0].read_auto_low()), + 0x4219 => Some(self.joypads[0].read_auto_high()), + // JOY2L/JOY2H - Joypad 2 (gameport 2, pin 4) (R) + 0x421A => Some(self.joypads[1].read_auto_low()), + 0x421B => Some(self.joypads[1].read_auto_high()), + // JOY3L/JOY3H - Joypad 3 (gameport 1, pin 5) (R) + 0x421C => Some(self.joypads[2].read_auto_low()), + 0x421D => Some(self.joypads[2].read_auto_high()), + // JOY4L/JOY4H - Joypad 4 (gameport 2, pin 5) (R) + 0x421E => Some(self.joypads[3].read_auto_low()), + 0x421F => Some(self.joypads[3].read_auto_high()), // DMA parameter area 0x4300..=0x43FF => { let ch = (addr >> 4) & 0x07; @@ -567,6 +591,8 @@ where self.wmadd.set(addr & WRAM_MASK); Some(()) } + // JOYWR - Joypad Output (W) + 0x4016 => Some(self.joypads.iter_mut().for_each(|j| j.strobe())), // NMITIMEN - Interrupt Enable and Joypad Request (W) 0x4200 => { // TODO joypad @@ -736,7 +762,13 @@ mod tests { use crate::frontend::NullRenderer; fn mainbus() -> Mainbus { - Mainbus::::new(&[], BusTrace::All, NullRenderer::new(0, 0).unwrap()) + let (joypads, _) = Joypad::new_channel_all(); + Mainbus::::new( + &[], + BusTrace::All, + NullRenderer::new(0, 0).unwrap(), + joypads, + ) } #[test] diff --git a/src/snes/joypad.rs b/src/snes/joypad.rs new file mode 100644 index 0000000..1d8eabc --- /dev/null +++ b/src/snes/joypad.rs @@ -0,0 +1,102 @@ +use std::cell::Cell; +use std::sync::mpsc; + +use num_derive::ToPrimitive; +use num_traits::ToPrimitive; +use strum::EnumCount; + +pub const JOYPAD_COUNT: usize = 4; + +pub type Joypads = [Joypad; JOYPAD_COUNT]; +pub type JoypadEventSender = mpsc::Sender; + +#[derive(Debug, ToPrimitive, EnumCount)] +pub enum Button { + B, + Y, + Select, + Start, + Up, + Down, + Left, + Right, + A, + X, + L, + R, +} + +#[derive(Debug)] +pub enum JoypadEvent { + Down(Button), + Up(Button), +} + +#[derive(Debug)] +pub struct Joypad { + pub state: Cell, + pub scan_pos: Cell, + event_recv: mpsc::Receiver, +} + +impl Joypad { + pub fn new(event_recv: mpsc::Receiver) -> Self { + Self { + state: Cell::new(0), + scan_pos: Cell::new(0), + event_recv, + } + } + + pub fn new_channel() -> (Joypad, JoypadEventSender) { + let (tx, rx) = mpsc::channel(); + (Self::new(rx), tx) + } + + pub fn new_channel_all() -> (Joypads, [JoypadEventSender; JOYPAD_COUNT]) { + let mut joypads = vec![]; + let mut senders = vec![]; + for _ in 0..JOYPAD_COUNT { + let (j, s) = Self::new_channel(); + joypads.push(j); + senders.push(s); + } + (joypads.try_into().unwrap(), senders.try_into().unwrap()) + } + + fn poll_events(&self) { + if let Ok(e) = self.event_recv.try_recv() { + match &e { + JoypadEvent::Up(button) => self + .state + .set(self.state.get() & !(1 << button.to_u8().unwrap())), + JoypadEvent::Down(button) => self + .state + .set(self.state.get() | (1 << button.to_u8().unwrap())), + } + } + } + + pub fn strobe(&self) { + self.poll_events(); + self.scan_pos.set(0); + } + + pub fn read(&self) -> u8 { + let pos = self.scan_pos.get() & 0x0F; + if pos == 0 { + self.poll_events(); + } + let v = ((self.state.get() >> pos) & 1) as u8; + self.scan_pos.set(pos + 1); + v + } + + pub fn read_auto_low(&self) -> u8 { + self.state.get().reverse_bits() as u8 + } + + pub fn read_auto_high(&self) -> u8 { + (self.state.get().reverse_bits() >> 8) as u8 + } +} diff --git a/src/snes/mod.rs b/src/snes/mod.rs index f8479e7..3f08ab4 100644 --- a/src/snes/mod.rs +++ b/src/snes/mod.rs @@ -1,3 +1,4 @@ pub mod bus; pub mod cpu_65816; +pub mod joypad; pub mod ppu; diff --git a/src/test/mod.rs b/src/test/mod.rs index 2c84085..1b5e8f6 100644 --- a/src/test/mod.rs +++ b/src/test/mod.rs @@ -9,11 +9,13 @@ use crate::frontend::test::TestRenderer; use crate::snes::bus::mainbus::{BusTrace, Mainbus}; use crate::snes::bus::Bus; use crate::snes::cpu_65816::cpu::Cpu65816; +use crate::snes::joypad::Joypad; use crate::snes::ppu::{SCREEN_HEIGHT, SCREEN_WIDTH}; fn test_display(rom: &[u8], pass_hash: &[u8], time_limit: u128, stable: bool) { let (display, dispstatus) = TestRenderer::new_test(SCREEN_WIDTH, SCREEN_HEIGHT); - let bus = Mainbus::::new(rom, BusTrace::None, display); + let (joypads, _) = Joypad::new_channel_all(); + let bus = Mainbus::::new(rom, BusTrace::None, display, joypads); let reset = bus.read16(0xFFFC); let mut cpu = Cpu65816::>::new(bus, reset);