Skip to content

Commit

Permalink
Implement joypad input
Browse files Browse the repository at this point in the history
  • Loading branch information
twvd committed Nov 11, 2023
1 parent a4233cf commit 279774d
Show file tree
Hide file tree
Showing 8 changed files with 213 additions and 29 deletions.
65 changes: 62 additions & 3 deletions src/bin/souper/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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::<SDLRenderer>::new(&f[load_offset..], args.bustrace, display);
let eventpump = SDLEventPump::new();
let bus = Mainbus::<SDLRenderer>::new(&f[load_offset..], args.bustrace, display, joypads);

let reset = bus.read16(0xFFFC);
let mut cpu = Cpu65816::<Mainbus<SDLRenderer>>::new(bus, reset);

loop {
let mut eventpoll = 0;
'mainloop: loop {
if args.verbose {
println!("{}", cpu.dump_state());
}
Expand All @@ -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(())
}
7 changes: 0 additions & 7 deletions src/frontend/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}
Expand All @@ -33,8 +30,4 @@ impl Renderer for NullRenderer {
fn update(&mut self) -> Result<()> {
Ok(())
}

fn poll(&mut self) -> bool {
true
}
}
19 changes: 9 additions & 10 deletions src/frontend/sdl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Event> {
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()
})
}
}
4 changes: 0 additions & 4 deletions src/frontend/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,4 @@ impl Renderer for TestRenderer {
});
Ok(())
}

fn poll(&mut self) -> bool {
true
}
}
40 changes: 36 additions & 4 deletions src/snes/bus/mainbus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand All @@ -31,6 +32,9 @@ where
wram: Vec<u8>,
trace: BusTrace,

/// Controllers
joypads: [Joypad; JOYPAD_COUNT],

/// Picture Processing Unit
ppu: PPU<TRenderer>,

Expand Down Expand Up @@ -209,13 +213,19 @@ impl<TRenderer> Mainbus<TRenderer>
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::<TRenderer>::new(renderer),

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -736,7 +762,13 @@ mod tests {
use crate::frontend::NullRenderer;

fn mainbus() -> Mainbus<NullRenderer> {
Mainbus::<NullRenderer>::new(&[], BusTrace::All, NullRenderer::new(0, 0).unwrap())
let (joypads, _) = Joypad::new_channel_all();
Mainbus::<NullRenderer>::new(
&[],
BusTrace::All,
NullRenderer::new(0, 0).unwrap(),
joypads,
)
}

#[test]
Expand Down
102 changes: 102 additions & 0 deletions src/snes/joypad.rs
Original file line number Diff line number Diff line change
@@ -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<JoypadEvent>;

#[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<u16>,
pub scan_pos: Cell<u8>,
event_recv: mpsc::Receiver<JoypadEvent>,
}

impl Joypad {
pub fn new(event_recv: mpsc::Receiver<JoypadEvent>) -> 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
}
}
1 change: 1 addition & 0 deletions src/snes/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod bus;
pub mod cpu_65816;
pub mod joypad;
pub mod ppu;
4 changes: 3 additions & 1 deletion src/test/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<TestRenderer>::new(rom, BusTrace::None, display);
let (joypads, _) = Joypad::new_channel_all();
let bus = Mainbus::<TestRenderer>::new(rom, BusTrace::None, display, joypads);
let reset = bus.read16(0xFFFC);
let mut cpu = Cpu65816::<Mainbus<TestRenderer>>::new(bus, reset);

Expand Down

0 comments on commit 279774d

Please sign in to comment.