Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/mt - Improve performance with threads and other optimizations #62

Merged
merged 39 commits into from
Nov 4, 2021
Merged
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
6cd1225
The emulation code now runs in another thread
alloncm Sep 12, 2021
e2f05f1
Improved the parking and the sync
alloncm Sep 12, 2021
770dd9c
Trying to improve perf not tested yet
alloncm Sep 13, 2021
bb06e5b
Add bench framework
alloncm Sep 13, 2021
bafdfb8
Some cleaning and optimization
alloncm Sep 18, 2021
a549639
Optimize the APU timer
alloncm Sep 18, 2021
1d2f1f8
Move the sample type behind Custom type
alloncm Sep 18, 2021
9888f79
Merge branch 'feature/mt' of https://github.com/alloncm/MagenBoy into…
alloncm Sep 20, 2021
800755f
The gameboy can exit properly
alloncm Sep 20, 2021
de2c998
Merge remote-tracking branch 'origin/dev' into feature/mt
alloncm Sep 24, 2021
732518a
The now tests compiles after the merge
alloncm Sep 24, 2021
2c73bf7
Move from rtrb to crossbeam channel
alloncm Oct 8, 2021
60d5f51
Reduce the apu bench loop to a sane number
alloncm Oct 9, 2021
95a3aa0
Add sample type
alloncm Oct 9, 2021
9ea63ea
Move to i16 samples
alloncm Oct 15, 2021
6e22821
Merge remote-tracking branch 'origin/feature/mt' into feature/mt
alloncm Oct 15, 2021
e519d50
Fix compile error
alloncm Oct 16, 2021
6803579
Use the const default sample
alloncm Oct 16, 2021
fcd1eef
Normal the i16 samples too
alloncm Oct 16, 2021
a143d41
Lock the fps to the original with the audio device
alloncm Oct 16, 2021
74d3caa
Went for i16 as it grants a greater optimizations
alloncm Oct 16, 2021
e1f9dae
Optimize the sound terminal
alloncm Oct 16, 2021
0ad4337
Update the readme
alloncm Oct 22, 2021
e1d134a
Optimize the mpmc gfx device
alloncm Oct 22, 2021
33b13a3
Optimize the APU
alloncm Oct 22, 2021
d6e7350
Add frame cap + turbo mode
alloncm Oct 23, 2021
a235e96
Fix some gfx device typos
alloncm Oct 27, 2021
19969eb
In the middle of testing sdl2 pull api
alloncm Oct 27, 2021
dd31bf6
Still in the middle
alloncm Oct 29, 2021
c030275
Add push and pull audio options and add vsync
alloncm Oct 30, 2021
6a7799c
Update the readme with cli flags :)
alloncm Oct 30, 2021
c8e598a
Arrange the conditinal comp stuff
alloncm Oct 31, 2021
4e74636
FIx an outdated test
alloncm Nov 1, 2021
f1d2753
Cr fixes
alloncm Nov 4, 2021
7281d3d
Move the cvt struct to the sdl reasmpler
alloncm Nov 4, 2021
001ad18
Extract sdl device initizalizing to a util
alloncm Nov 4, 2021
985cd85
Replace the clone in the audio pull
alloncm Nov 4, 2021
521d1ea
I think it supposed to close it gracefuly
alloncm Nov 4, 2021
133caa1
Inline the apu timer
alloncm Nov 4, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
438 changes: 388 additions & 50 deletions Cargo.lock

Large diffs are not rendered by default.

15 changes: 13 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,25 @@ The main goal of this project is to be able to play Pokemon on my own emulator.

**More will be added if neccessary (and by neccessary I mean if games I want to play will require them)**

## How to use

```shell
magenboy [path_to_rom] [other_optional_flags]
```

### Optional flags
* `--log` - Print logs in debug mode to a file
* `--file-audio` - Saves the audio to a file
* `--no-vsync` - Disable vsync

## GameBoy

### Development Status

