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:
parent
b68c73819a
commit
0de8a60006
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
305
core/src/sched.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue