core: Start working on a scheduler

A more robust cycle aware event scheduling, to easily implement serial-io, dmg audio channels and improve accuracy.
This brings a slight performance hit :/

I also ran dos2unix on some of the files :D


Former-commit-id: 62f4ba33e3a083b7976d6512ba6f5720ec493aa0
Former-commit-id: a4b3a92cd1eb156bbe9fd0ef77fbb0e7a45660cb
This commit is contained in:
Michel Heily 2020-10-03 22:09:38 +03:00 committed by MishMish
parent b68c73819a
commit 0de8a60006
9 changed files with 1113 additions and 634 deletions

View file

@ -1,29 +1,29 @@
pub mod connector; pub mod connector;
pub mod thread; pub mod thread;
pub mod util { pub mod util {
use jni::objects::{JObject, JValue}; use jni::objects::{JObject, JValue};
use jni::signature::{JavaType, Primitive}; use jni::signature::{JavaType, Primitive};
use jni::JNIEnv; use jni::JNIEnv;
pub fn get_sample_rate(env: &JNIEnv, audio_player_obj: JObject) -> i32 { pub fn get_sample_rate(env: &JNIEnv, audio_player_obj: JObject) -> i32 {
let audio_player_klass = env.get_object_class(audio_player_obj).unwrap(); let audio_player_klass = env.get_object_class(audio_player_obj).unwrap();
let mid_get_sample_rate = env let mid_get_sample_rate = env
.get_method_id(audio_player_klass, "getSampleRate", "()I") .get_method_id(audio_player_klass, "getSampleRate", "()I")
.expect("failed to get methodID for getSampleRate"); .expect("failed to get methodID for getSampleRate");
let result = env let result = env
.call_method_unchecked( .call_method_unchecked(
audio_player_obj, audio_player_obj,
mid_get_sample_rate, mid_get_sample_rate,
JavaType::Primitive(Primitive::Int), JavaType::Primitive(Primitive::Int),
&[], &[],
) )
.unwrap(); .unwrap();
let sample_rate = match result { let sample_rate = match result {
JValue::Int(sample_rate) => sample_rate as i32, JValue::Int(sample_rate) => sample_rate as i32,
_ => panic!("bad return value"), _ => panic!("bad return value"),
}; };
return sample_rate; return sample_rate;
} }
} }

View file

@ -1,66 +1,66 @@
use super::connector::AudioJNIConnector; use super::connector::AudioJNIConnector;
use std::sync::mpsc::{channel, Sender}; use std::sync::mpsc::{channel, Sender};
use std::thread; use std::thread;
use std::thread::JoinHandle; use std::thread::JoinHandle;
use rustboyadvance_core::util::audio::Consumer; use rustboyadvance_core::util::audio::Consumer;
use jni::JavaVM; use jni::JavaVM;
#[derive(Debug)] #[derive(Debug)]
#[allow(dead_code)] #[allow(dead_code)]
pub enum AudioThreadCommand { pub enum AudioThreadCommand {
RenderAudio, RenderAudio,
Pause, Pause,
Play, Play,
Terminate, Terminate,
} }
pub(crate) fn spawn_audio_worker_thread( pub(crate) fn spawn_audio_worker_thread(
audio_connector: AudioJNIConnector, audio_connector: AudioJNIConnector,
jvm: JavaVM, jvm: JavaVM,
mut consumer: Consumer<i16>, mut consumer: Consumer<i16>,
) -> (JoinHandle<AudioJNIConnector>, Sender<AudioThreadCommand>) { ) -> (JoinHandle<AudioJNIConnector>, Sender<AudioThreadCommand>) {
let (tx, rx) = channel(); let (tx, rx) = channel();
let handle = thread::spawn(move || { let handle = thread::spawn(move || {
info!("[AudioWorker] spawned!"); info!("[AudioWorker] spawned!");
info!("[AudioWorker] Attaching JVM"); info!("[AudioWorker] Attaching JVM");
let env = jvm.attach_current_thread().unwrap(); let env = jvm.attach_current_thread().unwrap();
loop { loop {
let command = rx.recv().unwrap(); let command = rx.recv().unwrap();
match command { match command {
AudioThreadCommand::Pause => { AudioThreadCommand::Pause => {
info!("[AudioWorker] - got {:?} command", command); info!("[AudioWorker] - got {:?} command", command);
audio_connector.pause(&env); audio_connector.pause(&env);
} }
AudioThreadCommand::Play => { AudioThreadCommand::Play => {
info!("[AudioWorker] - got {:?} command", command); info!("[AudioWorker] - got {:?} command", command);
audio_connector.play(&env); audio_connector.play(&env);
} }
AudioThreadCommand::RenderAudio => { AudioThreadCommand::RenderAudio => {
let mut samples = [0; 4096 * 2]; // TODO is this memset expansive ? let mut samples = [0; 4096 * 2]; // TODO is this memset expansive ?
let count = consumer.pop_slice(&mut samples); let count = consumer.pop_slice(&mut samples);
audio_connector.write_audio_samples(&env, &samples[0..count]); audio_connector.write_audio_samples(&env, &samples[0..count]);
} }
AudioThreadCommand::Terminate => { AudioThreadCommand::Terminate => {
info!("[AudioWorker] - got terminate command!"); info!("[AudioWorker] - got terminate command!");
break; break;
} }
} }
} }
info!("[AudioWorker] terminating"); info!("[AudioWorker] terminating");
// return the audio connector back // return the audio connector back
audio_connector audio_connector
}); });
(handle, tx) (handle, tx)
} }

View file