- CPU - Cycle accurate CPU
- PPU - Scan line accurate PPU
- PPU - Cycle accurate fifo PPU
- Timer - Mostly accurate timer
- APU - Mostly accurate APU
- APU - Cycle mostly accurate APU
- Tests
- [Blargg's cpu_instrs](https://github.com/retrio/gb-test-roms/tree/master/cpu_instrs) - :thumbsup:
- [dmg-acid2](https://github.com/mattcurrie/dmg-acid2) - :thumbsup:
Expand Down
13 changes: 11 additions & 2 deletions gb/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
[package]
name = "magenboy"
name = "gb"
version = "1.0.0"
authors = ["alloncm <alloncm@gmail.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[[bin]]
name = "magenboy"
path = "src/main.rs"

[dependencies]
lib_gb = {path = "../lib_gb/"}
log = "0.4"
fern = "0.6.0"
chrono = "0.4"
sdl2 = {version = "0.34", features = ["bundled","static-link"]}
wav = "0.6.0"
wav = "0.6.0"
alloncm marked this conversation as resolved.
Show resolved Hide resolved
crossbeam-channel = "0.5"

[features]
sdl-resample = []
push-audio = []
81 changes: 81 additions & 0 deletions gb/src/audio/audio_resampler.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
use lib_gb::apu::audio_device::{BUFFER_SIZE, DEFAULT_SAPMPLE, Sample, StereoSample};
alloncm marked this conversation as resolved.
Show resolved Hide resolved
use super::AudioResampler;

pub struct MagenAudioResampler{
to_skip:u32,
sampling_buffer:Vec<StereoSample>,
sampling_counter:u32,
reminder_steps:f32,
reminder_counter:f32,
alternate_to_skip:u32,
skip_to_use:u32,
}

impl MagenAudioResampler{
fn interpolate_sample(samples:&[StereoSample])->StereoSample{

alloncm marked this conversation as resolved.
Show resolved Hide resolved
let interpulated_left_sample = samples.iter().fold(DEFAULT_SAPMPLE, |acc, x| acc + x.left_sample) / samples.len() as Sample;
let interpulated_right_sample = samples.iter().fold(DEFAULT_SAPMPLE, |acc, x| acc + x.right_sample) / samples.len() as Sample;

return StereoSample{left_sample: interpulated_left_sample, right_sample: interpulated_right_sample};
}
}

impl AudioResampler for MagenAudioResampler{
fn new(original_frequency:u32, target_frequency:u32)->Self{
// Calling round in order to get the nearest integer and resample as precise as possible
let div = original_frequency as f32 / target_frequency as f32;

let lower_to_skip = div.floor() as u32;
let upper_to_skip = div.ceil() as u32;
let mut reminder = div.fract();
let (to_skip, alt_to_skip) = if reminder < 0.5{
(lower_to_skip, upper_to_skip)
}
else{
reminder = 1.0 - reminder;
(upper_to_skip, lower_to_skip)
};

if lower_to_skip == 0{
std::panic!("target freqency is too high: {}", target_frequency);
}

MagenAudioResampler{
to_skip:to_skip,
sampling_buffer:Vec::with_capacity(upper_to_skip as usize),
sampling_counter: 0,
reminder_steps:reminder,
reminder_counter:0.0,
alternate_to_skip: alt_to_skip,
skip_to_use:to_skip
}
}

fn resample(&mut self, buffer:&[StereoSample; BUFFER_SIZE])->Vec<StereoSample>{
let mut output = Vec::new();
for sample in buffer.into_iter(){
self.sampling_buffer.push(sample.clone());
self.sampling_counter += 1;

if self.sampling_counter == self.skip_to_use {
let interpolated_sample = Self::interpolate_sample(&self.sampling_buffer);
self.sampling_counter = 0;
self.sampling_buffer.clear();

output.push(interpolated_sample);
if self.reminder_counter >= 1.0{
self.skip_to_use = self.alternate_to_skip;
self.reminder_counter -= 1.0;
}
else{
self.skip_to_use = self.to_skip;
self.reminder_counter += self.reminder_steps;
}
}
}

return output;

alloncm marked this conversation as resolved.
Show resolved Hide resolved
}
}
56 changes: 56 additions & 0 deletions gb/src/audio/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
pub mod multi_device_audio;
pub mod wav_file_audio_device;

#[cfg(not(feature = "push-audio"))]
pub mod sdl_pull_audio_device;
#[cfg(feature = "push-audio")]
pub mod sdl_push_audio_device;

use std::ffi::CStr;
use lib_gb::apu::audio_device::{AudioDevice, BUFFER_SIZE, Sample, StereoSample};
use sdl2::{libc::c_char, sys::SDL_GetError};

#[cfg(feature = "sdl-resampler")]
alloncm marked this conversation as resolved.
Show resolved Hide resolved
pub type ChosenResampler = sdl_audio_resampler::SdlAudioResampler;
#[cfg(feature = "sdl-resampler")]
pub mod sdl_audio_resampler;
alloncm marked this conversation as resolved.
Show resolved Hide resolved

#[cfg(not(feature = "sdl-resampler"))]
pub mod audio_resampler;
#[cfg(not(feature = "sdl-resampler"))]
pub type ChosenResampler = audio_resampler::MagenAudioResampler;

fn get_sdl_error_message()->&'static str{
unsafe{
let error_message:*const c_char = SDL_GetError();

return CStr::from_ptr(error_message).to_str().unwrap();
}
}

pub trait AudioResampler{
fn new(original_frequency:u32, target_frequency:u32)->Self;
fn resample(&mut self, buffer:&[StereoSample; BUFFER_SIZE])->Vec<StereoSample>;
}

trait SdlAudioDevice<AR:AudioResampler> : AudioDevice{
alloncm marked this conversation as resolved.
Show resolved Hide resolved
const VOLUME:Sample = 10 as Sample;
alloncm marked this conversation as resolved.
Show resolved Hide resolved

fn push_buffer(&mut self, buffer:&[StereoSample; BUFFER_SIZE]){
let resample = self.get_resampler().resample(buffer);
for sample in resample{
let(buffer, index) = self.get_audio_buffer();
buffer[*index] = sample.left_sample * Self::VOLUME;
buffer[*index + 1] = sample.left_sample * Self::VOLUME;
*index += 2;
if *index == BUFFER_SIZE{
*index = 0;
self.full_buffer_callback().unwrap();
}
}
}

fn get_audio_buffer(&mut self)->(&mut [Sample;BUFFER_SIZE], &mut usize);
fn get_resampler(&mut self)->&mut AR;
fn full_buffer_callback(&self)->Result<(), String>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ impl MultiAudioDevice{
}

impl AudioDevice for MultiAudioDevice{
fn push_buffer(&mut self, buffer:&[Sample]) {
fn push_buffer(&mut self, buffer:&[StereoSample; BUFFER_SIZE]) {
for device in self.devices.iter_mut(){
device.push_buffer(buffer);
}
Expand Down
52 changes: 52 additions & 0 deletions gb/src/audio/sdl_audio_resampler.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use std::mem::MaybeUninit;
use lib_gb::apu::audio_device::{BUFFER_SIZE, StereoSample};
use sdl2::sys::*;
use super::AudioResampler;

pub struct SdlAudioResampler{
original_frequency:u32,
target_frequency:u32,
}

impl AudioResampler for SdlAudioResampler{
fn new(original_frequency:u32, target_frequency:u32)->Self{
Self{
original_frequency,
target_frequency,
}
}

fn resample(&mut self, buffer:&[StereoSample; BUFFER_SIZE])->Vec<StereoSample>{
unsafe{
let mut cvt = {
alloncm marked this conversation as resolved.
Show resolved Hide resolved
let mut cvt:MaybeUninit<SDL_AudioCVT> = MaybeUninit::uninit();
SDL_BuildAudioCVT(cvt.as_mut_ptr(), AUDIO_S16 as u16, 2, self.original_frequency as i32,
AUDIO_S16 as u16, 2, self.target_frequency as i32);
cvt.assume_init()
};

if cvt.needed != 1{
std::panic!("Cannot resample between freqs");
}

cvt.len = (BUFFER_SIZE * std::mem::size_of::<StereoSample>()) as i32;
let mut buf:Vec::<u8> = vec![0;(cvt.len * cvt.len_mult) as usize];

std::ptr::copy_nonoverlapping(buffer.as_ptr(), buf.as_mut_ptr() as *mut StereoSample, BUFFER_SIZE);

cvt.buf = buf.as_mut_ptr();
let status_code = SDL_ConvertAudio(&mut cvt);
if status_code != 0{
alloncm marked this conversation as resolved.
Show resolved Hide resolved
std::panic!("error while converting audio, status code: {}", status_code);
}

let buf_ptr = cvt.buf as *mut StereoSample;
let length = cvt.len_cvt as usize / std::mem::size_of::<StereoSample>();
let mut output = vec![StereoSample::const_defualt();length];

std::ptr::copy_nonoverlapping(buf_ptr, output.as_mut_ptr(), length);

return output;
}
}
}
133 changes: 133 additions & 0 deletions gb/src/audio/sdl_pull_audio_device.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
use std::{ffi::c_void, mem::MaybeUninit};
use lib_gb::{GB_FREQUENCY, apu::audio_device::*};
use sdl2::sys::*;
use crate::audio::get_sdl_error_message;
alloncm marked this conversation as resolved.
Show resolved Hide resolved

use super::{AudioResampler, SdlAudioDevice};

use crossbeam_channel::{Receiver, SendError, Sender, bounded};


struct Data{
alloncm marked this conversation as resolved.
Show resolved Hide resolved
rx: Receiver<[Sample;BUFFER_SIZE]>,
current_buf: Option<[Sample;BUFFER_SIZE]>,
current_buf_index:usize,
}

pub struct SdlPullAudioDevice<AR:AudioResampler>{
alloncm marked this conversation as resolved.
Show resolved Hide resolved
resampler: AR,
buffer: [Sample;BUFFER_SIZE],
alloncm marked this conversation as resolved.
Show resolved Hide resolved
buffer_index:usize,

tarnsmiter: Sender<[Sample;BUFFER_SIZE]>,

userdata: Data
}

impl<AR:AudioResampler> SdlPullAudioDevice<AR>{
pub fn new(frequency:i32, turbo_mul:u8)->Self{

// cap of less than 2 hurts the fps
let(s,r) = bounded(2);
alloncm marked this conversation as resolved.
Show resolved Hide resolved
let data = Data{
current_buf:Option::None,
current_buf_index:0,
rx:r
};

let mut device = SdlPullAudioDevice{
buffer:[DEFAULT_SAPMPLE;BUFFER_SIZE],
buffer_index:0,
resampler: AudioResampler::new(GB_FREQUENCY * turbo_mul as u32, frequency as u32),
tarnsmiter:s,
userdata:data
};

let desired_audio_spec = SDL_AudioSpec{
freq: frequency,
format: AUDIO_S16SYS as u16,
channels: 2,
silence: 0,
samples: BUFFER_SIZE as u16,
padding: 0,
size: 0,
callback: Option::Some(audio_callback),
userdata: (&mut device.userdata) as *mut Data as *mut c_void
};


let mut uninit_audio_spec:MaybeUninit<SDL_AudioSpec> = MaybeUninit::uninit();

unsafe{
SDL_Init(SDL_INIT_AUDIO);
SDL_ClearError();
let id = SDL_OpenAudioDevice(std::ptr::null(), 0, &desired_audio_spec, uninit_audio_spec.as_mut_ptr() , 0);

if id == 0{
std::panic!("{}", get_sdl_error_message());
}

let init_audio_spec:SDL_AudioSpec = uninit_audio_spec.assume_init();

if init_audio_spec.freq != frequency {
std::panic!("Error initializing audio could not use the frequency: {}", frequency);
}

//This will start the audio processing
SDL_PauseAudioDevice(id, 0);
};
alloncm marked this conversation as resolved.
Show resolved Hide resolved

return device;
}

fn push_audio_to_device(&self, audio:&[Sample; BUFFER_SIZE])->Result<(), SendError<[Sample; BUFFER_SIZE]>>{
self.tarnsmiter.send(audio.clone())
alloncm marked this conversation as resolved.
Show resolved Hide resolved
}
}

impl<AR:AudioResampler> SdlAudioDevice<AR> for SdlPullAudioDevice<AR>{
fn get_audio_buffer(&mut self) ->(&mut [Sample;BUFFER_SIZE], &mut usize) {
(&mut self.buffer, &mut self.buffer_index)
}
fn get_resampler(&mut self) ->&mut AR {
&mut self.resampler
}
fn full_buffer_callback(&self) ->Result<(), String> {
self.push_audio_to_device(&self.buffer).map_err(|e|e.to_string())
}
}

impl<AR:AudioResampler> AudioDevice for SdlPullAudioDevice<AR>{
fn push_buffer(&mut self, buffer:&[StereoSample; BUFFER_SIZE]) {
SdlAudioDevice::push_buffer(self, buffer);
}
}

unsafe extern "C" fn audio_callback(userdata:*mut c_void, buffer:*mut u8, length:i32){
let length = length as usize;
let s = &mut *(userdata as *mut Data);
alloncm marked this conversation as resolved.
Show resolved Hide resolved

if s.current_buf.is_none(){
s.current_buf = Some(s.rx.recv().unwrap());
}

let samples = s.current_buf.unwrap();
let samples_size = (samples.len() * std::mem::size_of::<Sample>()) - s.current_buf_index;
let samples_ptr = (samples.as_ptr() as *mut u8).add(s.current_buf_index);
std::ptr::copy_nonoverlapping(samples_ptr, buffer, std::cmp::min(length, samples_size));

if length > samples_size && s.rx.is_empty(){
s.current_buf = Option::None;
s.current_buf_index = 0;
std::ptr::write_bytes(buffer.add(samples.len() as usize), 0, length - samples_size);
}
else if length > samples_size{
s.current_buf = Option::None;
s.current_buf_index = 0;
audio_callback(userdata, buffer.add(samples_size), (length - samples_size) as i32);
}
else{
s.current_buf_index = length;
}
}

Loading