@ -1,403 +1,403 @@
use rustboyadvance_core::prelude::*; use rustboyadvance_core::prelude::*;
use rustboyadvance_core::util::audio::{AudioRingBuffer, Producer}; use rustboyadvance_core::util::audio::{AudioRingBuffer, Producer};
// use rustboyadvance_core::util::FpsCounter; // use rustboyadvance_core::util::FpsCounter;
use std::cell::RefCell; use std::cell::RefCell;
use std::path::Path; use std::path::Path;
use std::rc::Rc; use std::rc::Rc;
use std::sync::{Mutex, MutexGuard}; use std::sync::{Mutex, MutexGuard};
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use jni::objects::{GlobalRef, JMethodID, JObject, JString, JValue}; use jni::objects::{GlobalRef, JMethodID, JObject, JString, JValue};
use jni::signature; use jni::signature;
use jni::sys::{jboolean, jbyteArray, jintArray, jmethodID}; use jni::sys::{jboolean, jbyteArray, jintArray, jmethodID};
use jni::JNIEnv; use jni::JNIEnv;
use crate::audio::{self, connector::AudioJNIConnector, thread::AudioThreadCommand}; use crate::audio::{self, connector::AudioJNIConnector, thread::AudioThreadCommand};
struct Hardware { struct Hardware {
sample_rate: i32, sample_rate: i32,
audio_producer: Option<Producer<i16>>, audio_producer: Option<Producer<i16>>,
key_state: u16, key_state: u16,
} }
impl AudioInterface for Hardware { impl AudioInterface for Hardware {
fn push_sample(&mut self, samples: &[i16]) { fn push_sample(&mut self, samples: &[i16]) {
if let Some(prod) = &mut self.audio_producer { if let Some(prod) = &mut self.audio_producer {
for s in samples.iter() { for s in samples.iter() {
let _ = prod.push(*s); let _ = prod.push(*s);
} }
} else { } else {
// The gba is never ran before audio_producer is initialized // The gba is never ran before audio_producer is initialized
unreachable!() unreachable!()
} }
} }
fn get_sample_rate(&self) -> i32 { fn get_sample_rate(&self) -> i32 {
self.sample_rate self.sample_rate
} }
} }
impl InputInterface for Hardware { impl InputInterface for Hardware {
fn poll(&mut self) -> u16 { fn poll(&mut self) -> u16 {
self.key_state self.key_state
} }
} }
struct Renderer { struct Renderer {
renderer_ref: GlobalRef, renderer_ref: GlobalRef,
frame_buffer_ref: GlobalRef, frame_buffer_ref: GlobalRef,
mid_render_frame: jmethodID, mid_render_frame: jmethodID,
} }
impl Renderer { impl Renderer {
fn new(env: &JNIEnv, renderer_obj: JObject) -> Result<Renderer, String> { fn new(env: &JNIEnv, renderer_obj: JObject) -> Result<Renderer, String> {
let renderer_ref = env let renderer_ref = env
.new_global_ref(renderer_obj) .new_global_ref(renderer_obj)
.map_err(|e| format!("failed to add new global ref, error: {:?}", e))?; .map_err(|e| format!("failed to add new global ref, error: {:?}", e))?;
let frame_buffer = env let frame_buffer = env
.new_int_array(240 * 160) .new_int_array(240 * 160)
.map_err(|e| format!("failed to create framebuffer, error: {:?}", e))?; .map_err(|e| format!("failed to create framebuffer, error: {:?}", e))?;
let frame_buffer_ref = env let frame_buffer_ref = env
.new_global_ref(frame_buffer) .new_global_ref(frame_buffer)
.map_err(|e| format!("failed to add new global ref, error: {:?}", e))?; .map_err(|e| format!("failed to add new global ref, error: {:?}", e))?;
let renderer_klass = env let renderer_klass = env
.get_object_class(renderer_ref.as_obj()) .get_object_class(renderer_ref.as_obj())
.expect("failed to get renderer class"); .expect("failed to get renderer class");
let mid_render_frame = env let mid_render_frame = env
.get_method_id(renderer_klass, "renderFrame", "([I)V") .get_method_id(renderer_klass, "renderFrame", "([I)V")
.expect("failed to get methodID for renderFrame") .expect("failed to get methodID for renderFrame")
.into_inner(); .into_inner();
Ok(Renderer { Ok(Renderer {
renderer_ref, renderer_ref,
frame_buffer_ref, frame_buffer_ref,
mid_render_frame, mid_render_frame,
}) })
} }
#[inline] #[inline]
fn render_frame(&self, env: &JNIEnv, buffer: &[u32]) { fn render_frame(&self, env: &JNIEnv, buffer: &[u32]) {
unsafe { unsafe {
env.set_int_array_region( env.set_int_array_region(
self.frame_buffer_ref.as_obj().into_inner(), self.frame_buffer_ref.as_obj().into_inner(),
0, 0,
std::mem::transmute::<&[u32], &[i32]>(buffer), std::mem::transmute::<&[u32], &[i32]>(buffer),
) )
.unwrap(); .unwrap();
} }
env.call_method_unchecked( env.call_method_unchecked(
self.renderer_ref.as_obj(), self.renderer_ref.as_obj(),
JMethodID::from(self.mid_render_frame), JMethodID::from(self.mid_render_frame),
signature::JavaType::Primitive(signature::Primitive::Void), signature::JavaType::Primitive(signature::Primitive::Void),
&[JValue::from(self.frame_buffer_ref.as_obj())], &[JValue::from(self.frame_buffer_ref.as_obj())],
) )
.expect("failed to call renderFrame"); .expect("failed to call renderFrame");
} }
} }
struct Keypad { struct Keypad {
keypad_ref: GlobalRef, keypad_ref: GlobalRef,
mid_get_key_state: jmethodID, mid_get_key_state: jmethodID,
} }
impl Keypad { impl Keypad {
fn new(env: &JNIEnv, keypad_obj: JObject) -> Keypad { fn new(env: &JNIEnv, keypad_obj: JObject) -> Keypad {
let keypad_ref = env let keypad_ref = env
.new_global_ref(keypad_obj) .new_global_ref(keypad_obj)
.expect("failed to create keypad_ref"); .expect("failed to create keypad_ref");
let keypad_klass = env let keypad_klass = env
.get_object_class(keypad_ref.as_obj()) .get_object_class(keypad_ref.as_obj())
.expect("failed to create keypad class"); .expect("failed to create keypad class");
let mid_get_key_state = env let mid_get_key_state = env
.get_method_id(keypad_klass, "getKeyState", "()I") .get_method_id(keypad_klass, "getKeyState", "()I")
.expect("failed to get methodID for getKeyState") .expect("failed to get methodID for getKeyState")
.into_inner(); .into_inner();
Keypad { Keypad {
keypad_ref, keypad_ref,
mid_get_key_state, mid_get_key_state,
} }
} }
#[inline] #[inline]
fn get_key_state(&self, env: &JNIEnv) -> u16 { fn get_key_state(&self, env: &JNIEnv) -> u16 {
match env.call_method_unchecked( match env.call_method_unchecked(
self.keypad_ref.as_obj(), self.keypad_ref.as_obj(),
JMethodID::from(self.mid_get_key_state), JMethodID::from(self.mid_get_key_state),
signature::JavaType::Primitive(signature::Primitive::Int), signature::JavaType::Primitive(signature::Primitive::Int),
&[], &[],
) { ) {
Ok(JValue::Int(key_state)) => key_state as u16, Ok(JValue::Int(key_state)) => key_state as u16,
_ => panic!("failed to call getKeyState"), _ => panic!("failed to call getKeyState"),
} }
} }
} }
#[derive(Debug, PartialEq, Copy, Clone)] #[derive(Debug, PartialEq, Copy, Clone)]
pub enum EmulationState { pub enum EmulationState {
Initial, Initial,
Pausing, Pausing,
Paused, Paused,
Running(bool), Running(bool),
Stopping, Stopping,
Stopped, Stopped,
} }
impl Default for EmulationState { impl Default for EmulationState {
fn default() -> EmulationState { fn default() -> EmulationState {
EmulationState::Initial EmulationState::Initial
} }
} }
pub struct EmulatorContext { pub struct EmulatorContext {
hwif: Rc<RefCell<Hardware>>, hwif: Rc<RefCell<Hardware>>,
renderer: Renderer, renderer: Renderer,
audio_player_ref: GlobalRef, audio_player_ref: GlobalRef,
keypad: Keypad, keypad: Keypad,
pub emustate: Mutex<EmulationState>, pub emustate: Mutex<EmulationState>,
pub gba: GameBoyAdvance, pub gba: GameBoyAdvance,
} }
impl EmulatorContext { impl EmulatorContext {
pub fn native_open_context( pub fn native_open_context(
env: &JNIEnv, env: &JNIEnv,
bios: jbyteArray, bios: jbyteArray,
rom: jbyteArray, rom: jbyteArray,
renderer_obj: JObject, renderer_obj: JObject,
audio_player: JObject, audio_player: JObject,
keypad_obj: JObject, keypad_obj: JObject,
save_file: JString, save_file: JString,
skip_bios: jboolean, skip_bios: jboolean,
) -> Result<EmulatorContext, String> { ) -> Result<EmulatorContext, String> {
let bios = env let bios = env
.convert_byte_array(bios) .convert_byte_array(bios)
.map_err(|e| format!("could not get bios buffer, error {}", e))? .map_err(|e| format!("could not get bios buffer, error {}", e))?
.into_boxed_slice(); .into_boxed_slice();
let rom = env let rom = env
.convert_byte_array(rom) .convert_byte_array(rom)
.map_err(|e| format!("could not get rom buffer, error {}", e))? .map_err(|e| format!("could not get rom buffer, error {}", e))?
.into_boxed_slice(); .into_boxed_slice();
let save_file: String = env let save_file: String = env
.get_string(save_file) .get_string(save_file)
.map_err(|_| String::from("could not get save path"))? .map_err(|_| String::from("could not get save path"))?
.into(); .into();
let gamepak = GamepakBuilder::new() let gamepak = GamepakBuilder::new()
.take_buffer(rom) .take_buffer(rom)
.save_path(&Path::new(&save_file)) .save_path(&Path::new(&save_file))
.build() .build()
.map_err(|e| format!("failed to load rom, gba result: {:?}", e))?; .map_err(|e| format!("failed to load rom, gba result: {:?}", e))?;
info!("Loaded ROM file {:?}", gamepak.header); info!("Loaded ROM file {:?}", gamepak.header);
info!("Creating renderer"); info!("Creating renderer");
let renderer = Renderer::new(env, renderer_obj)?; let renderer = Renderer::new(env, renderer_obj)?;
info!("Creating GBA Instance"); info!("Creating GBA Instance");
let hw = Rc::new(RefCell::new(Hardware { let hw = Rc::new(RefCell::new(Hardware {
sample_rate: audio::util::get_sample_rate(env, audio_player), sample_rate: audio::util::get_sample_rate(env, audio_player),
audio_producer: None, audio_producer: None,
key_state: 0xffff, key_state: 0xffff,
})); }));
let mut gba = GameBoyAdvance::new(bios, gamepak, hw.clone(), hw.clone()); let mut gba = GameBoyAdvance::new(bios, gamepak, hw.clone(), hw.clone());
if skip_bios != 0 { if skip_bios != 0 {
info!("skipping bios"); info!("skipping bios");
gba.skip_bios(); gba.skip_bios();
} }
info!("creating keypad"); info!("creating keypad");
let keypad = Keypad::new(env, keypad_obj); let keypad = Keypad::new(env, keypad_obj);
info!("creating context"); info!("creating context");
let audio_player_ref = env.new_global_ref(audio_player).unwrap(); let audio_player_ref = env.new_global_ref(audio_player).unwrap();
let context = EmulatorContext { let context = EmulatorContext {
gba, gba,
keypad, keypad,
renderer, renderer,
audio_player_ref, audio_player_ref,
emustate: Mutex::new(EmulationState::default()), emustate: Mutex::new(EmulationState::default()),
hwif: hw.clone(), hwif: hw.clone(),
}; };
Ok(context) Ok(context)
} }
pub fn native_open_saved_state( pub fn native_open_saved_state(
env: &JNIEnv, env: &JNIEnv,
state: jbyteArray, state: jbyteArray,
renderer_obj: JObject, renderer_obj: JObject,
audio_player: JObject, audio_player: JObject,
keypad_obj: JObject, keypad_obj: JObject,
) -> Result<EmulatorContext, String> { ) -> Result<EmulatorContext, String> {
let state = env let state = env
.convert_byte_array(state) .convert_byte_array(state)
.map_err(|e| format!("could not get state buffer, error {}", e))?; .map_err(|e| format!("could not get state buffer, error {}", e))?;
let renderer = Renderer::new(env, renderer_obj)?; let renderer = Renderer::new(env, renderer_obj)?;
let hw = Rc::new(RefCell::new(Hardware { let hw = Rc::new(RefCell::new(Hardware {
sample_rate: audio::util::get_sample_rate(env, audio_player), sample_rate: audio::util::get_sample_rate(env, audio_player),
audio_producer: None, audio_producer: None,
key_state: 0xffff, key_state: 0xffff,
})); }));
let gba = let gba =
GameBoyAdvance::from_saved_state(&state, hw.clone(), hw.clone()).map_err(|e| { GameBoyAdvance::from_saved_state(&state, hw.clone(), hw.clone()).map_err(|e| {
format!( format!(
"failed to create GameBoyAdvance from saved state, error {:?}", "failed to create GameBoyAdvance from saved state, error {:?}",
e e
) )
})?; })?;
let keypad = Keypad::new(env, keypad_obj); let keypad = Keypad::new(env, keypad_obj);
let audio_player_ref = env.new_global_ref(audio_player).unwrap(); let audio_player_ref = env.new_global_ref(audio_player).unwrap();
Ok(EmulatorContext { Ok(EmulatorContext {
gba, gba,
keypad, keypad,
renderer, renderer,
audio_player_ref, audio_player_ref,
emustate: Mutex::new(EmulationState::default()), emustate: Mutex::new(EmulationState::default()),
hwif: hw.clone(), hwif: hw.clone(),
}) })
} }
fn render_video(&mut self, env: &JNIEnv) { fn render_video(&mut self, env: &JNIEnv) {
self.renderer.render_frame(env, self.gba.get_frame_buffer()); self.renderer.render_frame(env, self.gba.get_frame_buffer());
} }
/// Lock the emulation loop in order to perform updates to the struct /// Lock the emulation loop in order to perform updates to the struct
pub fn lock_and_get_gba(&mut self) -> (MutexGuard<EmulationState>, &mut GameBoyAdvance) { pub fn lock_and_get_gba(&mut self) -> (MutexGuard<EmulationState>, &mut GameBoyAdvance) {
(self.emustate.lock().unwrap(), &mut self.gba) (self.emustate.lock().unwrap(), &mut self.gba)
} }
/// Run the emulation main loop /// Run the emulation main loop
pub fn native_run(&mut self, env: &JNIEnv) -> Result<(), jni::errors::Error> { pub fn native_run(&mut self, env: &JNIEnv) -> Result<(), jni::errors::Error> {
const FRAME_TIME: Duration = Duration::from_nanos(1_000_000_000u64 / 60); const FRAME_TIME: Duration = Duration::from_nanos(1_000_000_000u64 / 60);
// Set the state to running // Set the state to running
*self.emustate.lock().unwrap() = EmulationState::Running(false); *self.emustate.lock().unwrap() = EmulationState::Running(false);
// Extract current JVM // Extract current JVM
let jvm = env.get_java_vm().unwrap(); let jvm = env.get_java_vm().unwrap();
// Instanciate an audio player connector // Instanciate an audio player connector
let audio_connector = AudioJNIConnector::new(env, self.audio_player_ref.as_obj()); let audio_connector = AudioJNIConnector::new(env, self.audio_player_ref.as_obj());
// Create a ringbuffer between the emulator and the audio thread // Create a ringbuffer between the emulator and the audio thread
let (prod, cons) = AudioRingBuffer::new_with_capacity(audio_connector.sample_count).split(); let (prod, cons) = AudioRingBuffer::new_with_capacity(audio_connector.sample_count).split();
// Store the ringbuffer producer in the emulator // Store the ringbuffer producer in the emulator
self.hwif.borrow_mut().audio_producer = Some(prod); self.hwif.borrow_mut().audio_producer = Some(prod);
// Spawn the audio worker thread, give it the audio connector, jvm and ringbuffer consumer // Spawn the audio worker thread, give it the audio connector, jvm and ringbuffer consumer
let (audio_thread_handle, audio_thread_tx) = let (audio_thread_handle, audio_thread_tx) =
audio::thread::spawn_audio_worker_thread(audio_connector, jvm, cons); audio::thread::spawn_audio_worker_thread(audio_connector, jvm, cons);
info!("starting main emulation loop"); info!("starting main emulation loop");
// let mut fps_counter = FpsCounter::default(); // let mut fps_counter = FpsCounter::default();
'running: loop { 'running: loop {
let emustate = *self.emustate.lock().unwrap(); let emustate = *self.emustate.lock().unwrap();
let limiter = match emustate { let limiter = match emustate {
EmulationState::Initial => unsafe { std::hint::unreachable_unchecked() }, EmulationState::Initial => unsafe { std::hint::unreachable_unchecked() },
EmulationState::Stopped => unsafe { std::hint::unreachable_unchecked() }, EmulationState::Stopped => unsafe { std::hint::unreachable_unchecked() },
EmulationState::Pausing => { EmulationState::Pausing => {
info!("emulation pause requested"); info!("emulation pause requested");
*self.emustate.lock().unwrap() = EmulationState::Paused; *self.emustate.lock().unwrap() = EmulationState::Paused;
continue; continue;
} }
EmulationState::Paused => continue, EmulationState::Paused => continue,
EmulationState::Stopping => break 'running, EmulationState::Stopping => break 'running,
EmulationState::Running(turbo) => !turbo, EmulationState::Running(turbo) => !turbo,
}; };
let start_time = Instant::now(); let start_time = Instant::now();
// check key state // check key state
self.hwif.borrow_mut().key_state = self.keypad.get_key_state(env); self.hwif.borrow_mut().key_state = self.keypad.get_key_state(env);
// run frame // run frame
self.gba.frame(); self.gba.frame();
// render video // render video
self.render_video(env); self.render_video(env);
// request audio worker to render the audio now // request audio worker to render the audio now
audio_thread_tx audio_thread_tx
.send(AudioThreadCommand::RenderAudio) .send(AudioThreadCommand::RenderAudio)
.unwrap(); .unwrap();
// if let Some(fps) = fps_counter.tick() { // if let Some(fps) = fps_counter.tick() {
// info!("FPS {}", fps); // info!("FPS {}", fps);
// } // }
if limiter { if limiter {
let time_passed = start_time.elapsed(); let time_passed = start_time.elapsed();
let delay = FRAME_TIME.checked_sub(time_passed); let delay = FRAME_TIME.checked_sub(time_passed);
match delay { match delay {
None => {} None => {}
Some(delay) => { Some(delay) => {
std::thread::sleep(delay); std::thread::sleep(delay);
} }
} }
} }
} }
info!("stopping, terminating audio worker"); info!("stopping, terminating audio worker");
audio_thread_tx.send(AudioThreadCommand::Terminate).unwrap(); // we surely have an endpoint, so it will work audio_thread_tx.send(AudioThreadCommand::Terminate).unwrap(); // we surely have an endpoint, so it will work
info!("waiting for audio worker to complete"); info!("waiting for audio worker to complete");
let audio_connector = audio_thread_handle.join().unwrap(); let audio_connector = audio_thread_handle.join().unwrap();
info!("audio worker terminated"); info!("audio worker terminated");
audio_connector.pause(env); audio_connector.pause(env);
self.hwif.borrow_mut().audio_producer = None; self.hwif.borrow_mut().audio_producer = None;
*self.emustate.lock().unwrap() = EmulationState::Stopped; *self.emustate.lock().unwrap() = EmulationState::Stopped;
Ok(()) Ok(())
} }
pub fn native_get_framebuffer(&mut self, env: &JNIEnv) -> jintArray { pub fn native_get_framebuffer(&mut self, env: &JNIEnv) -> jintArray {
let fb = env.new_int_array(240 * 160).unwrap(); let fb = env.new_int_array(240 * 160).unwrap();
self.pause(); self.pause();
unsafe { unsafe {
env.set_int_array_region( env.set_int_array_region(
fb, fb,
0, 0,
std::mem::transmute::<&[u32], &[i32]>(self.gba.get_frame_buffer()), std::mem::transmute::<&[u32], &[i32]>(self.gba.get_frame_buffer()),
) )
.unwrap(); .unwrap();
} }
self.resume(); self.resume();
fb fb
} }
pub fn pause(&mut self) { pub fn pause(&mut self) {
*self.emustate.lock().unwrap() = EmulationState::Pausing; *self.emustate.lock().unwrap() = EmulationState::Pausing;
while *self.emustate.lock().unwrap() != EmulationState::Paused { while *self.emustate.lock().unwrap() != EmulationState::Paused {
info!("awaiting pause...") info!("awaiting pause...")
} }
} }
pub fn resume(&mut self) { pub fn resume(&mut self) {
*self.emustate.lock().unwrap() = EmulationState::Running(false); *self.emustate.lock().unwrap() = EmulationState::Running(false);
} }
pub fn set_turbo(&mut self, turbo: bool) { pub fn set_turbo(&mut self, turbo: bool) {
*self.emustate.lock().unwrap() = EmulationState::Running(turbo); *self.emustate.lock().unwrap() = EmulationState::Running(turbo);
} }
pub fn request_stop(&mut self) { pub fn request_stop(&mut self) {
if EmulationState::Stopped != *self.emustate.lock().unwrap() { if EmulationState::Stopped != *self.emustate.lock().unwrap() {
*self.emustate.lock().unwrap() = EmulationState::Stopping; *self.emustate.lock().unwrap() = EmulationState::Stopping;
} }
} }
pub fn is_stopped(&self) -> bool { pub fn is_stopped(&self) -> bool {
*self.emustate.lock().unwrap() == EmulationState::Stopped *self.emustate.lock().unwrap() == EmulationState::Stopped
} }
} }

View file

@ -11,13 +11,14 @@ use super::dma::DmaController;
use super::gpu::*; use super::gpu::*;
use super::interrupt::*; use super::interrupt::*;
use super::iodev::*; use super::iodev::*;
use super::sched::{EventHandler, EventType, Scheduler, SharedScheduler};
use super::sound::SoundController; use super::sound::SoundController;
use super::sysbus::SysBus; use super::sysbus::SysBus;
use super::timer::Timers; use super::timer::Timers;
use super::{AudioInterface, InputInterface};
#[cfg(not(feature = "no_video_interface"))] #[cfg(not(feature = "no_video_interface"))]
use super::VideoInterface; use super::VideoInterface;
use super::{AudioInterface, InputInterface};
pub struct GameBoyAdvance { pub struct GameBoyAdvance {
pub sysbus: Box<SysBus>, pub sysbus: Box<SysBus>,
@ -30,6 +31,8 @@ pub struct GameBoyAdvance {
pub cycles_to_next_event: usize, pub cycles_to_next_event: usize,
scheduler: SharedScheduler,
overshoot_cycles: usize, overshoot_cycles: usize,
interrupt_flags: SharedInterruptFlags, interrupt_flags: SharedInterruptFlags,
} }
@ -37,6 +40,7 @@ pub struct GameBoyAdvance {
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
struct SaveState { struct SaveState {
sysbus: Box<SysBus>, sysbus: Box<SysBus>,
scheduler: Scheduler,
interrupt_flags: u16, interrupt_flags: u16,
cpu: arm7tdmi::Core, cpu: arm7tdmi::Core,
} }
@ -58,8 +62,7 @@ impl GameBoyAdvance {
pub fn new( pub fn new(
bios_rom: Box<[u8]>, bios_rom: Box<[u8]>,
gamepak: Cartridge, gamepak: Cartridge,
#[cfg(not(feature = "no_video_interface"))] #[cfg(not(feature = "no_video_interface"))] video_device: Rc<RefCell<dyn VideoInterface>>,
video_device: Rc<RefCell<dyn VideoInterface>>,
audio_device: Rc<RefCell<dyn AudioInterface>>, audio_device: Rc<RefCell<dyn AudioInterface>>,
input_device: Rc<RefCell<dyn InputInterface>>, input_device: Rc<RefCell<dyn InputInterface>>,
) -> GameBoyAdvance { ) -> GameBoyAdvance {
@ -70,12 +73,14 @@ impl GameBoyAdvance {
}; };
let interrupt_flags = Rc::new(Cell::new(IrqBitmask(0))); let interrupt_flags = Rc::new(Cell::new(IrqBitmask(0)));
let scheduler = Scheduler::new_shared();
let intc = InterruptController::new(interrupt_flags.clone()); let intc = InterruptController::new(interrupt_flags.clone());
let gpu = Box::new(Gpu::new(interrupt_flags.clone())); let gpu = Box::new(Gpu::new(scheduler.clone(), interrupt_flags.clone()));
let dmac = DmaController::new(interrupt_flags.clone()); let dmac = DmaController::new(interrupt_flags.clone());
let timers = Timers::new(interrupt_flags.clone()); let timers = Timers::new(interrupt_flags.clone());
let sound_controller = Box::new(SoundController::new( let sound_controller = Box::new(SoundController::new(
scheduler.clone(),
audio_device.borrow().get_sample_rate() as f32, audio_device.borrow().get_sample_rate() as f32,
)); ));
let io = IoDevices::new(intc, gpu, dmac, timers, sound_controller); let io = IoDevices::new(intc, gpu, dmac, timers, sound_controller);
@ -92,6 +97,8 @@ impl GameBoyAdvance {
audio_device: audio_device, audio_device: audio_device,
input_device: input_device, input_device: input_device,
scheduler: scheduler,
cycles_to_next_event: 1, cycles_to_next_event: 1,
overshoot_cycles: 0, overshoot_cycles: 0,
interrupt_flags: interrupt_flags, interrupt_flags: interrupt_flags,
@ -104,8 +111,7 @@ impl GameBoyAdvance {
pub fn from_saved_state( pub fn from_saved_state(
savestate: &[u8], savestate: &[u8],
#[cfg(not(feature = "no_video_interface"))] #[cfg(not(feature = "no_video_interface"))] video_device: Rc<RefCell<dyn VideoInterface>>,
video_device: Rc<RefCell<dyn VideoInterface>>,
audio_device: Rc<RefCell<dyn AudioInterface>>, audio_device: Rc<RefCell<dyn AudioInterface>>,
input_device: Rc<RefCell<dyn InputInterface>>, input_device: Rc<RefCell<dyn InputInterface>>,
) -> bincode::Result<GameBoyAdvance> { ) -> bincode::Result<GameBoyAdvance> {
@ -114,7 +120,10 @@ impl GameBoyAdvance {
let arm7tdmi = decoded.cpu; let arm7tdmi = decoded.cpu;
let mut sysbus = decoded.sysbus; let mut sysbus = decoded.sysbus;
let interrupts = Rc::new(Cell::new(IrqBitmask(decoded.interrupt_flags))); let interrupts = Rc::new(Cell::new(IrqBitmask(decoded.interrupt_flags)));
let scheduler = decoded.scheduler.make_shared();
sysbus.io.gpu.set_scheduler(scheduler.clone());
sysbus.io.sound.set_scheduler(scheduler.clone());
sysbus.io.connect_irq(interrupts.clone()); sysbus.io.connect_irq(interrupts.clone());
Ok(GameBoyAdvance { Ok(GameBoyAdvance {
@ -131,6 +140,8 @@ impl GameBoyAdvance {
cycles_to_next_event: 1, cycles_to_next_event: 1,
overshoot_cycles: 0, overshoot_cycles: 0,
scheduler: scheduler,
}) })
} }
@ -139,6 +150,7 @@ impl GameBoyAdvance {
cpu: self.cpu.clone(), cpu: self.cpu.clone(),
sysbus: self.sysbus.clone(), sysbus: self.sysbus.clone(),
interrupt_flags: self.interrupt_flags.get().value(), interrupt_flags: self.interrupt_flags.get().value(),
scheduler: (*self.scheduler).clone(),
}; };
bincode::serialize(&s) bincode::serialize(&s)
@ -149,10 +161,13 @@ impl GameBoyAdvance {
self.cpu = decoded.cpu; self.cpu = decoded.cpu;
self.sysbus = decoded.sysbus; self.sysbus = decoded.sysbus;
self.scheduler = Scheduler::make_shared(decoded.scheduler);
self.interrupt_flags = Rc::new(Cell::new(IrqBitmask(decoded.interrupt_flags))); self.interrupt_flags = Rc::new(Cell::new(IrqBitmask(decoded.interrupt_flags)));
// Redistribute shared pointer for interrupts // Redistribute shared pointers
self.sysbus.io.connect_irq(self.interrupt_flags.clone()); self.sysbus.io.connect_irq(self.interrupt_flags.clone());
self.sysbus.io.gpu.set_scheduler(self.scheduler.clone());
self.sysbus.io.sound.set_scheduler(self.scheduler.clone());
self.cycles_to_next_event = 1; self.cycles_to_next_event = 1;
@ -177,10 +192,12 @@ impl GameBoyAdvance {
pub fn frame(&mut self) { pub fn frame(&mut self) {
self.key_poll(); self.key_poll();
let mut scheduler = self.scheduler.clone();
let mut remaining_cycles = CYCLES_FULL_REFRESH - self.overshoot_cycles; let mut remaining_cycles = CYCLES_FULL_REFRESH - self.overshoot_cycles;
while remaining_cycles > 0 { while remaining_cycles > 0 {
let cycles = self.step(); let cycles = self.step(&mut scheduler);
if remaining_cycles >= cycles { if remaining_cycles >= cycles {
remaining_cycles -= cycles; remaining_cycles -= cycles;
} else { } else {
@ -228,15 +245,15 @@ impl GameBoyAdvance {
self.cpu.cycles - previous_cycles self.cpu.cycles - previous_cycles
} }
pub fn step(&mut self) -> usize { pub fn step(&mut self, scheduler: &mut Scheduler) -> usize {
// I hate myself for doing this, but rust left me no choice. // I hate myself for doing this, but rust left me no choice.
let io = unsafe { let io = unsafe {
let ptr = &mut *self.sysbus as *mut SysBus; let ptr = &mut *self.sysbus as *mut SysBus;
&mut (*ptr).io as &mut IoDevices &mut (*ptr).io as &mut IoDevices
}; };
let mut cycles_left = self.cycles_to_next_event; let available_cycles = self.scheduler.get_cycles_to_next_event();
let mut cycles_to_next_event = std::usize::MAX; let mut cycles_left = available_cycles;
let mut cycles = 0; let mut cycles = 0;
while cycles_left > 0 { while cycles_left > 0 {
@ -259,18 +276,9 @@ impl GameBoyAdvance {
cycles_left -= _cycles; cycles_left -= _cycles;
} }
// update gpu & sound
io.timers.update(cycles, &mut self.sysbus); io.timers.update(cycles, &mut self.sysbus);
io.gpu.update(
cycles, scheduler.run(cycles, self);
&mut cycles_to_next_event,
self.sysbus.as_mut(),
#[cfg(not(feature = "no_video_interface"))]
&self.video_device,
);
io.sound
.update(cycles, &mut cycles_to_next_event, &self.audio_device);
self.cycles_to_next_event = cycles_to_next_event;
cycles cycles
} }
@ -293,17 +301,11 @@ impl GameBoyAdvance {
let cycles = self.step_cpu(io); let cycles = self.step_cpu(io);
let breakpoint = self.check_breakpoint(); let breakpoint = self.check_breakpoint();
let mut _ignored = 0;
// update gpu & sound
io.timers.update(cycles, &mut self.sysbus); io.timers.update(cycles, &mut self.sysbus);
io.gpu.update(
cycles, // update gpu & sound
&mut _ignored, let mut scheduler = self.scheduler.clone();
self.sysbus.as_mut(), scheduler.run(cycles, self);
#[cfg(not(feature = "no_video_interface"))]
&self.video_device,
);
io.sound.update(cycles, &mut _ignored, &self.audio_device);
breakpoint breakpoint
} }
@ -320,6 +322,25 @@ impl GameBoyAdvance {
} }
} }
impl EventHandler for GameBoyAdvance {
fn handle_event(&mut self, event: EventType, extra_cycles: usize) {
let io = unsafe {
let ptr = &mut *self.sysbus as *mut SysBus;
&mut (*ptr).io as &mut IoDevices
};
match event {
EventType::Gpu(event) => io.gpu.on_event(
event,
extra_cycles,
self.sysbus.as_mut(),
#[cfg(not(feature = "no_video_interface"))]
&self.video_device,
),
EventType::Apu(event) => io.sound.on_event(event, extra_cycles, &self.audio_device),
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -337,6 +358,7 @@ mod tests {
} }
} }
#[cfg(not(feature = "no_video_interface"))]
impl VideoInterface for DummyInterface {} impl VideoInterface for DummyInterface {}
impl AudioInterface for DummyInterface {} impl AudioInterface for DummyInterface {}
impl InputInterface for DummyInterface {} impl InputInterface for DummyInterface {}
@ -350,8 +372,14 @@ mod tests {
.build() .build()
.unwrap(); .unwrap();
let dummy = Rc::new(RefCell::new(DummyInterface::new())); let dummy = Rc::new(RefCell::new(DummyInterface::new()));
let mut gba = let mut gba = GameBoyAdvance::new(
GameBoyAdvance::new(bios, cartridge, dummy.clone(), dummy.clone(), dummy.clone()); bios,
cartridge,
#[cfg(not(feature = "no_video_interface"))]
dummy.clone(),
dummy.clone(),
dummy.clone(),
);
gba.skip_bios(); gba.skip_bios();
gba gba

View file

@ -9,6 +9,7 @@ use serde::{Deserialize, Serialize};
use super::bus::*; use super::bus::*;
use super::dma::{DmaNotifer, TIMING_HBLANK, TIMING_VBLANK}; use super::dma::{DmaNotifer, TIMING_HBLANK, TIMING_VBLANK};
use super::interrupt::{self, Interrupt, InterruptConnect, SharedInterruptFlags}; use super::interrupt::{self, Interrupt, InterruptConnect, SharedInterruptFlags};
use super::sched::*;
pub use super::sysbus::consts::*; pub use super::sysbus::consts::*;
use super::util::BoxedMemory; use super::util::BoxedMemory;
#[cfg(not(feature = "no_video_interface"))] #[cfg(not(feature = "no_video_interface"))]
@ -183,6 +184,11 @@ pub struct Gpu {
pub state: GpuState, pub state: GpuState,
interrupt_flags: SharedInterruptFlags, interrupt_flags: SharedInterruptFlags,
/// When deserializing this struct using serde, make sure to call Gpu:::set_scheduler
#[serde(skip)]
#[serde(default = "Scheduler::new_shared")]
scheduler: SharedScheduler,
/// how many cycles left until next gpu state ? /// how many cycles left until next gpu state ?
cycles_left_for_current_state: usize, cycles_left_for_current_state: usize,
@ -224,9 +230,11 @@ impl InterruptConnect for Gpu {
} }
impl Gpu { impl Gpu {
pub fn new(interrupt_flags: SharedInterruptFlags) -> Gpu { pub fn new(mut scheduler: SharedScheduler, interrupt_flags: SharedInterruptFlags) -> Gpu {
scheduler.add_gpu_event(GpuEvent::HDraw, CYCLES_HDRAW);
Gpu { Gpu {
interrupt_flags, interrupt_flags,
scheduler,
dispcnt: DisplayControl(0x80), dispcnt: DisplayControl(0x80),
dispstat: DisplayStatus(0), dispstat: DisplayStatus(0),
backgrounds: [ backgrounds: [
@ -261,6 +269,10 @@ impl Gpu {
} }
} }
pub fn set_scheduler(&mut self, scheduler: SharedScheduler) {
self.scheduler = scheduler;
}
pub fn write_dispcnt(&mut self, value: u16) { pub fn write_dispcnt(&mut self, value: u16) {
let new_dispcnt = DisplayControl(value); let new_dispcnt = DisplayControl(value);
let old_mode = self.dispcnt.mode(); let old_mode = self.dispcnt.mode();
@ -414,6 +426,93 @@ impl Gpu {
&self.frame_buffer &self.frame_buffer
} }
#[inline]
fn update_vcount(&mut self, value: usize) {
self.vcount = value;
let vcount_setting = self.dispstat.vcount_setting();
self.dispstat
.set_vcount_flag(vcount_setting == self.vcount as u16);
if self.dispstat.vcount_irq_enable() && self.dispstat.get_vcount_flag() {
interrupt::signal_irq(&self.interrupt_flags, Interrupt::LCD_VCounterMatch);
}
}
#[inline]
fn handle_hdraw<D: DmaNotifer>(&mut self, dma_notifier: &mut D) -> (GpuEvent, usize) {
self.dispstat.set_hblank_flag(true);
if self.dispstat.hblank_irq_enable() {
interrupt::signal_irq(&self.interrupt_flags, Interrupt::LCD_HBlank);
};
dma_notifier.notify(TIMING_HBLANK);
// Next event
(GpuEvent::HBlank, CYCLES_HBLANK)
}
fn handle_hblank<D: DmaNotifer>(
&mut self,
dma_notifier: &mut D,
#[cfg(not(feature = "no_video_interface"))] video_device: &VideoDeviceRcRefCell,
) -> (GpuEvent, usize) {
self.update_vcount(self.vcount + 1);
if self.vcount < DISPLAY_HEIGHT {
self.dispstat.set_hblank_flag(false);
self.render_scanline();
// update BG2/3 reference points on the end of a scanline
for i in 0..2 {
self.bg_aff[i].internal_x += self.bg_aff[i].pb as i16 as i32;
self.bg_aff[i].internal_y += self.bg_aff[i].pd as i16 as i32;
}
(GpuEvent::HDraw, CYCLES_HDRAW)
} else {
// latch BG2/3 reference points on vblank
for i in 0..2 {
self.bg_aff[i].internal_x = self.bg_aff[i].x;
self.bg_aff[i].internal_y = self.bg_aff[i].y;
}
self.dispstat.set_vblank_flag(true);
self.dispstat.set_hblank_flag(false);
if self.dispstat.vblank_irq_enable() {
interrupt::signal_irq(&self.interrupt_flags, Interrupt::LCD_VBlank);
};
dma_notifier.notify(TIMING_VBLANK);
#[cfg(not(feature = "no_video_interface"))]
video_device.borrow_mut().render(&self.frame_buffer);
self.obj_buffer_reset();
(GpuEvent::VBlankHDraw, CYCLES_HDRAW)
}
}
fn handle_vblank_hdraw(&mut self) -> (GpuEvent, usize) {
self.dispstat.set_hblank_flag(true);
if self.dispstat.hblank_irq_enable() {
interrupt::signal_irq(&self.interrupt_flags, Interrupt::LCD_HBlank);
};
(GpuEvent::VBlankHBlank, CYCLES_HBLANK)
}
fn handle_vblank_hblank(&mut self) -> (GpuEvent, usize) {
if self.vcount < DISPLAY_HEIGHT + VBLANK_LINES - 1 {
self.update_vcount(self.vcount + 1);
self.dispstat.set_hblank_flag(false);
(GpuEvent::VBlankHDraw, CYCLES_HDRAW)
} else {
self.update_vcount(0);
self.dispstat.set_vblank_flag(false);
self.dispstat.set_hblank_flag(false);
self.render_scanline();
(GpuEvent::HDraw, CYCLES_HDRAW)
}
}
pub fn on_state_completed<D>( pub fn on_state_completed<D>(
&mut self, &mut self,
completed: GpuState, completed: GpuState,
@ -440,12 +539,8 @@ impl Gpu {
// Transition to HBlank // Transition to HBlank
self.state = HBlank; self.state = HBlank;
self.cycles_left_for_current_state = CYCLES_HBLANK; self.cycles_left_for_current_state = CYCLES_HBLANK;
self.dispstat.set_hblank_flag(true);
if self.dispstat.hblank_irq_enable() { self.handle_hdraw(dma_notifier);
interrupt::signal_irq(&self.interrupt_flags, Interrupt::LCD_HBlank);
};
dma_notifier.notify(TIMING_HBLANK);
} }
HBlank => { HBlank => {
update_vcount!(self.vcount + 1); update_vcount!(self.vcount + 1);
@ -538,6 +633,29 @@ impl Gpu {
*cycles_to_next_event = self.cycles_left_for_current_state; *cycles_to_next_event = self.cycles_left_for_current_state;
} }
} }
pub fn on_event<D>(
&mut self,
event: GpuEvent,
extra_cycles: usize,
dma_notifier: &mut D,
#[cfg(not(feature = "no_video_interface"))] video_device: &VideoDeviceRcRefCell,
) where
D: DmaNotifer,
{
let (next_event, cycles) = match event {
GpuEvent::HDraw => self.handle_hdraw(dma_notifier),
GpuEvent::HBlank => self.handle_hblank(
dma_notifier,
#[cfg(not(feature = "no_video_interface"))]
video_device,
),
GpuEvent::VBlankHDraw => self.handle_vblank_hdraw(),
GpuEvent::VBlankHBlank => self.handle_vblank_hblank(),
};
self.scheduler
.schedule(EventType::Gpu(next_event), cycles - extra_cycles);
}
} }
impl Bus for Gpu { impl Bus for Gpu {
@ -625,6 +743,7 @@ mod tests {
frame_counter: usize, frame_counter: usize,
} }
#[cfg(not(feature = "no_video_interface"))]
impl VideoInterface for TestVideoInterface { impl VideoInterface for TestVideoInterface {
fn render(&mut self, _buffer: &[u32]) { fn render(&mut self, _buffer: &[u32]) {
self.frame_counter += 1; self.frame_counter += 1;
@ -633,8 +752,13 @@ mod tests {
#[test] #[test]
fn test_gpu_state_machine() { fn test_gpu_state_machine() {
let mut gpu = Gpu::new(Rc::new(Cell::new(Default::default()))); let mut gpu = Gpu::new(
Scheduler::new_shared(),
Rc::new(Cell::new(Default::default())),
);
#[cfg(not(feature = "no_video_interface"))]
let video = Rc::new(RefCell::new(TestVideoInterface::default())); let video = Rc::new(RefCell::new(TestVideoInterface::default()));
#[cfg(not(feature = "no_video_interface"))]
let video_clone: VideoDeviceRcRefCell = video.clone(); let video_clone: VideoDeviceRcRefCell = video.clone();
let mut dma_notifier = NopDmaNotifer; let mut dma_notifier = NopDmaNotifer;
let mut cycles_to_next_event = CYCLES_FULL_REFRESH; let mut cycles_to_next_event = CYCLES_FULL_REFRESH;
@ -659,6 +783,7 @@ mod tests {
for line in 0..160 { for line in 0..160 {
println!("line = {}", line); println!("line = {}", line);
#[cfg(not(feature = "no_video_interface"))]
assert_eq!(video.borrow().frame_counter, 0); assert_eq!(video.borrow().frame_counter, 0);
assert_eq!(gpu.vcount, line); assert_eq!(gpu.vcount, line);
assert_eq!(gpu.state, GpuState::HDraw); assert_eq!(gpu.state, GpuState::HDraw);
@ -676,6 +801,7 @@ mod tests {
assert_eq!(gpu.interrupt_flags.get().LCD_VCounterMatch(), false); assert_eq!(gpu.interrupt_flags.get().LCD_VCounterMatch(), false);
} }
#[cfg(not(feature = "no_video_interface"))]
assert_eq!(video.borrow().frame_counter, 1); assert_eq!(video.borrow().frame_counter, 1);
for line in 0..68 { for line in 0..68 {
@ -694,6 +820,7 @@ mod tests {
update!(CYCLES_HBLANK); update!(CYCLES_HBLANK);
} }
#[cfg(not(feature = "no_video_interface"))]
assert_eq!(video.borrow().frame_counter, 1); assert_eq!(video.borrow().frame_counter, 1);
assert_eq!(total_cycles, CYCLES_FULL_REFRESH); assert_eq!(total_cycles, CYCLES_FULL_REFRESH);

View file

@ -40,6 +40,7 @@ pub mod arm7tdmi;
pub mod cartridge; pub mod cartridge;
pub mod disass; pub mod disass;
pub mod gpu; pub mod gpu;
mod sched;
pub mod sound; pub mod sound;
pub mod sysbus; pub mod sysbus;
pub use sysbus::SysBus; pub use sysbus::SysBus;

View file

@ -1,58 +1,58 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::convert::TryFrom; use std::convert::TryFrom;
use yaml_rust::YamlLoader; use yaml_rust::YamlLoader;
use super::cartridge::BackupType; use super::cartridge::BackupType;
#[derive(Debug)] #[derive(Debug)]
pub struct GameOverride { pub struct GameOverride {
force_rtc: bool, force_rtc: bool,
save_type: Option<BackupType>, save_type: Option<BackupType>,
} }
impl GameOverride { impl GameOverride {
pub fn force_rtc(&self) -> bool { pub fn force_rtc(&self) -> bool {
self.force_rtc self.force_rtc
} }
pub fn save_type(&self) -> Option<BackupType> { pub fn save_type(&self) -> Option<BackupType> {
self.save_type self.save_type
} }
} }
lazy_static! { lazy_static! {
static ref GAME_OVERRIDES: HashMap<String, GameOverride> = { static ref GAME_OVERRIDES: HashMap<String, GameOverride> = {
let mut m = HashMap::new(); let mut m = HashMap::new();
let docs = YamlLoader::load_from_str(include_str!("../overrides.yaml")) let docs = YamlLoader::load_from_str(include_str!("../overrides.yaml"))
.expect("failed to load overrides file"); .expect("failed to load overrides file");
let doc = &docs[0]; let doc = &docs[0];
let games = doc.as_vec().unwrap(); let games = doc.as_vec().unwrap();
for game in games { for game in games {
let game_code = String::from(game["code"].as_str().unwrap()); let game_code = String::from(game["code"].as_str().unwrap());
let force_rtc = game["rtc"].as_bool().unwrap_or(false); let force_rtc = game["rtc"].as_bool().unwrap_or(false);
let save_type = if let Some(save_type) = game["save_type"].as_str() { let save_type = if let Some(save_type) = game["save_type"].as_str() {
match BackupType::try_from(save_type) { match BackupType::try_from(save_type) {
Ok(x) => Some(x), Ok(x) => Some(x),
_ => panic!("{}: invalid save type {:#}", game_code, save_type), _ => panic!("{}: invalid save type {:#}", game_code, save_type),
} }
} else { } else {
None None
}; };
let game_overrride = GameOverride { let game_overrride = GameOverride {
force_rtc, force_rtc,
save_type, save_type,
}; };
m.insert(game_code, game_overrride); m.insert(game_code, game_overrride);
} }
m m
}; };
} }
pub fn get_game_overrides(game_code: &str) -> Option<&GameOverride> { pub fn get_game_overrides(game_code: &str) -> Option<&GameOverride> {
GAME_OVERRIDES.get(game_code) GAME_OVERRIDES.get(game_code)
} }

305
core/src/sched.rs Normal file
View file

@ -0,0 +1,305 @@
use std::cell::UnsafeCell;
use std::rc::Rc;
use serde::{Deserialize, Serialize};
const NUM_EVENTS: usize = 32;
#[repr(u32)]
#[derive(Serialize, Deserialize, Debug, PartialEq, Copy, Clone)]
pub enum GpuEvent {
HDraw,
HBlank,
VBlankHDraw,
VBlankHBlank,
}
#[repr(u32)]
#[derive(Serialize, Deserialize, Debug, PartialEq, Copy, Clone)]
pub enum ApuEvent {
Psg1Generate,
Psg2Generate,
Psg3Generate,
Psg4Generate,
Sample,
}
#[repr(u32)]
#[derive(Serialize, Deserialize, Debug, PartialEq, Copy, Clone)]
pub enum EventType {
Gpu(GpuEvent),
Apu(ApuEvent),
}
#[derive(Serialize, Deserialize, Debug, Clone)]
struct Event {
typ: EventType,
/// Timestamp in cycles
time: usize,
}
impl Event {
fn new(typ: EventType, time: usize) -> Event {
Event { typ, time }
}
fn get_type(&self) -> EventType {
self.typ
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Scheduler {
timestamp: usize,
events: Vec<Event>,
}
// Opt-out of runtime borrow checking by using unsafe cell
// SAFETY: We need to make sure that the scheduler event queue is not modified while iterating it.
#[repr(transparent)]
#[derive(Debug)]
pub struct SharedScheduler(Rc<UnsafeCell<Scheduler>>);
impl std::ops::Deref for SharedScheduler {
type Target = Scheduler;
fn deref(&self) -> &Self::Target {
unsafe { &(*self.0.get()) }
}
}
impl std::ops::DerefMut for SharedScheduler {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { &mut (*self.0.get()) }
}
}
impl Clone for SharedScheduler {
fn clone(&self) -> SharedScheduler {
SharedScheduler(self.0.clone())
}
}
pub trait EventHandler {
/// Handle the scheduler event
fn handle_event(&mut self, e: EventType, extra_cycles: usize);
}
impl Scheduler {
pub fn new_shared() -> SharedScheduler {
let sched = Scheduler {
timestamp: 0,
events: Vec::with_capacity(NUM_EVENTS),
};
SharedScheduler(Rc::new(UnsafeCell::new(sched)))
}
pub fn make_shared(self) -> SharedScheduler {
SharedScheduler(Rc::new(UnsafeCell::new(self)))
}
pub fn schedule(&mut self, typ: EventType, cycles: usize) {
let event = Event::new(typ, self.timestamp + cycles);
let idx = self
.events
.binary_search_by(|e| e.time.cmp(&event.time))
.unwrap_or_else(|x| x);
self.events.insert(idx, event);
}
pub fn add_gpu_event(&mut self, e: GpuEvent, cycles: usize) {
self.schedule(EventType::Gpu(e), cycles);
}
pub fn add_apu_event(&mut self, e: ApuEvent, cycles: usize) {
self.schedule(EventType::Apu(e), cycles);
}
pub fn run<H: EventHandler>(&mut self, cycles: usize, handler: &mut H) {
let run_to = self.timestamp + cycles;
self.timestamp = run_to;
while self.events.len() > 0 {
if run_to >= self.events[0].time {
let event = self.events.remove(0);
handler.handle_event(event.get_type(), run_to - event.time);
} else {
return;
}
}
}
pub fn get_cycles_to_next_event(&self) -> usize {
assert_ne!(self.events.len(), 0);
self.events[0].time - self.timestamp
}
#[allow(unused)]
fn is_empty(&self) -> bool {
self.events.is_empty()
}
}
#[cfg(test)]
mod test {
use super::*;
/// Some usecase example where a struct holds the scheduler
struct Holder {
sched: SharedScheduler,
event_bitmask: u32,
}
const BIT_GPU_VBLANKHDRAW: u32 = 1 << 0;
const BIT_APU_PSG1GENERATE: u32 = 1 << 1;
const BIT_APU_PSG2GENERATE: u32 = 1 << 2;
const BIT_APU_PSG3GENERATE: u32 = 1 << 3;
const BIT_APU_PSG4GENERATE: u32 = 1 << 4;
const BIT_APU_SAMPLE: u32 = 1 << 5;
#[inline]
fn get_event_bit(e: EventType) -> u32 {
match e {
EventType::Gpu(GpuEvent::VBlankHDraw) => BIT_GPU_VBLANKHDRAW,
EventType::Apu(ApuEvent::Psg1Generate) => BIT_APU_PSG1GENERATE,
EventType::Apu(ApuEvent::Psg2Generate) => BIT_APU_PSG2GENERATE,
EventType::Apu(ApuEvent::Psg3Generate) => BIT_APU_PSG3GENERATE,
EventType::Apu(ApuEvent::Psg4Generate) => BIT_APU_PSG4GENERATE,
EventType::Apu(ApuEvent::Sample) => BIT_APU_SAMPLE,
_ => unimplemented!("unsupported event for this test"),
}
}
impl Holder {
fn new() -> Holder {
Holder {
sched: Scheduler::new_shared(),
event_bitmask: 0,
}
}
fn is_event_done(&self, e: EventType) -> bool {
(self.event_bitmask & get_event_bit(e)) != 0
}
}
impl EventHandler for Holder {
fn handle_event(&mut self, e: EventType, extra_cycles: usize) {
println!("[holder] got event {:?} extra_cycles {}", e, extra_cycles);
self.event_bitmask |= get_event_bit(e);
}
}
#[test]
fn test_scheduler() {
let mut holder = Holder::new();
// clone the sched so we get a reference that is not owned by the holder
// SAFETY: since the SharedScheduler is built upon an UnsafeCell instead of RefCell, we are sacrificing runtime safety checks for performance.
// It is safe since the events iteration allows the EventHandler to modify the queue.
let mut sched = holder.sched.clone();
holder
.sched
.schedule(EventType::Gpu(GpuEvent::VBlankHDraw), 240);
holder
.sched
.schedule(EventType::Apu(ApuEvent::Psg1Generate), 60);
holder.sched.schedule(EventType::Apu(ApuEvent::Sample), 512);
holder
.sched
.schedule(EventType::Apu(ApuEvent::Psg2Generate), 13);
holder
.sched
.schedule(EventType::Apu(ApuEvent::Psg4Generate), 72);
println!("all events");
for e in sched.events.iter() {
let typ = e.get_type();
println!("{:?}", typ);
}
macro_rules! run_for {
($cycles:expr) => {
println!("running the scheduler for {} cycles", $cycles);
sched.run($cycles, &mut holder);
if (!sched.is_empty()) {
println!(
"cycles for next event: {}",
sched.get_cycles_to_next_event()
);
}
};
}
run_for!(100);
assert_eq!(
holder.is_event_done(EventType::Apu(ApuEvent::Psg1Generate)),
true
);
assert_eq!(
holder.is_event_done(EventType::Apu(ApuEvent::Psg2Generate)),
true
);
assert_eq!(
holder.is_event_done(EventType::Apu(ApuEvent::Psg4Generate)),
true
);
assert_eq!(
holder.is_event_done(EventType::Apu(ApuEvent::Sample)),
false
);
assert_eq!(
holder.is_event_done(EventType::Gpu(GpuEvent::VBlankHDraw)),
false
);
run_for!(100);
assert_eq!(
holder.is_event_done(EventType::Gpu(GpuEvent::VBlankHDraw)),
false
);
assert_eq!(
holder.is_event_done(EventType::Apu(ApuEvent::Sample)),
false
);
run_for!(100);
assert_eq!(
holder.is_event_done(EventType::Gpu(GpuEvent::VBlankHDraw)),
true
);
assert_eq!(
holder.is_event_done(EventType::Apu(ApuEvent::Sample)),
false
);
run_for!(211);
assert_eq!(
holder.is_event_done(EventType::Apu(ApuEvent::Sample)),
false
);
run_for!(1);
assert_eq!(holder.is_event_done(EventType::Apu(ApuEvent::Sample)), true);
println!("all events (holder)");
for e in holder.sched.events.iter() {
let typ = e.get_type();
println!("{:?}", typ);
}
println!("all events (cloned again)");
let sched_cloned = holder.sched.clone();
for e in sched_cloned.events.iter() {
let typ = e.get_type();
println!("{:?}", typ);
}
}
}

View file

@ -6,6 +6,7 @@ use serde::{Deserialize, Serialize};
use super::dma::DmaController; use super::dma::DmaController;
use super::iodev::consts::*; use super::iodev::consts::*;
use super::sched::*;
use crate::{AudioInterface, StereoSample}; use crate::{AudioInterface, StereoSample};
@ -62,6 +63,10 @@ type AudioDeviceRcRefCell = Rc<RefCell<dyn AudioInterface>>;
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
pub struct SoundController { pub struct SoundController {
#[serde(skip)]
#[serde(default = "Scheduler::new_shared")]
scheduler: SharedScheduler,
cycles: usize, // cycles count when we last provided a new sample. cycles: usize, // cycles count when we last provided a new sample.
mse: bool, mse: bool,
@ -101,9 +106,13 @@ pub struct SoundController {
} }
impl SoundController { impl SoundController {
pub fn new(audio_device_sample_rate: f32) -> SoundController { pub fn new(mut scheduler: SharedScheduler, audio_device_sample_rate: f32) -> SoundController {
let resampler = CosineResampler::new(32768_f32, audio_device_sample_rate); let resampler = CosineResampler::new(32768_f32, audio_device_sample_rate);
let cycles_per_sample = 512;
scheduler.schedule(EventType::Apu(ApuEvent::Sample), cycles_per_sample);
SoundController { SoundController {
scheduler,
cycles_per_sample,
cycles: 0, cycles: 0,
mse: false, mse: false,
left_volume: 0, left_volume: 0,
@ -127,7 +136,6 @@ impl SoundController {
sqr1_cur_vol: 0, sqr1_cur_vol: 0,
sound_bias: 0x200, sound_bias: 0x200,
sample_rate: 32_768f32, sample_rate: 32_768f32,
cycles_per_sample: 512,
dma_sound: [Default::default(), Default::default()], dma_sound: [Default::default(), Default::default()],
resampler: resampler, resampler: resampler,
@ -135,6 +143,10 @@ impl SoundController {
} }
} }
pub fn set_scheduler(&mut self, scheduler: SharedScheduler) {
self.scheduler = scheduler;
}
pub fn handle_read(&self, io_addr: u32) -> u16 { pub fn handle_read(&self, io_addr: u32) -> u16 {
let value = match io_addr { let value = match io_addr {
REG_SOUNDCNT_X => cbit(7, self.mse), REG_SOUNDCNT_X => cbit(7, self.mse),
@ -277,6 +289,11 @@ impl SoundController {
self.resampler.in_freq = self.sample_rate; self.resampler.in_freq = self.sample_rate;
} }
self.cycles_per_sample = 512 >> resolution; self.cycles_per_sample = 512 >> resolution;
info!(
"bias - setting sample frequency to {}hz",
self.cycles_per_sample
);
// TODO this will not affect the currently scheduled sample event
} }
_ => { _ => {
@ -317,46 +334,47 @@ impl SoundController {
} }
} }
pub fn update( #[inline]
&mut self, fn on_sample(&mut self, extra_cycles: usize, audio_device: &AudioDeviceRcRefCell) {
cycles: usize, let mut sample = [0f32; 2];
cycles_to_next_event: &mut usize,
audio_device: &AudioDeviceRcRefCell,
) {
self.cycles += cycles;
while self.cycles >= self.cycles_per_sample {
self.cycles -= self.cycles_per_sample;
// time to push a new sample! for channel in 0..=1 {
let mut dma_sample = 0;
let mut sample = [0f32; 2]; for dma in &mut self.dma_sound {
if dma.is_stereo_channel_enabled(channel) {
for channel in 0..=1 { let value = dma.value as i16;
let mut dma_sample = 0; dma_sample += value * (2 << dma.volume_shift);
for dma in &mut self.dma_sound {
if dma.is_stereo_channel_enabled(channel) {
let value = dma.value as i16;
dma_sample += value * (2 << dma.volume_shift);
}
} }
apply_bias(&mut dma_sample, self.sound_bias.bit_range(0..10) as i16);
sample[channel] = dma_sample as i32 as f32;
} }
let stereo_sample = (sample[0], sample[1]); apply_bias(&mut dma_sample, self.sound_bias.bit_range(0..10) as i16);
self.resampler.feed(stereo_sample, &mut self.output_buffer); sample[channel] = dma_sample as i32 as f32;
let mut audio = audio_device.borrow_mut();
self.output_buffer.drain(..).for_each(|(left, right)| {
audio.push_sample(&[
(left.round() as i16) * (std::i16::MAX / 512),
(right.round() as i16) * (std::i16::MAX / 512),
]);
});
} }
if self.cycles_per_sample < *cycles_to_next_event {
*cycles_to_next_event = self.cycles_per_sample; let stereo_sample = (sample[0], sample[1]);
self.resampler.feed(stereo_sample, &mut self.output_buffer);
let mut audio = audio_device.borrow_mut();
self.output_buffer.drain(..).for_each(|(left, right)| {
audio.push_sample(&[
(left.round() as i16) * (std::i16::MAX / 512),
(right.round() as i16) * (std::i16::MAX / 512),
]);
});
self.scheduler
.add_apu_event(ApuEvent::Sample, self.cycles_per_sample - extra_cycles);
}
pub fn on_event(
&mut self,
event: ApuEvent,
extra_cycles: usize,
audio_device: &AudioDeviceRcRefCell,
) {
match event {
ApuEvent::Sample => self.on_sample(extra_cycles, audio_device),
_ => debug!("got {:?} event", event),
} }
} }
} }