From 6cd1225aa5ecdbdc4fec5a184164a53f256f886e Mon Sep 17 00:00:00 2001 From: alloncm Date: Sun, 12 Sep 2021 18:43:11 +0300 Subject: [PATCH 01/36] The emulation code now runs in another thread Since SDL is really ambiguous about rendering from another thread the emulation is running on another thread. Using crossbeam parker for sync between threads (Some kind of AutoResetEvent from C#) And using rtrb for the ring buffer queue for moving data between the threads --- Cargo.lock | 102 +++++++++++++++++++++++++++++++++++ gb/Cargo.toml | 4 +- gb/src/main.rs | 100 ++++++++++++++++++++++------------ gb/src/sdl_gfx_device.rs | 4 +- lib_gb/src/ppu/gfx_device.rs | 4 +- 5 files changed, 175 insertions(+), 39 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 288d6d7b..8907c423 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,7 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "adler" version = "1.0.2" @@ -27,6 +29,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "cache-padded" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba" + [[package]] name = "cc" version = "1.0.70" @@ -76,6 +84,74 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "crossbeam" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae5588f6b3c3cb05239e90bd110f257254aecd01e4635400391aeae07497845" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils", + "lazy_static", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b10ddc024425c88c2ad148c1b0fd53f4c6d38db9697c9f1588381212fa657c9" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" +dependencies = [ + "cfg-if 1.0.0", + "lazy_static", +] + [[package]] name = "encoding_rs" version = "0.8.28" @@ -151,9 +227,11 @@ name = "magenboy" version = "1.0.0" dependencies = [ "chrono", + "crossbeam", "fern", "lib_gb", "log", + "rtrb", "sdl2", "wav", ] @@ -164,6 +242,15 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +[[package]] +name = "memoffset" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" +dependencies = [ + "autocfg", +] + [[package]] name = "miniz_oxide" version = "0.4.4" @@ -225,6 +312,21 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9b1a3d5f46d53f4a3478e2be4a5a5ce5108ea58b100dcd139830eae7f79a3a1" +[[package]] +name = "rtrb" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "318256ac02f7e11a48a10339ba5dca8bd7eb17496abf384e8ea909bb2ae5275f" +dependencies = [ + "cache-padded", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + [[package]] name = "sdl2" version = "0.34.5" diff --git a/gb/Cargo.toml b/gb/Cargo.toml index f9339ee7..ed0e0a4c 100644 --- a/gb/Cargo.toml +++ b/gb/Cargo.toml @@ -11,4 +11,6 @@ log = "0.4" fern = "0.6.0" chrono = "0.4" sdl2 = {version = "0.34", features = ["bundled","static-link"]} -wav = "0.6.0" \ No newline at end of file +wav = "0.6.0" +crossbeam = "0.8" +rtrb = "0.1" \ No newline at end of file diff --git a/gb/src/main.rs b/gb/src/main.rs index 2f738005..23e6d045 100644 --- a/gb/src/main.rs +++ b/gb/src/main.rs @@ -7,7 +7,7 @@ mod multi_device_audio; mod sdl_gfx_device; use crate::{mbc_handler::*, sdl_joypad_provider::*, multi_device_audio::*}; -use lib_gb::{keypad::button::Button, machine::gameboy::GameBoy, mmu::gb_mmu::BOOT_ROM_SIZE, ppu::gb_ppu::{SCREEN_HEIGHT, SCREEN_WIDTH}, GB_FREQUENCY, apu::audio_device::*}; +use lib_gb::{GB_FREQUENCY, apu::audio_device::*, keypad::button::Button, machine::gameboy::GameBoy, mmu::gb_mmu::BOOT_ROM_SIZE, ppu::{gb_ppu::{SCREEN_HEIGHT, SCREEN_WIDTH}, gfx_device::GfxDevice}}; use std::{fs, env, result::Result, vec::Vec}; use log::info; use sdl2::sys::*; @@ -58,6 +58,18 @@ fn check_for_terminal_feature_flag(args:&Vec::, flag:&str)->bool{ args.len() >= 3 && args.contains(&String::from(flag)) } +struct SpscGfxDevice{ + producer: rtrb::Producer<[u32;SCREEN_HEIGHT * SCREEN_WIDTH ]>, + parker: crossbeam::sync::Parker, +} + +impl GfxDevice for SpscGfxDevice{ + fn swap_buffer(&mut self, buffer:&[u32; SCREEN_WIDTH * SCREEN_HEIGHT]) { + self.producer.push(buffer.clone()).unwrap(); + self.parker.park(); + } +} + fn main() { let args: Vec = env::args().collect(); @@ -68,41 +80,57 @@ fn main() { Result::Err(error)=>std::panic!("error initing logger: {}", error) } - let audio_device = sdl_audio_device::SdlAudioDevie::new(44100); - let mut devices: Vec::> = Vec::new(); - devices.push(Box::new(audio_device)); - if check_for_terminal_feature_flag(&args, "--file-audio"){ - let wav_ad = wav_file_audio_device::WavfileAudioDevice::new(44100, GB_FREQUENCY, "output.wav"); - devices.push(Box::new(wav_ad)); - } - - let audio_devices = MultiAudioDevice::new(devices); + let mut sdl_gfx_device = sdl_gfx_device::SdlGfxDevice::new(SCREEN_WIDTH as u32, SCREEN_HEIGHT as u32, "MagenBoy", SCREEN_SCALE); - let sdl_gfx_device = sdl_gfx_device::SdlGfxDevice::new(SCREEN_WIDTH as u32, SCREEN_HEIGHT as u32, "MagenBoy", SCREEN_SCALE); - - let program_name = &args[1]; - let mut mbc = initialize_mbc(program_name); - let joypad_provider = SdlJoypadProvider::new(buttons_mapper); - - let mut gameboy = match fs::read("Dependencies/Init/dmg_boot.bin"){ - Result::Ok(file)=>{ - info!("found bootrom!"); + let (producer, mut c) = rtrb::RingBuffer::<[u32; SCREEN_HEIGHT * SCREEN_WIDTH]>::new(1).split(); + let parker = crossbeam::sync::Parker::new(); + let unparker = parker.unparker().clone(); + let spsc_gfx_device = SpscGfxDevice{producer, parker: parker}; + - let mut bootrom:[u8;BOOT_ROM_SIZE] = [0;BOOT_ROM_SIZE]; - for i in 0..BOOT_ROM_SIZE{ - bootrom[i] = file[i]; - } - - GameBoy::new_with_bootrom(&mut mbc, joypad_provider,audio_devices, sdl_gfx_device, bootrom) + let program_name = args[1].clone(); + + std::thread::Builder::new().name("Emualtion Thread".to_string()).spawn(move ||{ + + let audio_device = sdl_audio_device::SdlAudioDevie::new(44100); + let mut devices: Vec::> = Vec::new(); + devices.push(Box::new(audio_device)); + if check_for_terminal_feature_flag(&args, "--file-audio"){ + let wav_ad = wav_file_audio_device::WavfileAudioDevice::new(44100, GB_FREQUENCY, "output.wav"); + devices.push(Box::new(wav_ad)); } - Result::Err(_)=>{ - info!("could not find bootrom... booting directly to rom"); - GameBoy::new(&mut mbc, joypad_provider, audio_devices, sdl_gfx_device) + let audio_devices = MultiAudioDevice::new(devices); + let mut mbc = initialize_mbc(&program_name); + let joypad_provider = SdlJoypadProvider::new(buttons_mapper); + + let mut gameboy = match fs::read("Dependencies/Init/dmg_boot.bin"){ + Result::Ok(file)=>{ + info!("found bootrom!"); + + let mut bootrom:[u8;BOOT_ROM_SIZE] = [0;BOOT_ROM_SIZE]; + for i in 0..BOOT_ROM_SIZE{ + bootrom[i] = file[i]; + } + + GameBoy::new_with_bootrom(&mut mbc, joypad_provider,audio_devices, spsc_gfx_device, bootrom) + } + Result::Err(_)=>{ + info!("could not find bootrom... booting directly to rom"); + + GameBoy::new(&mut mbc, joypad_provider, audio_devices, spsc_gfx_device) + } + }; + + info!("initialized gameboy successfully!"); + + loop{ + gameboy.cycle_frame(); } - }; - - info!("initialized gameboy successfully!"); + + drop(gameboy); + release_mbc(&program_name, mbc); + }).unwrap(); unsafe{ let mut event: std::mem::MaybeUninit = std::mem::MaybeUninit::uninit(); @@ -116,7 +144,11 @@ fn main() { } } - gameboy.cycle_frame(); + if !c.is_empty(){ + let pop = c.pop().unwrap(); + sdl_gfx_device.swap_buffer(&pop); + unparker.unpark(); + } let end = SDL_GetPerformanceCounter(); let elapsed_ms:f64 = (end - start) as f64 / SDL_GetPerformanceFrequency() as f64 * 1000.0; @@ -129,6 +161,4 @@ fn main() { SDL_Quit(); } - drop(gameboy); - release_mbc(program_name, mbc); -} +} \ No newline at end of file diff --git a/gb/src/sdl_gfx_device.rs b/gb/src/sdl_gfx_device.rs index d74ffa46..a689b182 100644 --- a/gb/src/sdl_gfx_device.rs +++ b/gb/src/sdl_gfx_device.rs @@ -1,6 +1,6 @@ use std::ffi::{CString, c_void}; -use lib_gb::ppu::gfx_device::GfxDevice; +use lib_gb::ppu::{gb_ppu::{SCREEN_HEIGHT, SCREEN_WIDTH}, gfx_device::GfxDevice}; use sdl2::sys::*; pub struct SdlGfxDevice{ @@ -60,7 +60,7 @@ impl SdlGfxDevice{ } impl GfxDevice for SdlGfxDevice{ - fn swap_buffer(&self, buffer:&[u32]) { + fn swap_buffer(&mut self, buffer:&[u32; SCREEN_HEIGHT * SCREEN_WIDTH]) { unsafe{ let extended_buffer = Self::extend_vec(buffer, self.sacle as usize, self.width as usize, self.height as usize); diff --git a/lib_gb/src/ppu/gfx_device.rs b/lib_gb/src/ppu/gfx_device.rs index 400f51a2..017acacf 100644 --- a/lib_gb/src/ppu/gfx_device.rs +++ b/lib_gb/src/ppu/gfx_device.rs @@ -1,3 +1,5 @@ +use super::gb_ppu::{SCREEN_HEIGHT, SCREEN_WIDTH}; + pub trait GfxDevice{ - fn swap_buffer(&self, buffer:&[u32]); + fn swap_buffer(&mut self, buffer:&[u32; SCREEN_HEIGHT * SCREEN_WIDTH]); } \ No newline at end of file From e2f05f105fb65f63d91ce45d121a2ae7410512e9 Mon Sep 17 00:00:00 2001 From: alloncm Date: Sun, 12 Sep 2021 19:08:48 +0300 Subject: [PATCH 02/36] Improved the parking and the sync --- gb/src/main.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/gb/src/main.rs b/gb/src/main.rs index 23e6d045..611bb4a5 100644 --- a/gb/src/main.rs +++ b/gb/src/main.rs @@ -66,7 +66,9 @@ struct SpscGfxDevice{ impl GfxDevice for SpscGfxDevice{ fn swap_buffer(&mut self, buffer:&[u32; SCREEN_WIDTH * SCREEN_HEIGHT]) { self.producer.push(buffer.clone()).unwrap(); - self.parker.park(); + if self.producer.is_full(){ + self.parker.park(); + } } } @@ -146,8 +148,8 @@ fn main() { if !c.is_empty(){ let pop = c.pop().unwrap(); - sdl_gfx_device.swap_buffer(&pop); unparker.unpark(); + sdl_gfx_device.swap_buffer(&pop); } let end = SDL_GetPerformanceCounter(); From 770dd9c6f766412cb3cfb2db9b93eccf23cd55fa Mon Sep 17 00:00:00 2001 From: alloncm Date: Tue, 14 Sep 2021 02:08:35 +0300 Subject: [PATCH 03/36] Trying to improve perf not tested yet --- lib_gb/src/apu/frame_sequencer.rs | 2 +- lib_gb/src/apu/timer.rs | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/lib_gb/src/apu/frame_sequencer.rs b/lib_gb/src/apu/frame_sequencer.rs index c3ca4f6d..405cd26b 100644 --- a/lib_gb/src/apu/frame_sequencer.rs +++ b/lib_gb/src/apu/frame_sequencer.rs @@ -52,7 +52,7 @@ impl FrameSequencer{ // in a rare case where the length hasnt started the second iteration there might be a length step. // This will happen only after if the channel is activated after being not active. pub fn should_next_step_clock_length(&self)->bool{ - self.counter % 2 == 0 + self.counter & 1 == 0 } pub fn reset(&mut self){ diff --git a/lib_gb/src/apu/timer.rs b/lib_gb/src/apu/timer.rs index 7604b2e2..d1480692 100644 --- a/lib_gb/src/apu/timer.rs +++ b/lib_gb/src/apu/timer.rs @@ -13,12 +13,8 @@ impl Timer{ pub fn cycle(&mut self)->bool{ if self.cycles_to_tick != 0{ - self.cycle_counter += 1; - if self.cycle_counter >= self.cycles_to_tick{ - self.cycle_counter = 0; - - return true; - } + self.cycle_counter = (self.cycle_counter + 1) % self.cycles_to_tick; + return self.cycle_counter == 0; } return false; From bb06e5b3788786ae2e2ea41b81f84ae870bc9553 Mon Sep 17 00:00:00 2001 From: alloncm Date: Tue, 14 Sep 2021 02:09:17 +0300 Subject: [PATCH 04/36] Add bench framework chose criterion cause the regualr one is stil unstable. test with `cargo bench` --- Cargo.lock | 440 +++++++++++++++++++++++++++++++++++++ lib_gb/Cargo.toml | 7 + lib_gb/benches/my_bench.rs | 24 ++ 3 files changed, 471 insertions(+) create mode 100644 lib_gb/benches/my_bench.rs diff --git a/Cargo.lock b/Cargo.lock index 8907c423..087baf4b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,17 @@ dependencies = [ "memchr", ] +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + [[package]] name = "autocfg" version = "1.0.1" @@ -29,12 +40,39 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bstr" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90682c8d613ad3373e66de8c6411e0ae2ab2571e879d2efbf73558cc66f21279" +dependencies = [ + "lazy_static", + "memchr", + "regex-automata", + "serde", +] + +[[package]] +name = "bumpalo" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c59e7af012c713f529e7a3ee57ce9b31ddd858d4b512923602f74608b009631" + [[package]] name = "cache-padded" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba" +[[package]] +name = "cast" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c24dab4283a142afa2fdca129b80ad2c6284e073930f964c3a1293c225ee39a" +dependencies = [ + "rustc_version", +] + [[package]] name = "cc" version = "1.0.70" @@ -66,6 +104,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "clap" +version = "2.33.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" +dependencies = [ + "bitflags", + "textwrap", + "unicode-width", +] + [[package]] name = "cmake" version = "0.1.45" @@ -84,6 +133,42 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "criterion" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1604dafd25fba2fe2d5895a9da139f8dc9b319a5fe5354ca137cbbce4e178d10" +dependencies = [ + "atty", + "cast", + "clap", + "criterion-plot", + "csv", + "itertools", + "lazy_static", + "num-traits", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_cbor", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d00996de9f2f7559f7f4dc286073197f83e92256a59ed395f9aac01fe717da57" +dependencies = [ + "cast", + "itertools", +] + [[package]] name = "crossbeam" version = "0.8.1" @@ -152,6 +237,34 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "csv" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" +dependencies = [ + "bstr", + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" +dependencies = [ + "memchr", +] + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + [[package]] name = "encoding_rs" version = "0.8.28" @@ -194,6 +307,45 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "half" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62aca2aba2d62b4a7f5b33f3712cb1b0692779a56fb510499d5c0aa594daeaf3" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "itertools" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + +[[package]] +name = "js-sys" +version = "0.3.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1866b355d9c878e5e607473cbe3f63282c0b7aad2db1dbebf55076c686918254" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -204,6 +356,7 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" name = "lib_gb" version = "1.0.0" dependencies = [ + "criterion", "log", ] @@ -280,6 +433,93 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "oorandom" +version = "11.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" + +[[package]] +name = "plotters" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a3fd9ec30b9749ce28cd91f255d569591cdf937fe280c312143e3c4bad6f2a" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d88417318da0eaf0fdcdb51a0ee6c3bed624333bff8f946733049380be67ac1c" + +[[package]] +name = "plotters-svg" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521fa9638fa597e1dc53e9412a4f9cefb01187ee1f7413076f9e6749e2885ba9" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "proc-macro2" +version = "1.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rayon" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90" +dependencies = [ + "autocfg", + "crossbeam-deque", + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "lazy_static", + "num_cpus", +] + [[package]] name = "redox_syscall" version = "0.2.10" @@ -300,6 +540,12 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" + [[package]] name = "regex-syntax" version = "0.6.25" @@ -321,6 +567,30 @@ dependencies = [ "cache-padded", ] +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "scopeguard" version = "1.1.0" @@ -354,6 +624,61 @@ dependencies = [ "version-compare", ] +[[package]] +name = "semver" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012" + +[[package]] +name = "serde" +version = "1.0.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" + +[[package]] +name = "serde_cbor" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" +dependencies = [ + "half", + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7f9e390c27c3c0ce8bc5d725f6e4d30a29d26659494aa4b17535f7522c5c950" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "syn" +version = "1.0.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f107db402c2c2055242dbf4d2af0e69197202e9faacbef9571bbe47f5a1b84" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + [[package]] name = "tar" version = "0.4.37" @@ -365,6 +690,15 @@ dependencies = [ "xattr", ] +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + [[package]] name = "time" version = "0.1.44" @@ -376,6 +710,28 @@ dependencies = [ "winapi", ] +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "unicode-width" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + [[package]] name = "unidiff" version = "0.3.3" @@ -393,12 +749,77 @@ version = "0.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d63556a25bae6ea31b52e640d7c41d1ab27faba4ccb600013837a3d0b3994ca1" +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + [[package]] name = "wasi" version = "0.10.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" +[[package]] +name = "wasm-bindgen" +version = "0.2.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e68338db6becec24d3c7977b5bf8a48be992c934b5d07177e3931f5dc9b076c" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f34c405b4f0658583dba0c1c7c9b694f3cac32655db463b56c254a1c75269523" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d5a6580be83b19dc570a8f9c324251687ab2184e57086f71625feb57ec77c8" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3775a030dc6f5a0afd8a84981a21cc92a781eb429acef9ecce476d0c9113e92" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c279e376c7a8e8752a8f1eaa35b7b0bee6bb9fb0cdacfa97cc3f1f289c87e2b4" + [[package]] name = "wav" version = "0.6.0" @@ -408,6 +829,16 @@ dependencies = [ "riff", ] +[[package]] +name = "web-sys" +version = "0.3.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a84d70d1ec7d2da2d26a5bd78f4bca1b8c3254805363ce743b7a05bc30d195a" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "winapi" version = "0.3.9" @@ -424,6 +855,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/lib_gb/Cargo.toml b/lib_gb/Cargo.toml index ca607529..beaec5c7 100644 --- a/lib_gb/Cargo.toml +++ b/lib_gb/Cargo.toml @@ -5,3 +5,10 @@ authors = ["alloncm "] edition = "2018" [dependencies] log = "0.4" + +[dev-dependencies] +criterion = "0.3" + +[[bench]] +name = "my_bench" +harness = false \ No newline at end of file diff --git a/lib_gb/benches/my_bench.rs b/lib_gb/benches/my_bench.rs new file mode 100644 index 00000000..c053881c --- /dev/null +++ b/lib_gb/benches/my_bench.rs @@ -0,0 +1,24 @@ +use criterion::*; +use lib_gb::apu::{ + gb_apu::*, + audio_device::*, +}; + +pub fn criterion_bench(c: &mut Criterion){ + struct StubApu; + impl AudioDevice for StubApu{ + fn push_buffer(&mut self, _buffer:&[Sample]){} + } + + c.bench_function("test apu", |b| b.iter(||{ + let mut apu = GbApu::new(StubApu{}); + apu.enabled = true; + apu.sweep_tone_channel.enabled = true; + for _ in 0..100000{ + apu.cycle(255); + } + })); +} + +criterion_group!(benches, criterion_bench); +criterion_main!(benches); \ No newline at end of file From bafdfb82643b94ee5422c0286d96d427f6001e27 Mon Sep 17 00:00:00 2001 From: alloncm Date: Sat, 18 Sep 2021 03:26:55 +0300 Subject: [PATCH 05/36] Some cleaning and optimization - Use crossbeam-utils instead of the whole lib - Cache the analog sample in the channel instead of calculating it each time --- Cargo.lock | 26 +------------------------- gb/Cargo.toml | 2 +- gb/src/main.rs | 4 ++-- lib_gb/src/apu/channel.rs | 11 ++++++----- 4 files changed, 10 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 087baf4b..fba6b13d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -169,20 +169,6 @@ dependencies = [ "itertools", ] -[[package]] -name = "crossbeam" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae5588f6b3c3cb05239e90bd110f257254aecd01e4635400391aeae07497845" -dependencies = [ - "cfg-if 1.0.0", - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-epoch", - "crossbeam-queue", - "crossbeam-utils", -] - [[package]] name = "crossbeam-channel" version = "0.5.1" @@ -217,16 +203,6 @@ dependencies = [ "scopeguard", ] -[[package]] -name = "crossbeam-queue" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b10ddc024425c88c2ad148c1b0fd53f4c6d38db9697c9f1588381212fa657c9" -dependencies = [ - "cfg-if 1.0.0", - "crossbeam-utils", -] - [[package]] name = "crossbeam-utils" version = "0.8.5" @@ -380,7 +356,7 @@ name = "magenboy" version = "1.0.0" dependencies = [ "chrono", - "crossbeam", + "crossbeam-utils", "fern", "lib_gb", "log", diff --git a/gb/Cargo.toml b/gb/Cargo.toml index ed0e0a4c..32547146 100644 --- a/gb/Cargo.toml +++ b/gb/Cargo.toml @@ -12,5 +12,5 @@ fern = "0.6.0" chrono = "0.4" sdl2 = {version = "0.34", features = ["bundled","static-link"]} wav = "0.6.0" -crossbeam = "0.8" +crossbeam-utils = "0.8" rtrb = "0.1" \ No newline at end of file diff --git a/gb/src/main.rs b/gb/src/main.rs index 611bb4a5..d6a5b2b2 100644 --- a/gb/src/main.rs +++ b/gb/src/main.rs @@ -60,7 +60,7 @@ fn check_for_terminal_feature_flag(args:&Vec::, flag:&str)->bool{ struct SpscGfxDevice{ producer: rtrb::Producer<[u32;SCREEN_HEIGHT * SCREEN_WIDTH ]>, - parker: crossbeam::sync::Parker, + parker: crossbeam_utils::sync::Parker, } impl GfxDevice for SpscGfxDevice{ @@ -85,7 +85,7 @@ fn main() { let mut sdl_gfx_device = sdl_gfx_device::SdlGfxDevice::new(SCREEN_WIDTH as u32, SCREEN_HEIGHT as u32, "MagenBoy", SCREEN_SCALE); let (producer, mut c) = rtrb::RingBuffer::<[u32; SCREEN_HEIGHT * SCREEN_WIDTH]>::new(1).split(); - let parker = crossbeam::sync::Parker::new(); + let parker = crossbeam_utils::sync::Parker::new(); let unparker = parker.unparker().clone(); let spsc_gfx_device = SpscGfxDevice{producer, parker: parker}; diff --git a/lib_gb/src/apu/channel.rs b/lib_gb/src/apu/channel.rs index bc3e19e4..3ddc7fad 100644 --- a/lib_gb/src/apu/channel.rs +++ b/lib_gb/src/apu/channel.rs @@ -9,7 +9,7 @@ pub struct Channel{ pub sample_producer:Procuder, pub timer:Timer, - last_sample:u8, + last_sample:f32, } impl Channel{ @@ -21,7 +21,7 @@ impl Channel{ length_enable:false, timer: Timer::new(sample_producer.get_updated_frequency_ticks(0)), sample_producer, - last_sample: 0 + last_sample: 0.0 } } @@ -44,7 +44,7 @@ impl Channel{ self.timer.update_cycles_to_tick(self.sample_producer.get_updated_frequency_ticks(self.frequency)); self.sample_producer.reset(); - self.last_sample = 0; + self.last_sample = 0.0; } pub fn get_audio_sample(&mut self)->f32{ @@ -52,7 +52,8 @@ impl Channel{ let sample = if self.timer.cycle(){ self.timer.update_cycles_to_tick(self.sample_producer.get_updated_frequency_ticks(self.frequency)); - self.sample_producer.produce() + let s = self.sample_producer.produce(); + self.convert_digtial_to_analog(s) } else{ self.last_sample @@ -60,7 +61,7 @@ impl Channel{ self.last_sample = sample; - return self.convert_digtial_to_analog(self.last_sample); + return self.last_sample; } return 0.0; From a549639db661a2bf13d33dfe4c516bbf93b132d7 Mon Sep 17 00:00:00 2001 From: alloncm Date: Sat, 18 Sep 2021 03:31:24 +0300 Subject: [PATCH 06/36] Optimize the APU timer This optimization gained a crazy performance boost! Probably cause of the lack of division in the new solution. --- lib_gb/src/apu/timer.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib_gb/src/apu/timer.rs b/lib_gb/src/apu/timer.rs index d1480692..a0c0738b 100644 --- a/lib_gb/src/apu/timer.rs +++ b/lib_gb/src/apu/timer.rs @@ -11,9 +11,14 @@ impl Timer{ } } + // This function is a hot spot for the APU, almost every component uses the timer pub fn cycle(&mut self)->bool{ if self.cycles_to_tick != 0{ - self.cycle_counter = (self.cycle_counter + 1) % self.cycles_to_tick; + // The calculation used to be this: + // self.cycle_counter = (self.cycle_counter + 1) % self.cycles_to_tick; + // After benching with a profiler I found that those 2 lines are much faster, probably cause there is no division here + self.cycle_counter += 1; + self.cycle_counter = (self.cycle_counter != self.cycles_to_tick) as u16 * self.cycle_counter; return self.cycle_counter == 0; } From 1d2f1f896baf8a29e371fdfa981587fdba5fafd7 Mon Sep 17 00:00:00 2001 From: alloncm Date: Sat, 18 Sep 2021 15:09:46 +0300 Subject: [PATCH 07/36] Move the sample type behind Custom type I was checking optimization of replacing f32 with u16 or u32 but it seems that f32 is the better option, anyway if another platform will need replacing (or adding a compile condition should be easier). --- gb/src/audio_resampler.rs | 18 +++++++++--------- gb/src/multi_device_audio.rs | 2 +- gb/src/sdl_audio_device.rs | 8 ++++---- gb/src/wav_file_audio_device.rs | 4 ++-- lib_gb/benches/my_bench.rs | 4 ++-- lib_gb/src/apu/audio_device.rs | 17 +++++++++++++---- lib_gb/src/apu/channel.rs | 15 ++++++++------- lib_gb/src/apu/gb_apu.rs | 8 ++++---- lib_gb/src/apu/sound_terminal.rs | 10 +++++----- 9 files changed, 48 insertions(+), 38 deletions(-) diff --git a/gb/src/audio_resampler.rs b/gb/src/audio_resampler.rs index 0001d5e1..d82a796b 100644 --- a/gb/src/audio_resampler.rs +++ b/gb/src/audio_resampler.rs @@ -1,8 +1,8 @@ -use lib_gb::apu::audio_device::Sample; +use lib_gb::apu::audio_device::{SAMPLE_CONSTANT_DEFAULT, Sample, StereoSample}; pub struct AudioResampler{ to_skip:u32, - sampling_buffer:Vec, + sampling_buffer:Vec, sampling_counter:u32 } @@ -20,15 +20,15 @@ impl AudioResampler{ } } - pub fn resample(&mut self, buffer:&[Sample])->Vec{ + pub fn resample(&mut self, buffer:&[StereoSample])->Vec{ let mut output = Vec::new(); for sample in buffer.into_iter(){ self.sampling_buffer.push(*sample); self.sampling_counter += 1; if self.sampling_counter == self.to_skip { - let (interpulated_left_sample, interpulated_right_sample) = Self::interpolate_sample(&self.sampling_buffer); - let interpolated_sample = Sample{left_sample: interpulated_left_sample, right_sample: interpulated_right_sample}; + let interpolated_stereo_sample = Self::interpolate_sample(&self.sampling_buffer); + let interpolated_sample = StereoSample{left_sample: interpolated_stereo_sample.left_sample, right_sample: interpolated_stereo_sample.left_sample}; self.sampling_counter = 0; self.sampling_buffer.clear(); @@ -39,11 +39,11 @@ impl AudioResampler{ return output; } - fn interpolate_sample(samples:&[Sample])->(f32, f32){ + fn interpolate_sample(samples:&[StereoSample])->StereoSample{ - let interpulated_left_sample = samples.iter().fold(0.0, |acc, x| acc + x.left_sample) / samples.len() as f32; - let interpulated_right_sample = samples.iter().fold(0.0, |acc, x| acc + x.right_sample) / samples.len() as f32; + let interpulated_left_sample = samples.iter().fold(SAMPLE_CONSTANT_DEFAULT, |acc, x| acc + x.left_sample) / samples.len() as Sample; + let interpulated_right_sample = samples.iter().fold(SAMPLE_CONSTANT_DEFAULT, |acc, x| acc + x.right_sample) / samples.len() as Sample; - return (interpulated_left_sample, interpulated_right_sample); + return StereoSample{left_sample:interpulated_left_sample, right_sample:interpulated_right_sample}; } } \ No newline at end of file diff --git a/gb/src/multi_device_audio.rs b/gb/src/multi_device_audio.rs index fe169ad1..892c859b 100644 --- a/gb/src/multi_device_audio.rs +++ b/gb/src/multi_device_audio.rs @@ -11,7 +11,7 @@ impl MultiAudioDevice{ } impl AudioDevice for MultiAudioDevice{ - fn push_buffer(&mut self, buffer:&[Sample]) { + fn push_buffer(&mut self, buffer:&[StereoSample]) { for device in self.devices.iter_mut(){ device.push_buffer(buffer); } diff --git a/gb/src/sdl_audio_device.rs b/gb/src/sdl_audio_device.rs index 2346a9f4..da739b7b 100644 --- a/gb/src/sdl_audio_device.rs +++ b/gb/src/sdl_audio_device.rs @@ -11,7 +11,7 @@ pub struct SdlAudioDevie{ device_id: SDL_AudioDeviceID, resampler: AudioResampler, - buffer: Vec + buffer: Vec } impl SdlAudioDevie{ @@ -69,9 +69,9 @@ impl SdlAudioDevie{ } - fn push_audio_to_device(&self, audio:&[f32])->Result<(),&str>{ + fn push_audio_to_device(&self, audio:&[Sample])->Result<(),&str>{ let audio_ptr: *const c_void = audio.as_ptr() as *const c_void; - let data_byte_len = (audio.len() * std::mem::size_of::()) as u32; + let data_byte_len = (audio.len() * std::mem::size_of::()) as u32; unsafe{ while SDL_GetQueuedAudioSize(self.device_id) > BYTES_TO_WAIT{} @@ -87,7 +87,7 @@ impl SdlAudioDevie{ } impl AudioDevice for SdlAudioDevie{ - fn push_buffer(&mut self, buffer:&[Sample]){ + fn push_buffer(&mut self, buffer:&[StereoSample]){ for sample in self.resampler.resample(buffer){ self.buffer.push(sample.left_sample); diff --git a/gb/src/wav_file_audio_device.rs b/gb/src/wav_file_audio_device.rs index 713c581c..3e4a5389 100644 --- a/gb/src/wav_file_audio_device.rs +++ b/gb/src/wav_file_audio_device.rs @@ -6,7 +6,7 @@ pub struct WavfileAudioDevice{ target_frequency:u32, resampler: AudioResampler, filename:&'static str, - samples_buffer:Vec:: + samples_buffer:Vec:: } impl WavfileAudioDevice{ @@ -21,7 +21,7 @@ impl WavfileAudioDevice{ } impl AudioDevice for WavfileAudioDevice{ - fn push_buffer(&mut self, buffer:&[Sample]) { + fn push_buffer(&mut self, buffer:&[StereoSample]) { self.samples_buffer.append(self.resampler.resample(buffer).as_mut()); } } diff --git a/lib_gb/benches/my_bench.rs b/lib_gb/benches/my_bench.rs index c053881c..fde4c31c 100644 --- a/lib_gb/benches/my_bench.rs +++ b/lib_gb/benches/my_bench.rs @@ -7,14 +7,14 @@ use lib_gb::apu::{ pub fn criterion_bench(c: &mut Criterion){ struct StubApu; impl AudioDevice for StubApu{ - fn push_buffer(&mut self, _buffer:&[Sample]){} + fn push_buffer(&mut self, _buffer:&[StereoSample]){} } c.bench_function("test apu", |b| b.iter(||{ let mut apu = GbApu::new(StubApu{}); apu.enabled = true; apu.sweep_tone_channel.enabled = true; - for _ in 0..100000{ + for _ in 0..10{ apu.cycle(255); } })); diff --git a/lib_gb/src/apu/audio_device.rs b/lib_gb/src/apu/audio_device.rs index 76a2f988..a906d1f1 100644 --- a/lib_gb/src/apu/audio_device.rs +++ b/lib_gb/src/apu/audio_device.rs @@ -1,9 +1,18 @@ +pub type Sample = f32; +pub const SAMPLE_CONSTANT_DEFAULT:Sample = 0.0; + #[derive(Copy, Clone)] -pub struct Sample{ - pub left_sample:f32, - pub right_sample:f32 +pub struct StereoSample{ + pub left_sample:Sample, + pub right_sample:Sample +} + +impl StereoSample{ + pub const fn const_defualt()->Self{ + Self{left_sample:SAMPLE_CONSTANT_DEFAULT, right_sample:SAMPLE_CONSTANT_DEFAULT} + } } pub trait AudioDevice{ - fn push_buffer(&mut self, buffer:&[Sample]); + fn push_buffer(&mut self, buffer:&[StereoSample]); } \ No newline at end of file diff --git a/lib_gb/src/apu/channel.rs b/lib_gb/src/apu/channel.rs index 3ddc7fad..58f21c99 100644 --- a/lib_gb/src/apu/channel.rs +++ b/lib_gb/src/apu/channel.rs @@ -1,3 +1,4 @@ +use super::audio_device::{SAMPLE_CONSTANT_DEFAULT, Sample}; use super::sample_producer::SampleProducer; use super::timer::Timer; @@ -9,7 +10,7 @@ pub struct Channel{ pub sample_producer:Procuder, pub timer:Timer, - last_sample:f32, + last_sample:Sample, } impl Channel{ @@ -21,7 +22,7 @@ impl Channel{ length_enable:false, timer: Timer::new(sample_producer.get_updated_frequency_ticks(0)), sample_producer, - last_sample: 0.0 + last_sample: SAMPLE_CONSTANT_DEFAULT } } @@ -44,10 +45,10 @@ impl Channel{ self.timer.update_cycles_to_tick(self.sample_producer.get_updated_frequency_ticks(self.frequency)); self.sample_producer.reset(); - self.last_sample = 0.0; + self.last_sample = SAMPLE_CONSTANT_DEFAULT; } - pub fn get_audio_sample(&mut self)->f32{ + pub fn get_audio_sample(&mut self)->Sample{ if self.enabled{ let sample = if self.timer.cycle(){ @@ -64,10 +65,10 @@ impl Channel{ return self.last_sample; } - return 0.0; + return SAMPLE_CONSTANT_DEFAULT; } - fn convert_digtial_to_analog(&self, sample:u8)->f32{ - (sample as f32 / 7.5 ) - 1.0 + fn convert_digtial_to_analog(&self, sample:u8)->Sample{ + (sample as Sample / 7.5 ) - 1.0 } } diff --git a/lib_gb/src/apu/gb_apu.rs b/lib_gb/src/apu/gb_apu.rs index 1cc35c25..ff34f474 100644 --- a/lib_gb/src/apu/gb_apu.rs +++ b/lib_gb/src/apu/gb_apu.rs @@ -22,7 +22,7 @@ pub struct GbApu{ pub left_terminal:SoundTerminal, pub enabled:bool, - audio_buffer:[Sample;AUDIO_BUFFER_SIZE], + audio_buffer:[StereoSample;AUDIO_BUFFER_SIZE], current_t_cycle:u32, device:Device, last_enabled_state:bool @@ -36,7 +36,7 @@ impl GbApu{ wave_channel:Channel::::new(WaveSampleProducer::default()), tone_channel: Channel::::new(SquareSampleProducer::new()), noise_channel: Channel::::new(NoiseSampleProducer::default()), - audio_buffer:[Sample{left_sample:0.0, right_sample:0.0}; AUDIO_BUFFER_SIZE], + audio_buffer:[StereoSample::const_defualt(); AUDIO_BUFFER_SIZE], current_t_cycle:0, device:device, right_terminal: SoundTerminal::default(), @@ -56,7 +56,7 @@ impl GbApu{ let tick = self.frame_sequencer.cycle(); self.update_channels_for_frame_squencer(tick); - let mut samples:[f32;NUMBER_OF_CHANNELS] = [0.0;NUMBER_OF_CHANNELS]; + let mut samples:[Sample;NUMBER_OF_CHANNELS] = [SAMPLE_CONSTANT_DEFAULT;NUMBER_OF_CHANNELS]; samples[0] = self.sweep_tone_channel.get_audio_sample(); samples[1] = self.tone_channel.get_audio_sample(); samples[2] = self.wave_channel.get_audio_sample(); @@ -75,7 +75,7 @@ impl GbApu{ } else{ for _ in 0..t_cycles{ - self.audio_buffer[self.current_t_cycle as usize] = Sample{right_sample:0.0, left_sample:0.0}; + self.audio_buffer[self.current_t_cycle as usize] = StereoSample::const_defualt(); self.current_t_cycle += 1; self.push_buffer_if_full(); diff --git a/lib_gb/src/apu/sound_terminal.rs b/lib_gb/src/apu/sound_terminal.rs index 68b909b5..b685440c 100644 --- a/lib_gb/src/apu/sound_terminal.rs +++ b/lib_gb/src/apu/sound_terminal.rs @@ -1,4 +1,4 @@ -use super::sound_utils::NUMBER_OF_CHANNELS; +use super::{audio_device::{SAMPLE_CONSTANT_DEFAULT, Sample}, sound_utils::NUMBER_OF_CHANNELS}; pub struct SoundTerminal{ pub enabled:bool, @@ -17,16 +17,16 @@ impl Default for SoundTerminal{ } impl SoundTerminal{ - pub fn mix_terminal_samples(&self, samples:&[f32;NUMBER_OF_CHANNELS])->f32{ - let mut mixed_sample:f32 = 0.0; + pub fn mix_terminal_samples(&self, samples:&[Sample;NUMBER_OF_CHANNELS])->Sample{ + let mut mixed_sample:Sample = SAMPLE_CONSTANT_DEFAULT; for i in 0..NUMBER_OF_CHANNELS{ if self.channels[i]{ mixed_sample += samples[i]; } } - mixed_sample /= NUMBER_OF_CHANNELS as f32; + mixed_sample /= NUMBER_OF_CHANNELS as Sample; - return mixed_sample * (self.volume as f32 + 1.0); + return mixed_sample * (self.volume + 1) as Sample; } } \ No newline at end of file From 800755ff892061fabe7bbb18796bf3b83b3b5761 Mon Sep 17 00:00:00 2001 From: Alon Cohen-Magen Date: Mon, 20 Sep 2021 16:39:49 +0300 Subject: [PATCH 08/36] The gameboy can exit properly Add proper handling of closing the program --- gb/src/main.rs | 108 +++++++++++++++++++++++++++++-------------------- 1 file changed, 65 insertions(+), 43 deletions(-) diff --git a/gb/src/main.rs b/gb/src/main.rs index d6a5b2b2..3705032b 100644 --- a/gb/src/main.rs +++ b/gb/src/main.rs @@ -65,9 +65,11 @@ struct SpscGfxDevice{ impl GfxDevice for SpscGfxDevice{ fn swap_buffer(&mut self, buffer:&[u32; SCREEN_WIDTH * SCREEN_HEIGHT]) { - self.producer.push(buffer.clone()).unwrap(); - if self.producer.is_full(){ - self.parker.park(); + if !self.producer.is_abandoned(){ + self.producer.push(buffer.clone()).unwrap(); + if self.producer.is_full() && !self.producer.is_abandoned() { + self.parker.park(); + } } } } @@ -91,48 +93,14 @@ fn main() { let program_name = args[1].clone(); - - std::thread::Builder::new().name("Emualtion Thread".to_string()).spawn(move ||{ - - let audio_device = sdl_audio_device::SdlAudioDevie::new(44100); - let mut devices: Vec::> = Vec::new(); - devices.push(Box::new(audio_device)); - if check_for_terminal_feature_flag(&args, "--file-audio"){ - let wav_ad = wav_file_audio_device::WavfileAudioDevice::new(44100, GB_FREQUENCY, "output.wav"); - devices.push(Box::new(wav_ad)); - } - let audio_devices = MultiAudioDevice::new(devices); - let mut mbc = initialize_mbc(&program_name); - let joypad_provider = SdlJoypadProvider::new(buttons_mapper); - - let mut gameboy = match fs::read("Dependencies/Init/dmg_boot.bin"){ - Result::Ok(file)=>{ - info!("found bootrom!"); - - let mut bootrom:[u8;BOOT_ROM_SIZE] = [0;BOOT_ROM_SIZE]; - for i in 0..BOOT_ROM_SIZE{ - bootrom[i] = file[i]; - } - - GameBoy::new_with_bootrom(&mut mbc, joypad_provider,audio_devices, spsc_gfx_device, bootrom) - } - Result::Err(_)=>{ - info!("could not find bootrom... booting directly to rom"); - - GameBoy::new(&mut mbc, joypad_provider, audio_devices, spsc_gfx_device) - } - }; - - info!("initialized gameboy successfully!"); + let mut running = true; + // Casting to ptr cause you cant pass a raw ptr (*const/mut T) to another thread + let running_ptr:usize = (&running as *const bool) as usize; - loop{ - gameboy.cycle_frame(); - } - - drop(gameboy); - release_mbc(&program_name, mbc); - }).unwrap(); + let emualation_thread = std::thread::Builder::new().name("Emualtion Thread".to_string()).spawn( + move || emulation_thread_main(args, program_name, spsc_gfx_device, running_ptr) + ).unwrap(); unsafe{ let mut event: std::mem::MaybeUninit = std::mem::MaybeUninit::uninit(); @@ -161,6 +129,60 @@ fn main() { start = SDL_GetPerformanceCounter(); } + if !c.is_empty(){ + c.pop().unwrap(); + } + drop(c); + unparker.unpark(); + drop(unparker); + + std::ptr::write_volatile(&mut running as *mut bool, false); + emualation_thread.join().unwrap(); + SDL_Quit(); } +} + +// Receiving usize and not raw ptr cause in rust you cant pass a raw ptr to another thread +fn emulation_thread_main(args: Vec, program_name: String, spsc_gfx_device: SpscGfxDevice, running_ptr: usize) { + let audio_device = sdl_audio_device::SdlAudioDevie::new(44100); + let mut devices: Vec::> = Vec::new(); + devices.push(Box::new(audio_device)); + if check_for_terminal_feature_flag(&args, "--file-audio"){ + let wav_ad = wav_file_audio_device::WavfileAudioDevice::new(44100, GB_FREQUENCY, "output.wav"); + devices.push(Box::new(wav_ad)); + log::info!("Writing audio to file: output.wav"); + } + let audio_devices = MultiAudioDevice::new(devices); + let mut mbc = initialize_mbc(&program_name); + let joypad_provider = SdlJoypadProvider::new(buttons_mapper); + let mut gameboy = match fs::read("Dependencies/Init/dmg_boot.bin"){ + Result::Ok(file)=>{ + info!("found bootrom!"); + + let mut bootrom:[u8;BOOT_ROM_SIZE] = [0;BOOT_ROM_SIZE]; + for i in 0..BOOT_ROM_SIZE{ + bootrom[i] = file[i]; + } + + GameBoy::new_with_bootrom(&mut mbc, joypad_provider,audio_devices, spsc_gfx_device, bootrom) + } + Result::Err(_)=>{ + info!("could not find bootrom... booting directly to rom"); + + GameBoy::new(&mut mbc, joypad_provider, audio_devices, spsc_gfx_device) + } + }; + info!("initialized gameboy successfully!"); + + unsafe{ + let mut running = std::ptr::read_volatile(running_ptr as *const bool); + while running{ + gameboy.cycle_frame(); + running = std::ptr::read_volatile(running_ptr as *const bool); + } + } + drop(gameboy); + release_mbc(&program_name, mbc); + log::info!("Release the gameboy succefully"); } \ No newline at end of file From 732518ac3471b180da1f8e839858e16d5a18feca Mon Sep 17 00:00:00 2001 From: alloncm Date: Fri, 24 Sep 2021 16:32:43 +0300 Subject: [PATCH 09/36] The now tests compiles after the merge The merge broke some code in the tests --- lib_gb/tests/integration_tests.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib_gb/tests/integration_tests.rs b/lib_gb/tests/integration_tests.rs index 21d9d7b5..2881a952 100644 --- a/lib_gb/tests/integration_tests.rs +++ b/lib_gb/tests/integration_tests.rs @@ -1,6 +1,7 @@ use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; use std::io::Read; +use lib_gb::ppu::gb_ppu::{SCREEN_HEIGHT, SCREEN_WIDTH}; use lib_gb::{ apu::audio_device::AudioDevice, keypad::joypad_provider::JoypadProvider, machine::{gameboy::GameBoy, mbc_initializer::initialize_mbc}, ppu::gfx_device::GfxDevice @@ -12,7 +13,7 @@ struct CheckHashGfxDevice{ found_p:*mut bool, } impl GfxDevice for CheckHashGfxDevice{ - fn swap_buffer(&self, buffer:&[u32]) { + fn swap_buffer(&mut self, buffer:&[u32; SCREEN_HEIGHT * SCREEN_WIDTH]) { let mut s = DefaultHasher::new(); buffer.hash(&mut s); let hash = s.finish(); @@ -119,7 +120,7 @@ fn calc_hash(rom_path:&str){ static mut LAST_HASH:u64 = 0; struct GetHashGfxDevice; impl GfxDevice for GetHashGfxDevice{ - fn swap_buffer(&self, buffer:&[u32]) { + fn swap_buffer(&mut self, buffer:&[u32; SCREEN_HEIGHT * SCREEN_WIDTH]) { unsafe{ if FRAMES_COUNTER < 700{ FRAMES_COUNTER += 1; From 2c73bf7a8e7d3f76d347142ff50f1f0afcaf9508 Mon Sep 17 00:00:00 2001 From: alloncm Date: Sat, 9 Oct 2021 01:43:53 +0300 Subject: [PATCH 10/36] Move from rtrb to crossbeam channel Which turn to be faster (with blocking involved. --- Cargo.lock | 18 +----------------- gb/Cargo.toml | 3 +-- gb/src/main.rs | 46 +++++++++++++--------------------------------- 3 files changed, 15 insertions(+), 52 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 132ef785..0c74f177 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -97,12 +97,6 @@ dependencies = [ "pkg-config", ] -[[package]] -name = "cache-padded" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba" - [[package]] name = "cast" version = "0.2.7" @@ -627,11 +621,10 @@ name = "magenboy" version = "1.0.0" dependencies = [ "chrono", - "crossbeam-utils", + "crossbeam-channel", "fern", "lib_gb", "log", - "rtrb", "sdl2", "wav", ] @@ -1018,15 +1011,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9b1a3d5f46d53f4a3478e2be4a5a5ce5108ea58b100dcd139830eae7f79a3a1" -[[package]] -name = "rtrb" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "318256ac02f7e11a48a10339ba5dca8bd7eb17496abf384e8ea909bb2ae5275f" -dependencies = [ - "cache-padded", -] - [[package]] name = "rustc_version" version = "0.4.0" diff --git a/gb/Cargo.toml b/gb/Cargo.toml index 32547146..595ceb6b 100644 --- a/gb/Cargo.toml +++ b/gb/Cargo.toml @@ -12,5 +12,4 @@ fern = "0.6.0" chrono = "0.4" sdl2 = {version = "0.34", features = ["bundled","static-link"]} wav = "0.6.0" -crossbeam-utils = "0.8" -rtrb = "0.1" \ No newline at end of file +crossbeam-channel = "0.5" \ No newline at end of file diff --git a/gb/src/main.rs b/gb/src/main.rs index 3705032b..39c2e1ce 100644 --- a/gb/src/main.rs +++ b/gb/src/main.rs @@ -58,19 +58,13 @@ fn check_for_terminal_feature_flag(args:&Vec::, flag:&str)->bool{ args.len() >= 3 && args.contains(&String::from(flag)) } -struct SpscGfxDevice{ - producer: rtrb::Producer<[u32;SCREEN_HEIGHT * SCREEN_WIDTH ]>, - parker: crossbeam_utils::sync::Parker, +struct MpmcGfxDevice{ + sender: crossbeam_channel::Sender<[u32;SCREEN_HEIGHT * SCREEN_WIDTH]> } -impl GfxDevice for SpscGfxDevice{ - fn swap_buffer(&mut self, buffer:&[u32; SCREEN_WIDTH * SCREEN_HEIGHT]) { - if !self.producer.is_abandoned(){ - self.producer.push(buffer.clone()).unwrap(); - if self.producer.is_full() && !self.producer.is_abandoned() { - self.parker.park(); - } - } +impl GfxDevice for MpmcGfxDevice{ + fn swap_buffer(&mut self, buffer:&[u32; SCREEN_HEIGHT * SCREEN_WIDTH]) { + self.sender.send(buffer.clone()).unwrap(); } } @@ -85,12 +79,9 @@ fn main() { } let mut sdl_gfx_device = sdl_gfx_device::SdlGfxDevice::new(SCREEN_WIDTH as u32, SCREEN_HEIGHT as u32, "MagenBoy", SCREEN_SCALE); - - let (producer, mut c) = rtrb::RingBuffer::<[u32; SCREEN_HEIGHT * SCREEN_WIDTH]>::new(1).split(); - let parker = crossbeam_utils::sync::Parker::new(); - let unparker = parker.unparker().clone(); - let spsc_gfx_device = SpscGfxDevice{producer, parker: parker}; + let (s,r) = crossbeam_channel::bounded(1); + let mpmc_device = MpmcGfxDevice{sender:s}; let program_name = args[1].clone(); @@ -99,7 +90,7 @@ fn main() { let running_ptr:usize = (&running as *const bool) as usize; let emualation_thread = std::thread::Builder::new().name("Emualtion Thread".to_string()).spawn( - move || emulation_thread_main(args, program_name, spsc_gfx_device, running_ptr) + move || emulation_thread_main(args, program_name, mpmc_device, running_ptr) ).unwrap(); unsafe{ @@ -113,12 +104,10 @@ fn main() { break; } } + + let buffer = r.recv().unwrap(); + sdl_gfx_device.swap_buffer(&buffer); - if !c.is_empty(){ - let pop = c.pop().unwrap(); - unparker.unpark(); - sdl_gfx_device.swap_buffer(&pop); - } let end = SDL_GetPerformanceCounter(); let elapsed_ms:f64 = (end - start) as f64 / SDL_GetPerformanceFrequency() as f64 * 1000.0; @@ -129,13 +118,6 @@ fn main() { start = SDL_GetPerformanceCounter(); } - if !c.is_empty(){ - c.pop().unwrap(); - } - drop(c); - unparker.unpark(); - drop(unparker); - std::ptr::write_volatile(&mut running as *mut bool, false); emualation_thread.join().unwrap(); @@ -144,7 +126,7 @@ fn main() { } // Receiving usize and not raw ptr cause in rust you cant pass a raw ptr to another thread -fn emulation_thread_main(args: Vec, program_name: String, spsc_gfx_device: SpscGfxDevice, running_ptr: usize) { +fn emulation_thread_main(args: Vec, program_name: String, spsc_gfx_device: MpmcGfxDevice, running_ptr: usize) { let audio_device = sdl_audio_device::SdlAudioDevie::new(44100); let mut devices: Vec::> = Vec::new(); devices.push(Box::new(audio_device)); @@ -176,10 +158,8 @@ fn emulation_thread_main(args: Vec, program_name: String, spsc_gfx_devic info!("initialized gameboy successfully!"); unsafe{ - let mut running = std::ptr::read_volatile(running_ptr as *const bool); - while running{ + while std::ptr::read_volatile(running_ptr as *const bool){ gameboy.cycle_frame(); - running = std::ptr::read_volatile(running_ptr as *const bool); } } drop(gameboy); From 60d5f51ea97460b317fa7d877aadce33a6a8b5bd Mon Sep 17 00:00:00 2001 From: alloncm Date: Sat, 9 Oct 2021 18:19:22 +0300 Subject: [PATCH 11/36] Reduce the apu bench loop to a sane number --- lib_gb/benches/my_bench.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib_gb/benches/my_bench.rs b/lib_gb/benches/my_bench.rs index c053881c..430a12e2 100644 --- a/lib_gb/benches/my_bench.rs +++ b/lib_gb/benches/my_bench.rs @@ -14,7 +14,7 @@ pub fn criterion_bench(c: &mut Criterion){ let mut apu = GbApu::new(StubApu{}); apu.enabled = true; apu.sweep_tone_channel.enabled = true; - for _ in 0..100000{ + for _ in 0..100{ apu.cycle(255); } })); From 95a3aa05ae031dd6046b0bd730b6fd5f1875221c Mon Sep 17 00:00:00 2001 From: alloncm Date: Sat, 9 Oct 2021 19:15:51 +0300 Subject: [PATCH 12/36] Add sample type In order to abstract the sample type primitive. --- gb/src/audio_resampler.rs | 17 ++++++++--------- gb/src/multi_device_audio.rs | 2 +- gb/src/sdl_audio_device.rs | 6 +++--- gb/src/wav_file_audio_device.rs | 4 ++-- lib_gb/benches/my_bench.rs | 2 +- lib_gb/src/apu/audio_device.rs | 11 +++++++---- lib_gb/src/apu/channel.rs | 11 ++++++----- lib_gb/src/apu/gb_apu.rs | 8 ++++---- lib_gb/src/apu/sound_terminal.rs | 6 +++--- lib_gb/tests/integration_tests.rs | 2 +- 10 files changed, 36 insertions(+), 33 deletions(-) diff --git a/gb/src/audio_resampler.rs b/gb/src/audio_resampler.rs index 0001d5e1..182d466a 100644 --- a/gb/src/audio_resampler.rs +++ b/gb/src/audio_resampler.rs @@ -1,8 +1,8 @@ -use lib_gb::apu::audio_device::Sample; +use lib_gb::apu::audio_device::{DEFAULT_SAPMPLE, Sample, StereoSample}; pub struct AudioResampler{ to_skip:u32, - sampling_buffer:Vec, + sampling_buffer:Vec, sampling_counter:u32 } @@ -20,15 +20,14 @@ impl AudioResampler{ } } - pub fn resample(&mut self, buffer:&[Sample])->Vec{ + pub fn resample(&mut self, buffer:&[StereoSample])->Vec{ let mut output = Vec::new(); for sample in buffer.into_iter(){ self.sampling_buffer.push(*sample); self.sampling_counter += 1; if self.sampling_counter == self.to_skip { - let (interpulated_left_sample, interpulated_right_sample) = Self::interpolate_sample(&self.sampling_buffer); - let interpolated_sample = Sample{left_sample: interpulated_left_sample, right_sample: interpulated_right_sample}; + let interpolated_sample = Self::interpolate_sample(&self.sampling_buffer); self.sampling_counter = 0; self.sampling_buffer.clear(); @@ -39,11 +38,11 @@ impl AudioResampler{ return output; } - fn interpolate_sample(samples:&[Sample])->(f32, f32){ + fn interpolate_sample(samples:&[StereoSample])->StereoSample{ - let interpulated_left_sample = samples.iter().fold(0.0, |acc, x| acc + x.left_sample) / samples.len() as f32; - let interpulated_right_sample = samples.iter().fold(0.0, |acc, x| acc + x.right_sample) / samples.len() as f32; + 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 (interpulated_left_sample, interpulated_right_sample); + return StereoSample{left_sample: interpulated_left_sample,right_sample: interpulated_right_sample}; } } \ No newline at end of file diff --git a/gb/src/multi_device_audio.rs b/gb/src/multi_device_audio.rs index fe169ad1..892c859b 100644 --- a/gb/src/multi_device_audio.rs +++ b/gb/src/multi_device_audio.rs @@ -11,7 +11,7 @@ impl MultiAudioDevice{ } impl AudioDevice for MultiAudioDevice{ - fn push_buffer(&mut self, buffer:&[Sample]) { + fn push_buffer(&mut self, buffer:&[StereoSample]) { for device in self.devices.iter_mut(){ device.push_buffer(buffer); } diff --git a/gb/src/sdl_audio_device.rs b/gb/src/sdl_audio_device.rs index 2346a9f4..6a78c0a4 100644 --- a/gb/src/sdl_audio_device.rs +++ b/gb/src/sdl_audio_device.rs @@ -11,7 +11,7 @@ pub struct SdlAudioDevie{ device_id: SDL_AudioDeviceID, resampler: AudioResampler, - buffer: Vec + buffer: Vec } impl SdlAudioDevie{ @@ -19,7 +19,7 @@ impl SdlAudioDevie{ let desired_audio_spec = SDL_AudioSpec{ freq: frequency, - format: AUDIO_F32SYS as u16, + format: AUDIO_S16SYS as u16, channels: 2, silence: 0, samples: BUFFER_SIZE as u16, @@ -87,7 +87,7 @@ impl SdlAudioDevie{ } impl AudioDevice for SdlAudioDevie{ - fn push_buffer(&mut self, buffer:&[Sample]){ + fn push_buffer(&mut self, buffer:&[StereoSample]){ for sample in self.resampler.resample(buffer){ self.buffer.push(sample.left_sample); diff --git a/gb/src/wav_file_audio_device.rs b/gb/src/wav_file_audio_device.rs index 713c581c..3e4a5389 100644 --- a/gb/src/wav_file_audio_device.rs +++ b/gb/src/wav_file_audio_device.rs @@ -6,7 +6,7 @@ pub struct WavfileAudioDevice{ target_frequency:u32, resampler: AudioResampler, filename:&'static str, - samples_buffer:Vec:: + samples_buffer:Vec:: } impl WavfileAudioDevice{ @@ -21,7 +21,7 @@ impl WavfileAudioDevice{ } impl AudioDevice for WavfileAudioDevice{ - fn push_buffer(&mut self, buffer:&[Sample]) { + fn push_buffer(&mut self, buffer:&[StereoSample]) { self.samples_buffer.append(self.resampler.resample(buffer).as_mut()); } } diff --git a/lib_gb/benches/my_bench.rs b/lib_gb/benches/my_bench.rs index 430a12e2..d26a5793 100644 --- a/lib_gb/benches/my_bench.rs +++ b/lib_gb/benches/my_bench.rs @@ -7,7 +7,7 @@ use lib_gb::apu::{ pub fn criterion_bench(c: &mut Criterion){ struct StubApu; impl AudioDevice for StubApu{ - fn push_buffer(&mut self, _buffer:&[Sample]){} + fn push_buffer(&mut self, _buffer:&[StereoSample]){} } c.bench_function("test apu", |b| b.iter(||{ diff --git a/lib_gb/src/apu/audio_device.rs b/lib_gb/src/apu/audio_device.rs index 76a2f988..f98036e5 100644 --- a/lib_gb/src/apu/audio_device.rs +++ b/lib_gb/src/apu/audio_device.rs @@ -1,9 +1,12 @@ +pub type Sample = f32; +pub const DEFAULT_SAPMPLE:Sample = 0 as Sample; + #[derive(Copy, Clone)] -pub struct Sample{ - pub left_sample:f32, - pub right_sample:f32 +pub struct StereoSample{ + pub left_sample:Sample, + pub right_sample:Sample } pub trait AudioDevice{ - fn push_buffer(&mut self, buffer:&[Sample]); + fn push_buffer(&mut self, buffer:&[StereoSample]); } \ No newline at end of file diff --git a/lib_gb/src/apu/channel.rs b/lib_gb/src/apu/channel.rs index 3ddc7fad..c87ff413 100644 --- a/lib_gb/src/apu/channel.rs +++ b/lib_gb/src/apu/channel.rs @@ -1,3 +1,4 @@ +use super::audio_device::{DEFAULT_SAPMPLE, Sample}; use super::sample_producer::SampleProducer; use super::timer::Timer; @@ -9,7 +10,7 @@ pub struct Channel{ pub sample_producer:Procuder, pub timer:Timer, - last_sample:f32, + last_sample:Sample, } impl Channel{ @@ -21,7 +22,7 @@ impl Channel{ length_enable:false, timer: Timer::new(sample_producer.get_updated_frequency_ticks(0)), sample_producer, - last_sample: 0.0 + last_sample: DEFAULT_SAPMPLE } } @@ -44,10 +45,10 @@ impl Channel{ self.timer.update_cycles_to_tick(self.sample_producer.get_updated_frequency_ticks(self.frequency)); self.sample_producer.reset(); - self.last_sample = 0.0; + self.last_sample = DEFAULT_SAPMPLE; } - pub fn get_audio_sample(&mut self)->f32{ + pub fn get_audio_sample(&mut self)->Sample{ if self.enabled{ let sample = if self.timer.cycle(){ @@ -64,7 +65,7 @@ impl Channel{ return self.last_sample; } - return 0.0; + return DEFAULT_SAPMPLE; } fn convert_digtial_to_analog(&self, sample:u8)->f32{ diff --git a/lib_gb/src/apu/gb_apu.rs b/lib_gb/src/apu/gb_apu.rs index 1cc35c25..7ca2f860 100644 --- a/lib_gb/src/apu/gb_apu.rs +++ b/lib_gb/src/apu/gb_apu.rs @@ -22,7 +22,7 @@ pub struct GbApu{ pub left_terminal:SoundTerminal, pub enabled:bool, - audio_buffer:[Sample;AUDIO_BUFFER_SIZE], + audio_buffer:[StereoSample;AUDIO_BUFFER_SIZE], current_t_cycle:u32, device:Device, last_enabled_state:bool @@ -36,7 +36,7 @@ impl GbApu{ wave_channel:Channel::::new(WaveSampleProducer::default()), tone_channel: Channel::::new(SquareSampleProducer::new()), noise_channel: Channel::::new(NoiseSampleProducer::default()), - audio_buffer:[Sample{left_sample:0.0, right_sample:0.0}; AUDIO_BUFFER_SIZE], + audio_buffer:[StereoSample{left_sample:DEFAULT_SAPMPLE, right_sample:DEFAULT_SAPMPLE}; AUDIO_BUFFER_SIZE], current_t_cycle:0, device:device, right_terminal: SoundTerminal::default(), @@ -56,7 +56,7 @@ impl GbApu{ let tick = self.frame_sequencer.cycle(); self.update_channels_for_frame_squencer(tick); - let mut samples:[f32;NUMBER_OF_CHANNELS] = [0.0;NUMBER_OF_CHANNELS]; + let mut samples:[Sample;NUMBER_OF_CHANNELS] = [DEFAULT_SAPMPLE ; NUMBER_OF_CHANNELS]; samples[0] = self.sweep_tone_channel.get_audio_sample(); samples[1] = self.tone_channel.get_audio_sample(); samples[2] = self.wave_channel.get_audio_sample(); @@ -75,7 +75,7 @@ impl GbApu{ } else{ for _ in 0..t_cycles{ - self.audio_buffer[self.current_t_cycle as usize] = Sample{right_sample:0.0, left_sample:0.0}; + self.audio_buffer[self.current_t_cycle as usize] = StereoSample{right_sample:DEFAULT_SAPMPLE, left_sample:DEFAULT_SAPMPLE}; self.current_t_cycle += 1; self.push_buffer_if_full(); diff --git a/lib_gb/src/apu/sound_terminal.rs b/lib_gb/src/apu/sound_terminal.rs index 454ae43b..e216d897 100644 --- a/lib_gb/src/apu/sound_terminal.rs +++ b/lib_gb/src/apu/sound_terminal.rs @@ -1,4 +1,4 @@ -use super::sound_utils::NUMBER_OF_CHANNELS; +use super::{audio_device::{DEFAULT_SAPMPLE, Sample}, sound_utils::NUMBER_OF_CHANNELS}; pub struct SoundTerminal{ pub enabled:bool, @@ -17,8 +17,8 @@ impl Default for SoundTerminal{ } impl SoundTerminal{ - pub fn mix_terminal_samples(&self, samples:&[f32;NUMBER_OF_CHANNELS])->f32{ - let mut mixed_sample:f32 = 0.0; + pub fn mix_terminal_samples(&self, samples:&[Sample;NUMBER_OF_CHANNELS])->Sample{ + let mut mixed_sample:Sample = DEFAULT_SAPMPLE; for i in 0..NUMBER_OF_CHANNELS{ if self.channels[i]{ mixed_sample += samples[i]; diff --git a/lib_gb/tests/integration_tests.rs b/lib_gb/tests/integration_tests.rs index 2881a952..7fe1eef5 100644 --- a/lib_gb/tests/integration_tests.rs +++ b/lib_gb/tests/integration_tests.rs @@ -29,7 +29,7 @@ impl GfxDevice for CheckHashGfxDevice{ struct StubAudioDevice; impl AudioDevice for StubAudioDevice{ - fn push_buffer(&mut self, _buffer:&[lib_gb::apu::audio_device::Sample]) {} + fn push_buffer(&mut self, _buffer:&[lib_gb::apu::audio_device::StereoSample]) {} } struct StubJoypadProvider; From 9ea63eade586da0a7bf47e8e0eb698922ceb2378 Mon Sep 17 00:00:00 2001 From: alloncm Date: Fri, 15 Oct 2021 20:23:57 +0300 Subject: [PATCH 13/36] Move to i16 samples This should improve performance of the sound terminal --- gb/src/sdl_audio_device.rs | 2 +- gb/src/wav_file_audio_device.rs | 4 ++-- lib_gb/benches/my_bench.rs | 32 +++++++++++++++++++++++++++++--- lib_gb/src/apu/audio_device.rs | 2 +- lib_gb/src/apu/channel.rs | 7 +------ lib_gb/src/apu/sound_terminal.rs | 11 +++++------ 6 files changed, 39 insertions(+), 19 deletions(-) diff --git a/gb/src/sdl_audio_device.rs b/gb/src/sdl_audio_device.rs index 6a78c0a4..b3288994 100644 --- a/gb/src/sdl_audio_device.rs +++ b/gb/src/sdl_audio_device.rs @@ -69,7 +69,7 @@ impl SdlAudioDevie{ } - fn push_audio_to_device(&self, audio:&[f32])->Result<(),&str>{ + fn push_audio_to_device(&self, audio:&[Sample])->Result<(),&str>{ let audio_ptr: *const c_void = audio.as_ptr() as *const c_void; let data_byte_len = (audio.len() * std::mem::size_of::()) as u32; diff --git a/gb/src/wav_file_audio_device.rs b/gb/src/wav_file_audio_device.rs index 3e4a5389..230c2ae1 100644 --- a/gb/src/wav_file_audio_device.rs +++ b/gb/src/wav_file_audio_device.rs @@ -28,14 +28,14 @@ impl AudioDevice for WavfileAudioDevice{ impl Drop for WavfileAudioDevice{ fn drop(&mut self) { - let header = wav::header::Header::new(wav::WAV_FORMAT_IEEE_FLOAT, 2, self.target_frequency, 32); + let header = wav::header::Header::new(wav::WAV_FORMAT_PCM, 2, self.target_frequency, 16); let mut floats = Vec::with_capacity(self.samples_buffer.len() * 2); for sample in self.samples_buffer.iter(){ floats.push(sample.left_sample); floats.push(sample.right_sample); } - let data = wav::BitDepth::ThirtyTwoFloat(floats); + let data = wav::BitDepth::Sixteen(floats); let mut otuput_file = std::fs::File::create(self.filename).unwrap(); wav::write(header, &data, &mut otuput_file).unwrap(); } diff --git a/lib_gb/benches/my_bench.rs b/lib_gb/benches/my_bench.rs index d26a5793..555d8c28 100644 --- a/lib_gb/benches/my_bench.rs +++ b/lib_gb/benches/my_bench.rs @@ -1,7 +1,8 @@ use criterion::*; use lib_gb::apu::{ - gb_apu::*, - audio_device::*, + audio_device::*, channel::Channel, + gb_apu::*, sound_terminal::SoundTerminal, + square_sample_producer::SquareSampleProducer }; pub fn criterion_bench(c: &mut Criterion){ @@ -20,5 +21,30 @@ pub fn criterion_bench(c: &mut Criterion){ })); } -criterion_group!(benches, criterion_bench); +pub fn apu_sweep_tone_channel(c: &mut Criterion){ + + c.bench_function("test square channel", |b|b.iter(||{ + let mut channel = Channel::::new(SquareSampleProducer::new_with_sweep()); + channel.sound_length = 63; + channel.enabled = true; + channel.length_enable = true; + while channel.enabled{ + let _ = channel.get_audio_sample(); + channel.update_length_register(); + } + })); +} + +pub fn apu_sound_terminal(c:&mut Criterion){ + let mut sound_terminal = SoundTerminal::default(); + sound_terminal.channels = [true;4]; + sound_terminal.volume = 8; + c.bench_function("Sound terminal", |b| b.iter(||{ + + let samples:[Sample;4] = [100 as Sample,200 as Sample,5 as Sample,7 as Sample]; + let _ = sound_terminal.mix_terminal_samples(&samples); + })); +} + +criterion_group!(benches, criterion_bench, apu_sweep_tone_channel, apu_sound_terminal); criterion_main!(benches); \ No newline at end of file diff --git a/lib_gb/src/apu/audio_device.rs b/lib_gb/src/apu/audio_device.rs index f98036e5..38c88d34 100644 --- a/lib_gb/src/apu/audio_device.rs +++ b/lib_gb/src/apu/audio_device.rs @@ -1,4 +1,4 @@ -pub type Sample = f32; +pub type Sample = i16; pub const DEFAULT_SAPMPLE:Sample = 0 as Sample; #[derive(Copy, Clone)] diff --git a/lib_gb/src/apu/channel.rs b/lib_gb/src/apu/channel.rs index c87ff413..9d7d6a41 100644 --- a/lib_gb/src/apu/channel.rs +++ b/lib_gb/src/apu/channel.rs @@ -53,8 +53,7 @@ impl Channel{ let sample = if self.timer.cycle(){ self.timer.update_cycles_to_tick(self.sample_producer.get_updated_frequency_ticks(self.frequency)); - let s = self.sample_producer.produce(); - self.convert_digtial_to_analog(s) + self.sample_producer.produce() as Sample } else{ self.last_sample @@ -67,8 +66,4 @@ impl Channel{ return DEFAULT_SAPMPLE; } - - fn convert_digtial_to_analog(&self, sample:u8)->f32{ - (sample as f32 / 7.5 ) - 1.0 - } } diff --git a/lib_gb/src/apu/sound_terminal.rs b/lib_gb/src/apu/sound_terminal.rs index e216d897..a009cc1f 100644 --- a/lib_gb/src/apu/sound_terminal.rs +++ b/lib_gb/src/apu/sound_terminal.rs @@ -20,13 +20,12 @@ impl SoundTerminal{ pub fn mix_terminal_samples(&self, samples:&[Sample;NUMBER_OF_CHANNELS])->Sample{ let mut mixed_sample:Sample = DEFAULT_SAPMPLE; for i in 0..NUMBER_OF_CHANNELS{ - if self.channels[i]{ - mixed_sample += samples[i]; - } + // This code should add the samples[i] only if channels[i] it true. + // After profiling this code is faster than if and since this is a hot spot in the code + // Im writing it like this. + mixed_sample += samples[i] * self.channels[i] as u8 as Sample; } - mixed_sample /= NUMBER_OF_CHANNELS as f32; - - return mixed_sample * ((self.volume + 1) as f32 / 10.0); + return mixed_sample * ((self.volume + 1) as Sample); } } \ No newline at end of file From e519d50e2823acab9d45e3424deddb33ee6f4c81 Mon Sep 17 00:00:00 2001 From: alloncm Date: Sat, 16 Oct 2021 12:12:59 +0300 Subject: [PATCH 14/36] Fix compile error Casued by merge shit --- lib_gb/src/apu/audio_device.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib_gb/src/apu/audio_device.rs b/lib_gb/src/apu/audio_device.rs index a4e684c2..63b0080e 100644 --- a/lib_gb/src/apu/audio_device.rs +++ b/lib_gb/src/apu/audio_device.rs @@ -9,7 +9,7 @@ pub struct StereoSample{ impl StereoSample{ pub const fn const_defualt()->Self{ - Self{left_sample:SAMPLE_CONSTANT_DEFAULT, right_sample:SAMPLE_CONSTANT_DEFAULT} + Self{left_sample:DEFAULT_SAPMPLE, right_sample:DEFAULT_SAPMPLE} } } From 68035793833e2a75dcc3cb140b7f0c220604ca57 Mon Sep 17 00:00:00 2001 From: alloncm Date: Sat, 16 Oct 2021 12:14:19 +0300 Subject: [PATCH 15/36] Use the const default sample --- lib_gb/src/apu/gb_apu.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib_gb/src/apu/gb_apu.rs b/lib_gb/src/apu/gb_apu.rs index 7ca2f860..e0b72c24 100644 --- a/lib_gb/src/apu/gb_apu.rs +++ b/lib_gb/src/apu/gb_apu.rs @@ -36,7 +36,7 @@ impl GbApu{ wave_channel:Channel::::new(WaveSampleProducer::default()), tone_channel: Channel::::new(SquareSampleProducer::new()), noise_channel: Channel::::new(NoiseSampleProducer::default()), - audio_buffer:[StereoSample{left_sample:DEFAULT_SAPMPLE, right_sample:DEFAULT_SAPMPLE}; AUDIO_BUFFER_SIZE], + audio_buffer:[StereoSample::const_defualt(); AUDIO_BUFFER_SIZE], current_t_cycle:0, device:device, right_terminal: SoundTerminal::default(), @@ -75,7 +75,7 @@ impl GbApu{ } else{ for _ in 0..t_cycles{ - self.audio_buffer[self.current_t_cycle as usize] = StereoSample{right_sample:DEFAULT_SAPMPLE, left_sample:DEFAULT_SAPMPLE}; + self.audio_buffer[self.current_t_cycle as usize] = StereoSample::const_defualt(); self.current_t_cycle += 1; self.push_buffer_if_full(); From fcd1eef35aa5307fa6e486ce5a344035559e26f6 Mon Sep 17 00:00:00 2001 From: alloncm Date: Sat, 16 Oct 2021 12:16:17 +0300 Subject: [PATCH 16/36] Normal the i16 samples too This is important since it can cause flickering in the sound --- lib_gb/src/apu/sound_terminal.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib_gb/src/apu/sound_terminal.rs b/lib_gb/src/apu/sound_terminal.rs index a009cc1f..326adebc 100644 --- a/lib_gb/src/apu/sound_terminal.rs +++ b/lib_gb/src/apu/sound_terminal.rs @@ -26,6 +26,8 @@ impl SoundTerminal{ mixed_sample += samples[i] * self.channels[i] as u8 as Sample; } + mixed_sample >>= 2; // Divide by 4 in order to normal the sample + return mixed_sample * ((self.volume + 1) as Sample); } } \ No newline at end of file From a143d41c91b6da17cfaa8f13dc0b5adadece50e9 Mon Sep 17 00:00:00 2001 From: alloncm Date: Sat, 16 Oct 2021 12:19:52 +0300 Subject: [PATCH 17/36] Lock the fps to the original with the audio device --- gb/src/main.rs | 12 ------------ gb/src/sdl_audio_device.rs | 4 +++- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/gb/src/main.rs b/gb/src/main.rs index 39c2e1ce..c11893c4 100644 --- a/gb/src/main.rs +++ b/gb/src/main.rs @@ -12,8 +12,6 @@ use std::{fs, env, result::Result, vec::Vec}; use log::info; use sdl2::sys::*; -const FPS:f64 = GB_FREQUENCY as f64 / 70224.0; -const FRAME_TIME_MS:f64 = (1.0 / FPS) * 1000.0; const SCREEN_SCALE:u8 = 4; fn init_logger(debug:bool)->Result<(), fern::InitError>{ @@ -95,7 +93,6 @@ fn main() { unsafe{ let mut event: std::mem::MaybeUninit = std::mem::MaybeUninit::uninit(); - let mut start:u64 = SDL_GetPerformanceCounter(); loop{ if SDL_PollEvent(event.as_mut_ptr()) != 0{ @@ -107,15 +104,6 @@ fn main() { let buffer = r.recv().unwrap(); sdl_gfx_device.swap_buffer(&buffer); - - - let end = SDL_GetPerformanceCounter(); - let elapsed_ms:f64 = (end - start) as f64 / SDL_GetPerformanceFrequency() as f64 * 1000.0; - if elapsed_ms < FRAME_TIME_MS{ - SDL_Delay((FRAME_TIME_MS - elapsed_ms).floor() as u32); - } - - start = SDL_GetPerformanceCounter(); } std::ptr::write_volatile(&mut running as *mut bool, false); diff --git a/gb/src/sdl_audio_device.rs b/gb/src/sdl_audio_device.rs index 4238232f..dac40061 100644 --- a/gb/src/sdl_audio_device.rs +++ b/gb/src/sdl_audio_device.rs @@ -74,7 +74,9 @@ impl SdlAudioDevie{ let data_byte_len = (audio.len() * std::mem::size_of::()) as u32; unsafe{ - while SDL_GetQueuedAudioSize(self.device_id) > BYTES_TO_WAIT{} + while SDL_GetQueuedAudioSize(self.device_id) > BYTES_TO_WAIT{ + SDL_Delay(1); + } SDL_ClearError(); if SDL_QueueAudio(self.device_id, audio_ptr, data_byte_len) != 0{ From 74d3caa5db14d3d65b62e82ef1de169f020233fe Mon Sep 17 00:00:00 2001 From: alloncm Date: Sun, 17 Oct 2021 00:52:39 +0300 Subject: [PATCH 18/36] Went for i16 as it grants a greater optimizations --- gb/src/wav_file_audio_device.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gb/src/wav_file_audio_device.rs b/gb/src/wav_file_audio_device.rs index 230c2ae1..25f2b00d 100644 --- a/gb/src/wav_file_audio_device.rs +++ b/gb/src/wav_file_audio_device.rs @@ -29,13 +29,13 @@ impl AudioDevice for WavfileAudioDevice{ impl Drop for WavfileAudioDevice{ fn drop(&mut self) { let header = wav::header::Header::new(wav::WAV_FORMAT_PCM, 2, self.target_frequency, 16); - let mut floats = Vec::with_capacity(self.samples_buffer.len() * 2); + let mut samples = Vec::with_capacity(self.samples_buffer.len() * 2); for sample in self.samples_buffer.iter(){ - floats.push(sample.left_sample); - floats.push(sample.right_sample); + samples.push(sample.left_sample); + samples.push(sample.right_sample); } - let data = wav::BitDepth::Sixteen(floats); + let data = wav::BitDepth::Sixteen(samples); let mut otuput_file = std::fs::File::create(self.filename).unwrap(); wav::write(header, &data, &mut otuput_file).unwrap(); } From e1f9daefde203b53500ade51c8499e2c7d0a658f Mon Sep 17 00:00:00 2001 From: alloncm Date: Sun, 17 Oct 2021 00:53:58 +0300 Subject: [PATCH 19/36] Optimize the sound terminal * Inline it, for some reason it wont be inlined automaticaly * Instead of multiplying with 1 or 0 Im anding with 0xFFFF or 0 --- lib_gb/benches/my_bench.rs | 7 ++++--- lib_gb/src/apu/apu_registers_updater.rs | 4 ++-- lib_gb/src/apu/sound_terminal.rs | 17 ++++++++++++++--- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/lib_gb/benches/my_bench.rs b/lib_gb/benches/my_bench.rs index 555d8c28..09deb6b1 100644 --- a/lib_gb/benches/my_bench.rs +++ b/lib_gb/benches/my_bench.rs @@ -37,12 +37,13 @@ pub fn apu_sweep_tone_channel(c: &mut Criterion){ pub fn apu_sound_terminal(c:&mut Criterion){ let mut sound_terminal = SoundTerminal::default(); - sound_terminal.channels = [true;4]; + for i in 0..4{ + sound_terminal.set_channel_state(i, true); + } sound_terminal.volume = 8; c.bench_function("Sound terminal", |b| b.iter(||{ - let samples:[Sample;4] = [100 as Sample,200 as Sample,5 as Sample,7 as Sample]; - let _ = sound_terminal.mix_terminal_samples(&samples); + let _ = sound_terminal.mix_terminal_samples(black_box(&samples)); })); } diff --git a/lib_gb/src/apu/apu_registers_updater.rs b/lib_gb/src/apu/apu_registers_updater.rs index bd4eb793..ac8eebfe 100644 --- a/lib_gb/src/apu/apu_registers_updater.rs +++ b/lib_gb/src/apu/apu_registers_updater.rs @@ -54,10 +54,10 @@ pub fn set_nr50(apu:&mut GbApu, nr50:u8){ pub fn set_nr51(apu:&mut GbApu, nr51:u8){ for i in 0..NUMBER_OF_CHANNELS{ - apu.right_terminal.channels[i as usize] = nr51 & (1 << i) != 0; + apu.right_terminal.set_channel_state(i, nr51 & (1 << i) != 0); } for i in 0..NUMBER_OF_CHANNELS{ - apu.left_terminal.channels[i as usize] = nr51 & (0b1_0000 << i) != 0; + apu.left_terminal.set_channel_state(i, nr51 & (0b1_0000 << i) != 0); } } diff --git a/lib_gb/src/apu/sound_terminal.rs b/lib_gb/src/apu/sound_terminal.rs index 326adebc..d0f09f3c 100644 --- a/lib_gb/src/apu/sound_terminal.rs +++ b/lib_gb/src/apu/sound_terminal.rs @@ -1,29 +1,40 @@ use super::{audio_device::{DEFAULT_SAPMPLE, Sample}, sound_utils::NUMBER_OF_CHANNELS}; +type ChannelMask = u16; + +const ENABLE_MASK:ChannelMask = 0xFFFF; +const DISABLE_MASK:ChannelMask = 0x0; + pub struct SoundTerminal{ pub enabled:bool, pub volume:u8, - pub channels:[bool;NUMBER_OF_CHANNELS] + channel_masks:[ChannelMask;NUMBER_OF_CHANNELS] } impl Default for SoundTerminal{ fn default() -> Self { SoundTerminal{ enabled:false, - channels:[false;NUMBER_OF_CHANNELS], + channel_masks:[DISABLE_MASK;NUMBER_OF_CHANNELS], volume:0 } } } impl SoundTerminal{ + pub fn set_channel_state(&mut self, channel:usize, state:bool){ + self.channel_masks[channel] = state as u16 * ENABLE_MASK; + } + + // For some reason this function is not inlined on release mode + #[inline] pub fn mix_terminal_samples(&self, samples:&[Sample;NUMBER_OF_CHANNELS])->Sample{ let mut mixed_sample:Sample = DEFAULT_SAPMPLE; for i in 0..NUMBER_OF_CHANNELS{ // This code should add the samples[i] only if channels[i] it true. // After profiling this code is faster than if and since this is a hot spot in the code // Im writing it like this. - mixed_sample += samples[i] * self.channels[i] as u8 as Sample; + mixed_sample += samples[i] & self.channel_masks[i] as Sample; } mixed_sample >>= 2; // Divide by 4 in order to normal the sample From 0ad43373dab1a3a0be7ca116232af6ea1878b75a Mon Sep 17 00:00:00 2001 From: alloncm Date: Fri, 22 Oct 2021 13:59:07 +0300 Subject: [PATCH 20/36] Update the readme Update thhe ppu and the apu dev status --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7dd2c8dd..f59b9484 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,9 @@ The main goal of this project is to be able to play Pokemon on my own emulator. ### 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: From e1d134a626c2965d0972b7f899639459523908ee Mon Sep 17 00:00:00 2001 From: alloncm Date: Fri, 22 Oct 2021 14:04:43 +0300 Subject: [PATCH 21/36] Optimize the mpmc gfx device There ware too much cloning whcih caused a BO in debug mode. Using pointer casted to usize to communicate between threads and a multi buffering in the ppu (to avoid race conditions, at least I hope) I managed to solve this issue in debug mode too! --- Cargo.lock | 4 ++-- gb/src/main.rs | 15 +++++++++------ lib_gb/src/ppu/gb_ppu.rs | 28 ++++++++++++++++++---------- 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0c74f177..4f75498b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -444,9 +444,9 @@ dependencies = [ [[package]] name = "half" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62aca2aba2d62b4a7f5b33f3712cb1b0692779a56fb510499d5c0aa594daeaf3" +checksum = "ac5956d4e63858efaec57e0d6c1c2f6a41e1487f830314a324ccd7e2223a7ca0" [[package]] name = "hashbrown" diff --git a/gb/src/main.rs b/gb/src/main.rs index c11893c4..0ce0daba 100644 --- a/gb/src/main.rs +++ b/gb/src/main.rs @@ -7,7 +7,7 @@ mod multi_device_audio; mod sdl_gfx_device; use crate::{mbc_handler::*, sdl_joypad_provider::*, multi_device_audio::*}; -use lib_gb::{GB_FREQUENCY, apu::audio_device::*, keypad::button::Button, machine::gameboy::GameBoy, mmu::gb_mmu::BOOT_ROM_SIZE, ppu::{gb_ppu::{SCREEN_HEIGHT, SCREEN_WIDTH}, gfx_device::GfxDevice}}; +use lib_gb::{GB_FREQUENCY, apu::audio_device::*, keypad::button::Button, machine::gameboy::GameBoy, mmu::gb_mmu::BOOT_ROM_SIZE, ppu::{gb_ppu::{BUFFERS_NUMBER, SCREEN_HEIGHT, SCREEN_WIDTH}, gfx_device::GfxDevice}}; use std::{fs, env, result::Result, vec::Vec}; use log::info; use sdl2::sys::*; @@ -57,12 +57,14 @@ fn check_for_terminal_feature_flag(args:&Vec::, flag:&str)->bool{ } struct MpmcGfxDevice{ - sender: crossbeam_channel::Sender<[u32;SCREEN_HEIGHT * SCREEN_WIDTH]> + sender: crossbeam_channel::Sender } impl GfxDevice for MpmcGfxDevice{ fn swap_buffer(&mut self, buffer:&[u32; SCREEN_HEIGHT * SCREEN_WIDTH]) { - self.sender.send(buffer.clone()).unwrap(); + if self.sender.send(buffer.as_ptr() as usize).is_err(){ + log::debug!("The receiver endpoint has been closed"); + } } } @@ -78,7 +80,7 @@ fn main() { let mut sdl_gfx_device = sdl_gfx_device::SdlGfxDevice::new(SCREEN_WIDTH as u32, SCREEN_HEIGHT as u32, "MagenBoy", SCREEN_SCALE); - let (s,r) = crossbeam_channel::bounded(1); + let (s,r) = crossbeam_channel::bounded(BUFFERS_NUMBER - 1); let mpmc_device = MpmcGfxDevice{sender:s}; let program_name = args[1].clone(); @@ -103,9 +105,10 @@ fn main() { } let buffer = r.recv().unwrap(); - sdl_gfx_device.swap_buffer(&buffer); + sdl_gfx_device.swap_buffer(&*(buffer as *const [u32; SCREEN_WIDTH * SCREEN_HEIGHT])); } + drop(r); std::ptr::write_volatile(&mut running as *mut bool, false); emualation_thread.join().unwrap(); @@ -152,5 +155,5 @@ fn emulation_thread_main(args: Vec, program_name: String, spsc_gfx_devic } drop(gameboy); release_mbc(&program_name, mbc); - log::info!("Release the gameboy succefully"); + log::info!("released the gameboy succefully"); } \ No newline at end of file diff --git a/lib_gb/src/ppu/gb_ppu.rs b/lib_gb/src/ppu/gb_ppu.rs index 06758648..a26679ac 100644 --- a/lib_gb/src/ppu/gb_ppu.rs +++ b/lib_gb/src/ppu/gb_ppu.rs @@ -10,6 +10,8 @@ use super::fifo::{FIFO_SIZE, sprite_fetcher::*}; pub const SCREEN_HEIGHT: usize = 144; pub const SCREEN_WIDTH: usize = 160; +pub const BUFFERS_NUMBER:usize = 2; + const OAM_ENTRY_SIZE:u16 = 4; const OAM_MEMORY_SIZE:usize = 0xA0; @@ -39,7 +41,8 @@ pub struct GbPpu{ gfx_device: GFX, t_cycles_passed:u16, - screen_buffer: [u32; SCREEN_HEIGHT * SCREEN_WIDTH], + screen_buffers: [[u32; SCREEN_HEIGHT * SCREEN_WIDTH];BUFFERS_NUMBER], + current_screen_buffer_index:usize, push_lcd_buffer:Vec, screen_buffer_index:usize, pixel_x_pos:u8, @@ -61,7 +64,8 @@ impl GbPpu{ lcd_control: 0, bg_pos: Vec2::{x:0, y:0}, window_pos: Vec2::{x:0,y:0}, - screen_buffer:[0;SCREEN_HEIGHT * SCREEN_WIDTH], + screen_buffers:[[0;SCREEN_HEIGHT * SCREEN_WIDTH];BUFFERS_NUMBER], + current_screen_buffer_index:0, bg_color_mapping:[WHITE, LIGHT_GRAY, DARK_GRAY, BLACK], obj_color_mapping0: [None, Some(LIGHT_GRAY), Some(DARK_GRAY), Some(BLACK)], obj_color_mapping1: [None, Some(LIGHT_GRAY), Some(DARK_GRAY), Some(BLACK)], @@ -85,11 +89,10 @@ impl GbPpu{ } pub fn turn_off(&mut self){ - self.screen_buffer_index = 0; self.t_cycles_passed = 0; //This is an expensive operation! - unsafe{std::ptr::write_bytes(self.screen_buffer.as_mut_ptr(), 0xFF, self.screen_buffer.len())}; - self.gfx_device.swap_buffer(&self.screen_buffer); + unsafe{std::ptr::write_bytes(self.screen_buffers[self.current_screen_buffer_index].as_mut_ptr(), 0xFF, SCREEN_HEIGHT * SCREEN_WIDTH)}; + self.swap_buffer(); self.state = PpuState::Hblank; self.ly_register = 0; self.stat_triggered = false; @@ -114,18 +117,23 @@ impl GbPpu{ self.update_stat_register(if_register); - for pixel in self.push_lcd_buffer.iter(){ - self.screen_buffer[self.screen_buffer_index] = u32::from(*pixel); + for i in 0..self.push_lcd_buffer.len(){ + self.screen_buffers[self.current_screen_buffer_index][self.screen_buffer_index] = u32::from(self.push_lcd_buffer[i]); self.screen_buffer_index += 1; - if self.screen_buffer_index == self.screen_buffer.len(){ - self.gfx_device.swap_buffer(&self.screen_buffer); - self.screen_buffer_index = 0; + if self.screen_buffer_index == SCREEN_WIDTH * SCREEN_HEIGHT{ + self.swap_buffer(); } } self.push_lcd_buffer.clear(); } + fn swap_buffer(&mut self){ + self.gfx_device.swap_buffer(&self.screen_buffers[self.current_screen_buffer_index]); + self.screen_buffer_index = 0; + self.current_screen_buffer_index = (self.current_screen_buffer_index + 1) % BUFFERS_NUMBER; + } + fn update_stat_register(&mut self, if_register: &mut u8) { self.stat_register &= 0b1111_1100; self.stat_register |= self.state as u8; From 33b13a385b87c768a278b0dd493cfa908a109c65 Mon Sep 17 00:00:00 2001 From: alloncm Date: Fri, 22 Oct 2021 18:16:49 +0300 Subject: [PATCH 22/36] Optimize the APU Instead of cycling every t_cycle the apu now cycle in m_cycle increasing its performance by almost 4! This will hust emulation accuracy but I think I can live with this minor hit :D --- gb/src/audio_resampler.rs | 5 ++-- gb/src/sdl_audio_device.rs | 5 ++-- lib_gb/benches/my_bench.rs | 2 +- lib_gb/src/apu/apu_registers_updater.rs | 6 ++++ lib_gb/src/apu/gb_apu.rs | 39 ++++++++++++------------- lib_gb/src/apu/timer.rs | 5 ++-- lib_gb/src/utils/mod.rs | 2 +- 7 files changed, 36 insertions(+), 28 deletions(-) diff --git a/gb/src/audio_resampler.rs b/gb/src/audio_resampler.rs index 182d466a..257dc6b4 100644 --- a/gb/src/audio_resampler.rs +++ b/gb/src/audio_resampler.rs @@ -8,7 +8,8 @@ pub struct AudioResampler{ impl AudioResampler{ pub fn new(original_frequency:u32, target_frequency:u32)->Self{ - let to_skip = original_frequency / target_frequency as u32; + // Calling round in order to get the nearest integer and resample as precise as possible + let to_skip = (original_frequency as f32 / target_frequency as f32).round() as u32; if to_skip == 0{ std::panic!("target freqency is too high: {}", target_frequency); } @@ -43,6 +44,6 @@ impl AudioResampler{ 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}; + return StereoSample{left_sample: interpulated_left_sample, right_sample: interpulated_right_sample}; } } \ No newline at end of file diff --git a/gb/src/sdl_audio_device.rs b/gb/src/sdl_audio_device.rs index dac40061..4bd02189 100644 --- a/gb/src/sdl_audio_device.rs +++ b/gb/src/sdl_audio_device.rs @@ -6,6 +6,7 @@ use crate::audio_resampler::AudioResampler; //After twicking those numbers Iv reached this, this will affect fps which will affect sound tearing const BUFFER_SIZE:usize = 1024 * 2; const BYTES_TO_WAIT:u32 = BUFFER_SIZE as u32 * 8; +const VOLUME:Sample = 10 as Sample; pub struct SdlAudioDevie{ device_id: SDL_AudioDeviceID, @@ -92,8 +93,8 @@ impl AudioDevice for SdlAudioDevie{ fn push_buffer(&mut self, buffer:&[StereoSample]){ for sample in self.resampler.resample(buffer){ - self.buffer.push(sample.left_sample); - self.buffer.push(sample.right_sample); + self.buffer.push(sample.left_sample * VOLUME); + self.buffer.push(sample.right_sample * VOLUME); if self.buffer.len() == BUFFER_SIZE{ self.push_audio_to_device(&self.buffer).unwrap(); diff --git a/lib_gb/benches/my_bench.rs b/lib_gb/benches/my_bench.rs index 09deb6b1..5937c401 100644 --- a/lib_gb/benches/my_bench.rs +++ b/lib_gb/benches/my_bench.rs @@ -16,7 +16,7 @@ pub fn criterion_bench(c: &mut Criterion){ apu.enabled = true; apu.sweep_tone_channel.enabled = true; for _ in 0..100{ - apu.cycle(255); + apu.cycle(10); } })); } diff --git a/lib_gb/src/apu/apu_registers_updater.rs b/lib_gb/src/apu/apu_registers_updater.rs index ac8eebfe..1112863e 100644 --- a/lib_gb/src/apu/apu_registers_updater.rs +++ b/lib_gb/src/apu/apu_registers_updater.rs @@ -62,8 +62,14 @@ pub fn set_nr51(apu:&mut GbApu, nr51:u8){ } pub fn set_nr52(apu:&mut GbApu, ports:&mut [u8;IO_PORTS_SIZE], nr52:u8){ + let prev_apu_state = apu.enabled; apu.enabled = nr52 & BIT_7_MASK != 0; + // Apu turned off + if !apu.enabled && prev_apu_state{ + apu.reset(); + } + for i in NR10_REGISTER_INDEX..NR52_REGISTER_INDEX{ ports[i as usize] = 0; } diff --git a/lib_gb/src/apu/gb_apu.rs b/lib_gb/src/apu/gb_apu.rs index e0b72c24..1456e76b 100644 --- a/lib_gb/src/apu/gb_apu.rs +++ b/lib_gb/src/apu/gb_apu.rs @@ -23,7 +23,7 @@ pub struct GbApu{ pub enabled:bool, audio_buffer:[StereoSample;AUDIO_BUFFER_SIZE], - current_t_cycle:u32, + current_m_cycle:u32, device:Device, last_enabled_state:bool } @@ -37,7 +37,7 @@ impl GbApu{ tone_channel: Channel::::new(SquareSampleProducer::new()), noise_channel: Channel::::new(NoiseSampleProducer::default()), audio_buffer:[StereoSample::const_defualt(); AUDIO_BUFFER_SIZE], - current_t_cycle:0, + current_m_cycle:0, device:device, right_terminal: SoundTerminal::default(), left_terminal: SoundTerminal::default(), @@ -47,11 +47,8 @@ impl GbApu{ } pub fn cycle(&mut self, m_cycles_passed:u8){ - //converting m_cycles to t_cycles - let t_cycles = m_cycles_passed * 4; - if self.enabled{ - for _ in 0..t_cycles{ + for _ in 0..m_cycles_passed{ let tick = self.frame_sequencer.cycle(); self.update_channels_for_frame_squencer(tick); @@ -65,35 +62,37 @@ impl GbApu{ let left_sample = self.left_terminal.mix_terminal_samples(&samples); let right_sample = self.right_terminal.mix_terminal_samples(&samples); - self.audio_buffer[self.current_t_cycle as usize].left_sample = left_sample; - self.audio_buffer[self.current_t_cycle as usize].right_sample = right_sample; + self.audio_buffer[self.current_m_cycle as usize].left_sample = left_sample; + self.audio_buffer[self.current_m_cycle as usize].right_sample = right_sample; - self.current_t_cycle += 1; + self.current_m_cycle += 1; self.push_buffer_if_full(); } } else{ - for _ in 0..t_cycles{ - self.audio_buffer[self.current_t_cycle as usize] = StereoSample::const_defualt(); - self.current_t_cycle += 1; + for _ in 0..m_cycles_passed{ + self.audio_buffer[self.current_m_cycle as usize] = StereoSample::const_defualt(); + self.current_m_cycle += 1; self.push_buffer_if_full(); } - - self.tone_channel.reset(); - self.sweep_tone_channel.reset(); - self.wave_channel.reset(); - self.noise_channel.reset(); - self.frame_sequencer.reset(); } self.last_enabled_state = self.enabled; } + pub fn reset(&mut self){ + self.tone_channel.reset(); + self.sweep_tone_channel.reset(); + self.wave_channel.reset(); + self.noise_channel.reset(); + self.frame_sequencer.reset(); + } + fn push_buffer_if_full(&mut self){ - if self.current_t_cycle as usize >= AUDIO_BUFFER_SIZE{ - self.current_t_cycle = 0; + if self.current_m_cycle as usize >= AUDIO_BUFFER_SIZE{ + self.current_m_cycle = 0; self.device.push_buffer(&self.audio_buffer); } } diff --git a/lib_gb/src/apu/timer.rs b/lib_gb/src/apu/timer.rs index a0c0738b..ea7d3b67 100644 --- a/lib_gb/src/apu/timer.rs +++ b/lib_gb/src/apu/timer.rs @@ -3,11 +3,12 @@ pub struct Timer{ cycle_counter:u16 } +// By deviding by 4 (shifting right 2) Im losing precison in favor of performance impl Timer{ pub fn new(cycles_to_tick:u16)->Self{ Timer{ cycle_counter:0, - cycles_to_tick:cycles_to_tick + cycles_to_tick:cycles_to_tick >> 2 } } @@ -26,7 +27,7 @@ impl Timer{ } pub fn update_cycles_to_tick(&mut self, cycles_to_tick:u16){ - self.cycles_to_tick = cycles_to_tick; + self.cycles_to_tick = cycles_to_tick >> 2; self.cycle_counter = 0; } } \ No newline at end of file diff --git a/lib_gb/src/utils/mod.rs b/lib_gb/src/utils/mod.rs index 0dc5325e..7c0f5cd0 100644 --- a/lib_gb/src/utils/mod.rs +++ b/lib_gb/src/utils/mod.rs @@ -5,7 +5,7 @@ pub mod memory_registers; pub mod bit_masks; pub mod fixed_size_queue; -pub const GB_FREQUENCY:u32 = 4_194_304; +pub const GB_FREQUENCY:u32 = 4_194_304 / 4; pub fn create_default_array()->[T;SIZE]{ create_array(||T::default()) From d6e735016577f5e19c7cffe2f6044ed3132c69b8 Mon Sep 17 00:00:00 2001 From: alloncm Date: Sat, 23 Oct 2021 19:19:31 +0300 Subject: [PATCH 23/36] Add frame cap + turbo mode Add frame cap using SDL_Delay. Also add the option to configure the emulation speed (both ppu and apu) --- Cargo.lock | 26 +++++++++++++------------- gb/Cargo.toml | 6 +++++- gb/src/main.rs | 5 +++-- gb/src/sdl_audio_device.rs | 6 +++--- gb/src/sdl_gfx_device.rs | 27 +++++++++++++++++++++++++-- lib_gb/Cargo.toml | 1 + 6 files changed, 50 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4f75498b..ee7c6a38 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -412,6 +412,19 @@ dependencies = [ "slab", ] +[[package]] +name = "gb" +version = "1.0.0" +dependencies = [ + "chrono", + "crossbeam-channel", + "fern", + "lib_gb", + "log", + "sdl2", + "wav", +] + [[package]] name = "getrandom" version = "0.2.3" @@ -616,19 +629,6 @@ dependencies = [ "cfg-if 1.0.0", ] -[[package]] -name = "magenboy" -version = "1.0.0" -dependencies = [ - "chrono", - "crossbeam-channel", - "fern", - "lib_gb", - "log", - "sdl2", - "wav", -] - [[package]] name = "matches" version = "0.1.9" diff --git a/gb/Cargo.toml b/gb/Cargo.toml index 595ceb6b..e6ebe490 100644 --- a/gb/Cargo.toml +++ b/gb/Cargo.toml @@ -1,10 +1,14 @@ [package] -name = "magenboy" +name = "gb" version = "1.0.0" authors = ["alloncm "] edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[[bin]] +name = "magen_boy" +path = "src/main.rs" + [dependencies] lib_gb = {path = "../lib_gb/"} log = "0.4" diff --git a/gb/src/main.rs b/gb/src/main.rs index 0ce0daba..0ffd3b31 100644 --- a/gb/src/main.rs +++ b/gb/src/main.rs @@ -13,6 +13,7 @@ use log::info; use sdl2::sys::*; const SCREEN_SCALE:u8 = 4; +const TURBO_MUL:u8 = 2; fn init_logger(debug:bool)->Result<(), fern::InitError>{ let level = if debug {log::LevelFilter::Debug} else {log::LevelFilter::Info}; @@ -78,7 +79,7 @@ fn main() { Result::Err(error)=>std::panic!("error initing logger: {}", error) } - let mut sdl_gfx_device = sdl_gfx_device::SdlGfxDevice::new(SCREEN_WIDTH as u32, SCREEN_HEIGHT as u32, "MagenBoy", SCREEN_SCALE); + let mut sdl_gfx_device = sdl_gfx_device::SdlGfxDevice::new(SCREEN_WIDTH as u32, SCREEN_HEIGHT as u32, "MagenBoy", SCREEN_SCALE, TURBO_MUL); let (s,r) = crossbeam_channel::bounded(BUFFERS_NUMBER - 1); let mpmc_device = MpmcGfxDevice{sender:s}; @@ -118,7 +119,7 @@ fn main() { // Receiving usize and not raw ptr cause in rust you cant pass a raw ptr to another thread fn emulation_thread_main(args: Vec, program_name: String, spsc_gfx_device: MpmcGfxDevice, running_ptr: usize) { - let audio_device = sdl_audio_device::SdlAudioDevie::new(44100); + let audio_device = sdl_audio_device::SdlAudioDevie::new(44100, TURBO_MUL); let mut devices: Vec::> = Vec::new(); devices.push(Box::new(audio_device)); if check_for_terminal_feature_flag(&args, "--file-audio"){ diff --git a/gb/src/sdl_audio_device.rs b/gb/src/sdl_audio_device.rs index 4bd02189..4fe0607f 100644 --- a/gb/src/sdl_audio_device.rs +++ b/gb/src/sdl_audio_device.rs @@ -5,7 +5,7 @@ use crate::audio_resampler::AudioResampler; //After twicking those numbers Iv reached this, this will affect fps which will affect sound tearing const BUFFER_SIZE:usize = 1024 * 2; -const BYTES_TO_WAIT:u32 = BUFFER_SIZE as u32 * 8; +const BYTES_TO_WAIT:u32 = BUFFER_SIZE as u32 * 16; const VOLUME:Sample = 10 as Sample; pub struct SdlAudioDevie{ @@ -16,7 +16,7 @@ pub struct SdlAudioDevie{ } impl SdlAudioDevie{ - pub fn new(frequency:i32)->Self{ + pub fn new(frequency:i32, turbo_mul:u8)->Self{ let desired_audio_spec = SDL_AudioSpec{ freq: frequency, @@ -57,7 +57,7 @@ impl SdlAudioDevie{ return SdlAudioDevie{ device_id: device_id, buffer:Vec::with_capacity(BUFFER_SIZE), - resampler: AudioResampler::new(GB_FREQUENCY, frequency as u32) + resampler: AudioResampler::new(GB_FREQUENCY * turbo_mul as u32, frequency as u32) }; } diff --git a/gb/src/sdl_gfx_device.rs b/gb/src/sdl_gfx_device.rs index a689b182..dee2a51a 100644 --- a/gb/src/sdl_gfx_device.rs +++ b/gb/src/sdl_gfx_device.rs @@ -10,10 +10,14 @@ pub struct SdlGfxDevice{ width:u32, height:u32, sacle:u8, + frame_start_time:u64, + frame_time_ms:f64, + discard:u8, + turbo_mul:u8, } impl SdlGfxDevice{ - pub fn new(buffer_width:u32, buffer_height:u32, window_name:&str, screen_scale: u8)->Self{ + pub fn new(buffer_width:u32, buffer_height:u32, window_name:&str, screen_scale: u8, turbo_mul:u8)->Self{ let cs_wnd_name = CString::new(window_name).unwrap(); let (_window, renderer, texture): (*mut SDL_Window, *mut SDL_Renderer, *mut SDL_Texture) = unsafe{ @@ -38,7 +42,11 @@ impl SdlGfxDevice{ texture, height:buffer_height, width:buffer_width, - sacle:screen_scale + sacle:screen_scale, + frame_start_time: unsafe{SDL_GetPerformanceCounter()}, + frame_time_ms: (1.0/(60.0 as f64)) * 1_000.0, + discard:0, + turbo_mul } } @@ -61,6 +69,13 @@ impl SdlGfxDevice{ impl GfxDevice for SdlGfxDevice{ fn swap_buffer(&mut self, buffer:&[u32; SCREEN_HEIGHT * SCREEN_WIDTH]) { + if self.turbo_mul > 1{ + self.discard = (self.discard + 1) % self.turbo_mul; + if self.discard == 0{ + return; + } + } + unsafe{ let extended_buffer = Self::extend_vec(buffer, self.sacle as usize, self.width as usize, self.height as usize); @@ -73,6 +88,14 @@ impl GfxDevice for SdlGfxDevice{ //There is no need to call SDL_RenderClear since im replacing the whole buffer SDL_RenderCopy(self.renderer, self.texture, std::ptr::null(), std::ptr::null()); SDL_RenderPresent(self.renderer); + + let frame_end_time = SDL_GetPerformanceCounter(); + let elapsed = ((frame_end_time - self.frame_start_time) as f64 / (SDL_GetPerformanceFrequency() as f64)) * 1_000.0; + if elapsed < self.frame_time_ms{ + SDL_Delay(((self.frame_time_ms - elapsed).floor()) as Uint32); + } + + self.frame_start_time = SDL_GetPerformanceCounter(); } } } \ No newline at end of file diff --git a/lib_gb/Cargo.toml b/lib_gb/Cargo.toml index 44d6fcac..9ba19354 100644 --- a/lib_gb/Cargo.toml +++ b/lib_gb/Cargo.toml @@ -3,6 +3,7 @@ name = "lib_gb" version = "1.0.0" authors = ["alloncm "] edition = "2018" + [dependencies] log = "0.4" From a235e9604039beec99ec1ebc89a3d16cb95b14a8 Mon Sep 17 00:00:00 2001 From: alloncm Date: Wed, 27 Oct 2021 22:54:50 +0300 Subject: [PATCH 24/36] Fix some gfx device typos --- gb/src/main.rs | 2 +- gb/src/sdl_gfx_device.rs | 12 +++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/gb/src/main.rs b/gb/src/main.rs index 0ffd3b31..66a48d55 100644 --- a/gb/src/main.rs +++ b/gb/src/main.rs @@ -13,7 +13,7 @@ use log::info; use sdl2::sys::*; const SCREEN_SCALE:u8 = 4; -const TURBO_MUL:u8 = 2; +const TURBO_MUL:u8 = 1; fn init_logger(debug:bool)->Result<(), fern::InitError>{ let level = if debug {log::LevelFilter::Debug} else {log::LevelFilter::Info}; diff --git a/gb/src/sdl_gfx_device.rs b/gb/src/sdl_gfx_device.rs index dee2a51a..2990f58e 100644 --- a/gb/src/sdl_gfx_device.rs +++ b/gb/src/sdl_gfx_device.rs @@ -25,13 +25,13 @@ impl SdlGfxDevice{ let wind:*mut SDL_Window = SDL_CreateWindow( cs_wnd_name.as_ptr(), SDL_WINDOWPOS_UNDEFINED_MASK as i32, SDL_WINDOWPOS_UNDEFINED_MASK as i32, - buffer_width as i32 * 4, buffer_height as i32 * 4, 0); + buffer_width as i32 * screen_scale as i32, buffer_height as i32 * screen_scale as i32, 0); let rend: *mut SDL_Renderer = SDL_CreateRenderer(wind, -1, 0); let tex: *mut SDL_Texture = SDL_CreateTexture(rend, SDL_PixelFormatEnum::SDL_PIXELFORMAT_ARGB8888 as u32, SDL_TextureAccess::SDL_TEXTUREACCESS_STREAMING as i32, - buffer_width as i32 * 4, buffer_height as i32 * 4); + buffer_width as i32 * screen_scale as i32, buffer_height as i32 * screen_scale as i32); (wind, rend, tex) }; @@ -69,11 +69,9 @@ impl SdlGfxDevice{ impl GfxDevice for SdlGfxDevice{ fn swap_buffer(&mut self, buffer:&[u32; SCREEN_HEIGHT * SCREEN_WIDTH]) { - if self.turbo_mul > 1{ - self.discard = (self.discard + 1) % self.turbo_mul; - if self.discard == 0{ - return; - } + self.discard = (self.discard + 1) % self.turbo_mul; + if self.discard != 0{ + return; } unsafe{ From 19969eb1cf95bc8adcc579526803ed807bec2dc8 Mon Sep 17 00:00:00 2001 From: alloncm Date: Thu, 28 Oct 2021 01:36:35 +0300 Subject: [PATCH 25/36] In the middle of testing sdl2 pull api --- gb/src/audio_resampler.rs | 2 +- gb/src/main.rs | 2 +- gb/src/sdl_audio_device.rs | 102 +++++++++++++++++++++++++-------- lib_gb/src/apu/audio_device.rs | 7 ++- lib_gb/src/apu/gb_apu.rs | 2 +- 5 files changed, 86 insertions(+), 29 deletions(-) diff --git a/gb/src/audio_resampler.rs b/gb/src/audio_resampler.rs index 257dc6b4..f8582b7c 100644 --- a/gb/src/audio_resampler.rs +++ b/gb/src/audio_resampler.rs @@ -24,7 +24,7 @@ impl AudioResampler{ pub fn resample(&mut self, buffer:&[StereoSample])->Vec{ let mut output = Vec::new(); for sample in buffer.into_iter(){ - self.sampling_buffer.push(*sample); + self.sampling_buffer.push(sample.clone()); self.sampling_counter += 1; if self.sampling_counter == self.to_skip { diff --git a/gb/src/main.rs b/gb/src/main.rs index 66a48d55..a41038ce 100644 --- a/gb/src/main.rs +++ b/gb/src/main.rs @@ -12,7 +12,7 @@ use std::{fs, env, result::Result, vec::Vec}; use log::info; use sdl2::sys::*; -const SCREEN_SCALE:u8 = 4; +const SCREEN_SCALE:u8 = 1; const TURBO_MUL:u8 = 1; fn init_logger(debug:bool)->Result<(), fern::InitError>{ diff --git a/gb/src/sdl_audio_device.rs b/gb/src/sdl_audio_device.rs index 4fe0607f..028d2d05 100644 --- a/gb/src/sdl_audio_device.rs +++ b/gb/src/sdl_audio_device.rs @@ -1,23 +1,44 @@ -use std::{vec::Vec,mem::MaybeUninit,ffi::{CStr, c_void}}; +use std::{ffi::{CStr, c_void}, mem::MaybeUninit}; use lib_gb::{GB_FREQUENCY, apu::audio_device::*}; use sdl2::{sys::*,libc::c_char}; use crate::audio_resampler::AudioResampler; +use crossbeam_channel::{Receiver, Sender, bounded}; + //After twicking those numbers Iv reached this, this will affect fps which will affect sound tearing const BUFFER_SIZE:usize = 1024 * 2; const BYTES_TO_WAIT:u32 = BUFFER_SIZE as u32 * 16; const VOLUME:Sample = 10 as Sample; + +struct Data{ + pub rx: Receiver<[Sample;BUFFER_SIZE]>, + pub current_buf: Option<[Sample;BUFFER_SIZE]>, + pub current_buf_index:usize, +} pub struct SdlAudioDevie{ device_id: SDL_AudioDeviceID, resampler: AudioResampler, - buffer: Vec + buffer: [Sample;BUFFER_SIZE], + buffer_index:usize, + + tx: Sender<[Sample;BUFFER_SIZE]>, } impl SdlAudioDevie{ pub fn new(frequency:i32, turbo_mul:u8)->Self{ + let(s,r) = bounded(3); + let boxed_data = Box::new(Data{ + current_buf:Option::None, + current_buf_index:0, + rx:r + }); + + let leaked_data = Box::leak(boxed_data); + + let desired_audio_spec = SDL_AudioSpec{ freq: frequency, format: AUDIO_S16SYS as u16, @@ -26,8 +47,8 @@ impl SdlAudioDevie{ samples: BUFFER_SIZE as u16, padding: 0, size: 0, - callback: Option::None, - userdata: std::ptr::null_mut() + callback: Option::Some(audio_callback), + userdata: leaked_data as *mut Data as *mut c_void }; @@ -53,11 +74,12 @@ impl SdlAudioDevie{ id }; - return SdlAudioDevie{ device_id: device_id, - buffer:Vec::with_capacity(BUFFER_SIZE), - resampler: AudioResampler::new(GB_FREQUENCY * turbo_mul as u32, frequency as u32) + buffer:[DEFAULT_SAPMPLE;BUFFER_SIZE], + buffer_index:0, + resampler: AudioResampler::new(GB_FREQUENCY * turbo_mul as u32, frequency as u32), + tx:s, }; } @@ -70,22 +92,52 @@ impl SdlAudioDevie{ } - fn push_audio_to_device(&self, audio:&[Sample])->Result<(),&str>{ - let audio_ptr: *const c_void = audio.as_ptr() as *const c_void; - let data_byte_len = (audio.len() * std::mem::size_of::()) as u32; + fn push_audio_to_device(&self, audio:&[Sample; BUFFER_SIZE])->Result<(),&str>{ + // let audio_ptr: *const c_void = audio.as_ptr() as *const c_void; + // let data_byte_len = (audio.len() * std::mem::size_of::()) as u32; - unsafe{ - while SDL_GetQueuedAudioSize(self.device_id) > BYTES_TO_WAIT{ - SDL_Delay(1); - } + // unsafe{ + // while SDL_GetQueuedAudioSize(self.device_id) > BYTES_TO_WAIT{ + // SDL_Delay(1); + // } - SDL_ClearError(); - if SDL_QueueAudio(self.device_id, audio_ptr, data_byte_len) != 0{ - return Err(Self::get_sdl_error_message()); - } + // SDL_ClearError(); + // if SDL_QueueAudio(self.device_id, audio_ptr, data_byte_len) != 0{ + // return Err(Self::get_sdl_error_message()); + // } - Ok(()) - } + // Ok(()) + // } + self.tx.send(audio.clone()).unwrap(); + Ok(()) + } +} + +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); + + 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::()) - 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; } } @@ -93,12 +145,12 @@ impl AudioDevice for SdlAudioDevie{ fn push_buffer(&mut self, buffer:&[StereoSample]){ for sample in self.resampler.resample(buffer){ - self.buffer.push(sample.left_sample * VOLUME); - self.buffer.push(sample.right_sample * VOLUME); - - if self.buffer.len() == BUFFER_SIZE{ + self.buffer[self.buffer_index] = sample.left_sample * VOLUME; + self.buffer[self.buffer_index + 1] = sample.right_sample * VOLUME; + self.buffer_index += 2; + if self.buffer_index == BUFFER_SIZE{ self.push_audio_to_device(&self.buffer).unwrap(); - self.buffer.clear(); + self.buffer_index = 0; } } } diff --git a/lib_gb/src/apu/audio_device.rs b/lib_gb/src/apu/audio_device.rs index 63b0080e..23474dcf 100644 --- a/lib_gb/src/apu/audio_device.rs +++ b/lib_gb/src/apu/audio_device.rs @@ -1,7 +1,6 @@ pub type Sample = i16; pub const DEFAULT_SAPMPLE:Sample = 0 as Sample; -#[derive(Copy, Clone)] pub struct StereoSample{ pub left_sample:Sample, pub right_sample:Sample @@ -13,6 +12,12 @@ impl StereoSample{ } } +impl Clone for StereoSample{ + fn clone(&self) -> Self { + Self{left_sample:self.left_sample,right_sample:self.right_sample} + } +} + pub trait AudioDevice{ fn push_buffer(&mut self, buffer:&[StereoSample]); } \ No newline at end of file diff --git a/lib_gb/src/apu/gb_apu.rs b/lib_gb/src/apu/gb_apu.rs index 1456e76b..a090f5b5 100644 --- a/lib_gb/src/apu/gb_apu.rs +++ b/lib_gb/src/apu/gb_apu.rs @@ -36,7 +36,7 @@ impl GbApu{ wave_channel:Channel::::new(WaveSampleProducer::default()), tone_channel: Channel::::new(SquareSampleProducer::new()), noise_channel: Channel::::new(NoiseSampleProducer::default()), - audio_buffer:[StereoSample::const_defualt(); AUDIO_BUFFER_SIZE], + audio_buffer:crate::utils::create_array(StereoSample::const_defualt), current_m_cycle:0, device:device, right_terminal: SoundTerminal::default(), From dd31bf6d3e30b03f384689981f7e3a56adc2c67a Mon Sep 17 00:00:00 2001 From: alloncm Date: Fri, 29 Oct 2021 22:29:51 +0300 Subject: [PATCH 26/36] Still in the middle --- gb/Cargo.toml | 7 ++++- gb/src/audio_resampler.rs | 45 ++++++++++++++++++++++----- gb/src/main.rs | 7 +++-- gb/src/multi_device_audio.rs | 2 +- gb/src/sdl_audio_device.rs | 11 +++++-- gb/src/sdl_audio_resampler.rs | 51 +++++++++++++++++++++++++++++++ gb/src/sdl_gfx_device.rs | 2 +- gb/src/wav_file_audio_device.rs | 5 ++- lib_gb/benches/my_bench.rs | 2 +- lib_gb/src/apu/audio_device.rs | 5 ++- lib_gb/src/apu/gb_apu.rs | 6 ++-- lib_gb/tests/integration_tests.rs | 3 +- 12 files changed, 122 insertions(+), 24 deletions(-) create mode 100644 gb/src/sdl_audio_resampler.rs diff --git a/gb/Cargo.toml b/gb/Cargo.toml index e6ebe490..22556d13 100644 --- a/gb/Cargo.toml +++ b/gb/Cargo.toml @@ -16,4 +16,9 @@ fern = "0.6.0" chrono = "0.4" sdl2 = {version = "0.34", features = ["bundled","static-link"]} wav = "0.6.0" -crossbeam-channel = "0.5" \ No newline at end of file +crossbeam-channel = "0.5" + +[features] +default = ["sdl-resample"] +sdl-resample = [] +push-audio = [] \ No newline at end of file diff --git a/gb/src/audio_resampler.rs b/gb/src/audio_resampler.rs index f8582b7c..2527dcf8 100644 --- a/gb/src/audio_resampler.rs +++ b/gb/src/audio_resampler.rs @@ -1,42 +1,71 @@ -use lib_gb::apu::audio_device::{DEFAULT_SAPMPLE, Sample, StereoSample}; +use lib_gb::apu::audio_device::{BUFFER_SIZE, DEFAULT_SAPMPLE, Sample, StereoSample}; pub struct AudioResampler{ to_skip:u32, sampling_buffer:Vec, - sampling_counter:u32 + sampling_counter:u32, + reminder_steps:f32, + reminder_counter:f32, + alternate_to_skip:u32, + skip_to_use:u32, } impl AudioResampler{ pub 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 to_skip = (original_frequency as f32 / target_frequency as f32).round() as u32; - if to_skip == 0{ + 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); } AudioResampler{ to_skip:to_skip, - sampling_buffer:Vec::with_capacity(to_skip as usize), - sampling_counter: 0 + 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 } } - pub fn resample(&mut self, buffer:&[StereoSample])->Vec{ + pub fn resample(&mut self, buffer:&[StereoSample; BUFFER_SIZE])->Vec{ 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.to_skip { + 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; + } fn interpolate_sample(samples:&[StereoSample])->StereoSample{ diff --git a/gb/src/main.rs b/gb/src/main.rs index a41038ce..c02431e6 100644 --- a/gb/src/main.rs +++ b/gb/src/main.rs @@ -1,7 +1,10 @@ mod mbc_handler; mod sdl_joypad_provider; mod sdl_audio_device; +#[cfg(not(feature = "sdl-resample"))] mod audio_resampler; +#[cfg(feature = "sdl-resample")] +mod sdl_audio_resampler; mod wav_file_audio_device; mod multi_device_audio; mod sdl_gfx_device; @@ -12,8 +15,8 @@ use std::{fs, env, result::Result, vec::Vec}; use log::info; use sdl2::sys::*; -const SCREEN_SCALE:u8 = 1; -const TURBO_MUL:u8 = 1; +const SCREEN_SCALE:u8 = 4; +const TURBO_MUL:u8 = 2; fn init_logger(debug:bool)->Result<(), fern::InitError>{ let level = if debug {log::LevelFilter::Debug} else {log::LevelFilter::Info}; diff --git a/gb/src/multi_device_audio.rs b/gb/src/multi_device_audio.rs index 892c859b..e90ba597 100644 --- a/gb/src/multi_device_audio.rs +++ b/gb/src/multi_device_audio.rs @@ -11,7 +11,7 @@ impl MultiAudioDevice{ } impl AudioDevice for MultiAudioDevice{ - fn push_buffer(&mut self, buffer:&[StereoSample]) { + fn push_buffer(&mut self, buffer:&[StereoSample; BUFFER_SIZE]) { for device in self.devices.iter_mut(){ device.push_buffer(buffer); } diff --git a/gb/src/sdl_audio_device.rs b/gb/src/sdl_audio_device.rs index 028d2d05..a978920d 100644 --- a/gb/src/sdl_audio_device.rs +++ b/gb/src/sdl_audio_device.rs @@ -1,12 +1,15 @@ use std::{ffi::{CStr, c_void}, mem::MaybeUninit}; use lib_gb::{GB_FREQUENCY, apu::audio_device::*}; use sdl2::{sys::*,libc::c_char}; + +#[cfg(not(feature = "sdl-resample"))] use crate::audio_resampler::AudioResampler; +#[cfg(feature = "sdl-resample")] +use crate::sdl_audio_resampler::SdlAudioResampler as AudioResampler; use crossbeam_channel::{Receiver, Sender, bounded}; //After twicking those numbers Iv reached this, this will affect fps which will affect sound tearing -const BUFFER_SIZE:usize = 1024 * 2; const BYTES_TO_WAIT:u32 = BUFFER_SIZE as u32 * 16; const VOLUME:Sample = 10 as Sample; @@ -17,6 +20,7 @@ struct Data{ pub current_buf_index:usize, } pub struct SdlAudioDevie{ + // #[cfg(feature = "push_audio")] device_id: SDL_AudioDeviceID, resampler: AudioResampler, @@ -142,8 +146,9 @@ unsafe extern "C" fn audio_callback(userdata:*mut c_void, buffer:*mut u8, length } impl AudioDevice for SdlAudioDevie{ - fn push_buffer(&mut self, buffer:&[StereoSample]){ - for sample in self.resampler.resample(buffer){ + fn push_buffer(&mut self, buffer:&[StereoSample; BUFFER_SIZE]){ + let resample = self.resampler.resample(buffer); + for sample in resample{ self.buffer[self.buffer_index] = sample.left_sample * VOLUME; self.buffer[self.buffer_index + 1] = sample.right_sample * VOLUME; diff --git a/gb/src/sdl_audio_resampler.rs b/gb/src/sdl_audio_resampler.rs new file mode 100644 index 00000000..53a7194d --- /dev/null +++ b/gb/src/sdl_audio_resampler.rs @@ -0,0 +1,51 @@ +use std::mem::MaybeUninit; +use lib_gb::apu::audio_device::{BUFFER_SIZE, StereoSample}; +use sdl2::sys::*; + +pub struct SdlAudioResampler{ + original_frequency:u32, + target_frequency:u32, +} + +impl SdlAudioResampler{ + pub fn new(original_frequency:u32, target_frequency:u32)->Self{ + Self{ + original_frequency, + target_frequency, + } + } + + pub fn resample(&mut self, buffer:&[StereoSample; BUFFER_SIZE])->Vec{ + unsafe{ + let mut cvt = { + let mut cvt:MaybeUninit = 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::()) as i32; + let mut buf:Vec:: = 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{ + 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::(); + let mut output = vec![StereoSample::const_defualt();length]; + + std::ptr::copy_nonoverlapping(buf_ptr, output.as_mut_ptr(), length); + + return output; + } + } +} \ No newline at end of file diff --git a/gb/src/sdl_gfx_device.rs b/gb/src/sdl_gfx_device.rs index 2990f58e..de049ef3 100644 --- a/gb/src/sdl_gfx_device.rs +++ b/gb/src/sdl_gfx_device.rs @@ -90,7 +90,7 @@ impl GfxDevice for SdlGfxDevice{ let frame_end_time = SDL_GetPerformanceCounter(); let elapsed = ((frame_end_time - self.frame_start_time) as f64 / (SDL_GetPerformanceFrequency() as f64)) * 1_000.0; if elapsed < self.frame_time_ms{ - SDL_Delay(((self.frame_time_ms - elapsed).floor()) as Uint32); + // SDL_Delay(((self.frame_time_ms - elapsed).floor()) as Uint32); } self.frame_start_time = SDL_GetPerformanceCounter(); diff --git a/gb/src/wav_file_audio_device.rs b/gb/src/wav_file_audio_device.rs index 25f2b00d..798d7ae4 100644 --- a/gb/src/wav_file_audio_device.rs +++ b/gb/src/wav_file_audio_device.rs @@ -1,6 +1,9 @@ use lib_gb::apu::audio_device::*; +#[cfg(not(feature = "sdl-resample"))] use crate::audio_resampler::AudioResampler; +#[cfg(feature = "sdl-resample")] +use crate::sdl_audio_resampler::SdlAudioResampler as AudioResampler; pub struct WavfileAudioDevice{ target_frequency:u32, @@ -21,7 +24,7 @@ impl WavfileAudioDevice{ } impl AudioDevice for WavfileAudioDevice{ - fn push_buffer(&mut self, buffer:&[StereoSample]) { + fn push_buffer(&mut self, buffer:&[StereoSample; BUFFER_SIZE]) { self.samples_buffer.append(self.resampler.resample(buffer).as_mut()); } } diff --git a/lib_gb/benches/my_bench.rs b/lib_gb/benches/my_bench.rs index 5937c401..0fa41513 100644 --- a/lib_gb/benches/my_bench.rs +++ b/lib_gb/benches/my_bench.rs @@ -8,7 +8,7 @@ use lib_gb::apu::{ pub fn criterion_bench(c: &mut Criterion){ struct StubApu; impl AudioDevice for StubApu{ - fn push_buffer(&mut self, _buffer:&[StereoSample]){} + fn push_buffer(&mut self, _buffer:&[StereoSample; BUFFER_SIZE]){} } c.bench_function("test apu", |b| b.iter(||{ diff --git a/lib_gb/src/apu/audio_device.rs b/lib_gb/src/apu/audio_device.rs index 23474dcf..a4f4939d 100644 --- a/lib_gb/src/apu/audio_device.rs +++ b/lib_gb/src/apu/audio_device.rs @@ -1,6 +1,9 @@ pub type Sample = i16; pub const DEFAULT_SAPMPLE:Sample = 0 as Sample; +pub const BUFFER_SIZE:usize = 2048; + +#[repr(C, packed)] pub struct StereoSample{ pub left_sample:Sample, pub right_sample:Sample @@ -19,5 +22,5 @@ impl Clone for StereoSample{ } pub trait AudioDevice{ - fn push_buffer(&mut self, buffer:&[StereoSample]); + fn push_buffer(&mut self, buffer:&[StereoSample; BUFFER_SIZE]); } \ No newline at end of file diff --git a/lib_gb/src/apu/gb_apu.rs b/lib_gb/src/apu/gb_apu.rs index a090f5b5..6233f458 100644 --- a/lib_gb/src/apu/gb_apu.rs +++ b/lib_gb/src/apu/gb_apu.rs @@ -10,8 +10,6 @@ use super::{ sound_utils::NUMBER_OF_CHANNELS }; -pub const AUDIO_BUFFER_SIZE:usize = 0x400; - pub struct GbApu{ pub wave_channel:Channel, pub sweep_tone_channel:Channel, @@ -22,7 +20,7 @@ pub struct GbApu{ pub left_terminal:SoundTerminal, pub enabled:bool, - audio_buffer:[StereoSample;AUDIO_BUFFER_SIZE], + audio_buffer:[StereoSample;BUFFER_SIZE], current_m_cycle:u32, device:Device, last_enabled_state:bool @@ -91,7 +89,7 @@ impl GbApu{ } fn push_buffer_if_full(&mut self){ - if self.current_m_cycle as usize >= AUDIO_BUFFER_SIZE{ + if self.current_m_cycle as usize >= BUFFER_SIZE{ self.current_m_cycle = 0; self.device.push_buffer(&self.audio_buffer); } diff --git a/lib_gb/tests/integration_tests.rs b/lib_gb/tests/integration_tests.rs index 7fe1eef5..53520119 100644 --- a/lib_gb/tests/integration_tests.rs +++ b/lib_gb/tests/integration_tests.rs @@ -1,6 +1,7 @@ use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; use std::io::Read; +use lib_gb::apu::audio_device::BUFFER_SIZE; use lib_gb::ppu::gb_ppu::{SCREEN_HEIGHT, SCREEN_WIDTH}; use lib_gb::{ apu::audio_device::AudioDevice, keypad::joypad_provider::JoypadProvider, @@ -29,7 +30,7 @@ impl GfxDevice for CheckHashGfxDevice{ struct StubAudioDevice; impl AudioDevice for StubAudioDevice{ - fn push_buffer(&mut self, _buffer:&[lib_gb::apu::audio_device::StereoSample]) {} + fn push_buffer(&mut self, _buffer:&[lib_gb::apu::audio_device::StereoSample; BUFFER_SIZE]) {} } struct StubJoypadProvider; From c030275afe17d9dd403b339c18876503d751a780 Mon Sep 17 00:00:00 2001 From: alloncm Date: Sun, 31 Oct 2021 01:57:02 +0300 Subject: [PATCH 27/36] Add push and pull audio options and add vsync * Audio pull by deafult and under the featue flag - push-audio * vsync is on by default and by passing `--no-vsync` it can be disabled --- gb/Cargo.toml | 3 +- gb/src/main.rs | 12 ++- gb/src/sdl_audio_device.rs | 188 ++++++++++++++++++++++++++----------- gb/src/sdl_gfx_device.rs | 22 ++--- 4 files changed, 151 insertions(+), 74 deletions(-) diff --git a/gb/Cargo.toml b/gb/Cargo.toml index 22556d13..d05dff1d 100644 --- a/gb/Cargo.toml +++ b/gb/Cargo.toml @@ -6,7 +6,7 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [[bin]] -name = "magen_boy" +name = "magenboy" path = "src/main.rs" [dependencies] @@ -19,6 +19,5 @@ wav = "0.6.0" crossbeam-channel = "0.5" [features] -default = ["sdl-resample"] sdl-resample = [] push-audio = [] \ No newline at end of file diff --git a/gb/src/main.rs b/gb/src/main.rs index c02431e6..7d3bcfe4 100644 --- a/gb/src/main.rs +++ b/gb/src/main.rs @@ -16,7 +16,7 @@ use log::info; use sdl2::sys::*; const SCREEN_SCALE:u8 = 4; -const TURBO_MUL:u8 = 2; +const TURBO_MUL:u8 = 1; fn init_logger(debug:bool)->Result<(), fern::InitError>{ let level = if debug {log::LevelFilter::Debug} else {log::LevelFilter::Info}; @@ -82,7 +82,8 @@ fn main() { Result::Err(error)=>std::panic!("error initing logger: {}", error) } - let mut sdl_gfx_device = sdl_gfx_device::SdlGfxDevice::new(SCREEN_WIDTH as u32, SCREEN_HEIGHT as u32, "MagenBoy", SCREEN_SCALE, TURBO_MUL); + let mut sdl_gfx_device = sdl_gfx_device::SdlGfxDevice::new(SCREEN_WIDTH as u32, SCREEN_HEIGHT as u32, + "MagenBoy", SCREEN_SCALE, TURBO_MUL, check_for_terminal_feature_flag(&args, "--no-vsync")); let (s,r) = crossbeam_channel::bounded(BUFFERS_NUMBER - 1); let mpmc_device = MpmcGfxDevice{sender:s}; @@ -122,7 +123,12 @@ fn main() { // Receiving usize and not raw ptr cause in rust you cant pass a raw ptr to another thread fn emulation_thread_main(args: Vec, program_name: String, spsc_gfx_device: MpmcGfxDevice, running_ptr: usize) { - let audio_device = sdl_audio_device::SdlAudioDevie::new(44100, TURBO_MUL); + + #[cfg(feature = "push-audio")] + let audio_device = sdl_audio_device::SdlPushAudioDevice::new(44100, TURBO_MUL); + #[cfg(not(feature = "push-audio"))] + let audio_device = sdl_audio_device::SdlPullAudioDevice::new(44100, TURBO_MUL); + let mut devices: Vec::> = Vec::new(); devices.push(Box::new(audio_device)); if check_for_terminal_feature_flag(&args, "--file-audio"){ diff --git a/gb/src/sdl_audio_device.rs b/gb/src/sdl_audio_device.rs index a978920d..a5bb44e3 100644 --- a/gb/src/sdl_audio_device.rs +++ b/gb/src/sdl_audio_device.rs @@ -7,41 +7,59 @@ use crate::audio_resampler::AudioResampler; #[cfg(feature = "sdl-resample")] use crate::sdl_audio_resampler::SdlAudioResampler as AudioResampler; -use crossbeam_channel::{Receiver, Sender, bounded}; +#[cfg(not(feature = "push-audio"))] +use crossbeam_channel::{Receiver, SendError, Sender, bounded}; //After twicking those numbers Iv reached this, this will affect fps which will affect sound tearing +#[cfg(feature = "push-audio")] const BYTES_TO_WAIT:u32 = BUFFER_SIZE as u32 * 16; const VOLUME:Sample = 10 as Sample; +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(); + } +} +#[cfg(not(feature = "push-audio"))] struct Data{ - pub rx: Receiver<[Sample;BUFFER_SIZE]>, - pub current_buf: Option<[Sample;BUFFER_SIZE]>, - pub current_buf_index:usize, + rx: Receiver<[Sample;BUFFER_SIZE]>, + current_buf: Option<[Sample;BUFFER_SIZE]>, + current_buf_index:usize, } -pub struct SdlAudioDevie{ - // #[cfg(feature = "push_audio")] - device_id: SDL_AudioDeviceID, - resampler: AudioResampler, +#[cfg(not(feature = "push-audio"))] +pub struct SdlPullAudioDevice{ + resampler: AudioResampler, buffer: [Sample;BUFFER_SIZE], buffer_index:usize, - tx: Sender<[Sample;BUFFER_SIZE]>, + tarnsmiter: Sender<[Sample;BUFFER_SIZE]>, + + userdata: Data } -impl SdlAudioDevie{ +#[cfg(not(feature = "push-audio"))] +impl SdlPullAudioDevice{ pub fn new(frequency:i32, turbo_mul:u8)->Self{ - let(s,r) = bounded(3); - let boxed_data = Box::new(Data{ + // cap of less than 2 hurts the fps + let(s,r) = bounded(2); + let data = Data{ current_buf:Option::None, current_buf_index:0, rx:r - }); + }; - let leaked_data = Box::leak(boxed_data); - + 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, @@ -52,19 +70,19 @@ impl SdlAudioDevie{ padding: 0, size: 0, callback: Option::Some(audio_callback), - userdata: leaked_data as *mut Data as *mut c_void + userdata: (&mut device.userdata) as *mut Data as *mut c_void }; let mut uninit_audio_spec:MaybeUninit = MaybeUninit::uninit(); - let device_id = unsafe{ + 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!("{}",Self::get_sdl_error_message()); + std::panic!("{}", get_sdl_error_message()); } let init_audio_spec:SDL_AudioSpec = uninit_audio_spec.assume_init(); @@ -75,48 +93,17 @@ impl SdlAudioDevie{ //This will start the audio processing SDL_PauseAudioDevice(id, 0); - - id }; - return SdlAudioDevie{ - device_id: device_id, - buffer:[DEFAULT_SAPMPLE;BUFFER_SIZE], - buffer_index:0, - resampler: AudioResampler::new(GB_FREQUENCY * turbo_mul as u32, frequency as u32), - tx:s, - }; - } - 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(); - } + return device; } - - fn push_audio_to_device(&self, audio:&[Sample; BUFFER_SIZE])->Result<(),&str>{ - // let audio_ptr: *const c_void = audio.as_ptr() as *const c_void; - // let data_byte_len = (audio.len() * std::mem::size_of::()) as u32; - - // unsafe{ - // while SDL_GetQueuedAudioSize(self.device_id) > BYTES_TO_WAIT{ - // SDL_Delay(1); - // } - - // SDL_ClearError(); - // if SDL_QueueAudio(self.device_id, audio_ptr, data_byte_len) != 0{ - // return Err(Self::get_sdl_error_message()); - // } - - // Ok(()) - // } - self.tx.send(audio.clone()).unwrap(); - Ok(()) + fn push_audio_to_device(&self, audio:&[Sample; BUFFER_SIZE])->Result<(), SendError<[Sample; BUFFER_SIZE]>>{ + self.tarnsmiter.send(audio.clone()) } } +#[cfg(not(feature = "push-audio"))] 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); @@ -145,7 +132,100 @@ unsafe extern "C" fn audio_callback(userdata:*mut c_void, buffer:*mut u8, length } } -impl AudioDevice for SdlAudioDevie{ +#[cfg(not(feature = "push-audio"))] +impl AudioDevice for SdlPullAudioDevice{ + fn push_buffer(&mut self, buffer:&[StereoSample; BUFFER_SIZE]){ + let resample = self.resampler.resample(buffer); + for sample in resample{ + + self.buffer[self.buffer_index] = sample.left_sample * VOLUME; + self.buffer[self.buffer_index + 1] = sample.right_sample * VOLUME; + self.buffer_index += 2; + if self.buffer_index == BUFFER_SIZE{ + self.push_audio_to_device(&self.buffer).unwrap(); + self.buffer_index = 0; + } + } + } +} + + +#[cfg(feature = "push-audio")] +pub struct SdlPushAudioDevice{ + device_id: SDL_AudioDeviceID, + resampler: AudioResampler, + + buffer: [Sample;BUFFER_SIZE], + buffer_index:usize, +} + +#[cfg(feature = "push-audio")] +impl SdlPushAudioDevice{ + pub fn new(frequency:i32, turbo_mul:u8)->Self{ + 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::None, + userdata: std::ptr::null_mut() + }; + + + let mut uninit_audio_spec:MaybeUninit = MaybeUninit::uninit(); + + let device_id = 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); + + id + }; + return SdlPushAudioDevice{ + device_id: device_id, + buffer:[DEFAULT_SAPMPLE;BUFFER_SIZE], + buffer_index:0, + resampler: AudioResampler::new(GB_FREQUENCY * turbo_mul as u32, frequency as u32) + }; + } + + fn push_audio_to_device(&self, audio:&[Sample; BUFFER_SIZE])->Result<(),&str>{ + let audio_ptr: *const c_void = audio.as_ptr() as *const c_void; + let data_byte_len = (audio.len() * std::mem::size_of::()) as u32; + + unsafe{ + while SDL_GetQueuedAudioSize(self.device_id) > BYTES_TO_WAIT{ + SDL_Delay(1); + } + + SDL_ClearError(); + if SDL_QueueAudio(self.device_id, audio_ptr, data_byte_len) != 0{ + return Err(get_sdl_error_message()); + } + + Ok(()) + } + } +} + +#[cfg(feature = "push-audio")] +impl AudioDevice for SdlPushAudioDevice{ fn push_buffer(&mut self, buffer:&[StereoSample; BUFFER_SIZE]){ let resample = self.resampler.resample(buffer); for sample in resample{ diff --git a/gb/src/sdl_gfx_device.rs b/gb/src/sdl_gfx_device.rs index de049ef3..ff8c66d3 100644 --- a/gb/src/sdl_gfx_device.rs +++ b/gb/src/sdl_gfx_device.rs @@ -10,14 +10,12 @@ pub struct SdlGfxDevice{ width:u32, height:u32, sacle:u8, - frame_start_time:u64, - frame_time_ms:f64, discard:u8, turbo_mul:u8, } impl SdlGfxDevice{ - pub fn new(buffer_width:u32, buffer_height:u32, window_name:&str, screen_scale: u8, turbo_mul:u8)->Self{ + pub fn new(buffer_width:u32, buffer_height:u32, window_name:&str, screen_scale: u8, turbo_mul:u8, disable_vsync:bool)->Self{ let cs_wnd_name = CString::new(window_name).unwrap(); let (_window, renderer, texture): (*mut SDL_Window, *mut SDL_Renderer, *mut SDL_Texture) = unsafe{ @@ -26,8 +24,12 @@ impl SdlGfxDevice{ cs_wnd_name.as_ptr(), SDL_WINDOWPOS_UNDEFINED_MASK as i32, SDL_WINDOWPOS_UNDEFINED_MASK as i32, buffer_width as i32 * screen_scale as i32, buffer_height as i32 * screen_scale as i32, 0); - - let rend: *mut SDL_Renderer = SDL_CreateRenderer(wind, -1, 0); + let mut flags = SDL_RendererFlags::SDL_RENDERER_ACCELERATED as u32; + if !disable_vsync{ + flags |= SDL_RendererFlags::SDL_RENDERER_PRESENTVSYNC as u32; + } + + let rend: *mut SDL_Renderer = SDL_CreateRenderer(wind, -1, flags); let tex: *mut SDL_Texture = SDL_CreateTexture(rend, SDL_PixelFormatEnum::SDL_PIXELFORMAT_ARGB8888 as u32, SDL_TextureAccess::SDL_TEXTUREACCESS_STREAMING as i32, @@ -43,8 +45,6 @@ impl SdlGfxDevice{ height:buffer_height, width:buffer_width, sacle:screen_scale, - frame_start_time: unsafe{SDL_GetPerformanceCounter()}, - frame_time_ms: (1.0/(60.0 as f64)) * 1_000.0, discard:0, turbo_mul } @@ -86,14 +86,6 @@ impl GfxDevice for SdlGfxDevice{ //There is no need to call SDL_RenderClear since im replacing the whole buffer SDL_RenderCopy(self.renderer, self.texture, std::ptr::null(), std::ptr::null()); SDL_RenderPresent(self.renderer); - - let frame_end_time = SDL_GetPerformanceCounter(); - let elapsed = ((frame_end_time - self.frame_start_time) as f64 / (SDL_GetPerformanceFrequency() as f64)) * 1_000.0; - if elapsed < self.frame_time_ms{ - // SDL_Delay(((self.frame_time_ms - elapsed).floor()) as Uint32); - } - - self.frame_start_time = SDL_GetPerformanceCounter(); } } } \ No newline at end of file From 6a7799c58c20f87febe81c8d346b591cacf778cf Mon Sep 17 00:00:00 2001 From: alloncm Date: Sun, 31 Oct 2021 01:57:19 +0300 Subject: [PATCH 28/36] Update the readme with cli flags :) --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index f59b9484..08636e24 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,17 @@ 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 From c8e598a3b8215b4c2931103a1895b5e40828f734 Mon Sep 17 00:00:00 2001 From: alloncm Date: Sun, 31 Oct 2021 23:21:15 +0200 Subject: [PATCH 29/36] Arrange the conditinal comp stuff There are 2 flags * sdl-resample - will compile the sdl resampler instead of my resampler * push-audio - will push the audio to the device instead of the device pulling it by himslef. --- gb/src/{ => audio}/audio_resampler.rs | 29 +-- gb/src/audio/mod.rs | 56 +++++ gb/src/{ => audio}/multi_device_audio.rs | 0 gb/src/{ => audio}/sdl_audio_resampler.rs | 9 +- gb/src/audio/sdl_pull_audio_device.rs | 133 +++++++++++ gb/src/audio/sdl_push_audio_device.rs | 101 ++++++++ gb/src/{ => audio}/wav_file_audio_device.rs | 16 +- gb/src/main.rs | 16 +- gb/src/sdl_audio_device.rs | 242 -------------------- 9 files changed, 322 insertions(+), 280 deletions(-) rename gb/src/{ => audio}/audio_resampler.rs (89%) create mode 100644 gb/src/audio/mod.rs rename gb/src/{ => audio}/multi_device_audio.rs (100%) rename gb/src/{ => audio}/sdl_audio_resampler.rs (87%) create mode 100644 gb/src/audio/sdl_pull_audio_device.rs create mode 100644 gb/src/audio/sdl_push_audio_device.rs rename gb/src/{ => audio}/wav_file_audio_device.rs (76%) delete mode 100644 gb/src/sdl_audio_device.rs diff --git a/gb/src/audio_resampler.rs b/gb/src/audio/audio_resampler.rs similarity index 89% rename from gb/src/audio_resampler.rs rename to gb/src/audio/audio_resampler.rs index 2527dcf8..6a59d0a0 100644 --- a/gb/src/audio_resampler.rs +++ b/gb/src/audio/audio_resampler.rs @@ -1,6 +1,7 @@ use lib_gb::apu::audio_device::{BUFFER_SIZE, DEFAULT_SAPMPLE, Sample, StereoSample}; +use super::AudioResampler; -pub struct AudioResampler{ +pub struct MagenAudioResampler{ to_skip:u32, sampling_buffer:Vec, sampling_counter:u32, @@ -10,8 +11,18 @@ pub struct AudioResampler{ skip_to_use:u32, } -impl AudioResampler{ - pub fn new(original_frequency:u32, target_frequency:u32)->Self{ +impl MagenAudioResampler{ + fn interpolate_sample(samples:&[StereoSample])->StereoSample{ + + 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; @@ -30,7 +41,7 @@ impl AudioResampler{ std::panic!("target freqency is too high: {}", target_frequency); } - AudioResampler{ + MagenAudioResampler{ to_skip:to_skip, sampling_buffer:Vec::with_capacity(upper_to_skip as usize), sampling_counter: 0, @@ -41,7 +52,7 @@ impl AudioResampler{ } } - pub fn resample(&mut self, buffer:&[StereoSample; BUFFER_SIZE])->Vec{ + fn resample(&mut self, buffer:&[StereoSample; BUFFER_SIZE])->Vec{ let mut output = Vec::new(); for sample in buffer.into_iter(){ self.sampling_buffer.push(sample.clone()); @@ -67,12 +78,4 @@ impl AudioResampler{ return output; } - - fn interpolate_sample(samples:&[StereoSample])->StereoSample{ - - 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}; - } } \ No newline at end of file diff --git a/gb/src/audio/mod.rs b/gb/src/audio/mod.rs new file mode 100644 index 00000000..31dd91e0 --- /dev/null +++ b/gb/src/audio/mod.rs @@ -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")] +pub type ChosenResampler = sdl_audio_resampler::SdlAudioResampler; +#[cfg(feature = "sdl-resampler")] +pub mod sdl_audio_resampler; + +#[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; +} + +trait SdlAudioDevice : AudioDevice{ + const VOLUME:Sample = 10 as Sample; + + 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>; +} diff --git a/gb/src/multi_device_audio.rs b/gb/src/audio/multi_device_audio.rs similarity index 100% rename from gb/src/multi_device_audio.rs rename to gb/src/audio/multi_device_audio.rs diff --git a/gb/src/sdl_audio_resampler.rs b/gb/src/audio/sdl_audio_resampler.rs similarity index 87% rename from gb/src/sdl_audio_resampler.rs rename to gb/src/audio/sdl_audio_resampler.rs index 53a7194d..09bb02da 100644 --- a/gb/src/sdl_audio_resampler.rs +++ b/gb/src/audio/sdl_audio_resampler.rs @@ -1,21 +1,22 @@ 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 SdlAudioResampler{ - pub fn new(original_frequency:u32, target_frequency:u32)->Self{ +impl AudioResampler for SdlAudioResampler{ + fn new(original_frequency:u32, target_frequency:u32)->Self{ Self{ original_frequency, target_frequency, } } - - pub fn resample(&mut self, buffer:&[StereoSample; BUFFER_SIZE])->Vec{ + + fn resample(&mut self, buffer:&[StereoSample; BUFFER_SIZE])->Vec{ unsafe{ let mut cvt = { let mut cvt:MaybeUninit = MaybeUninit::uninit(); diff --git a/gb/src/audio/sdl_pull_audio_device.rs b/gb/src/audio/sdl_pull_audio_device.rs new file mode 100644 index 00000000..c45fd7b5 --- /dev/null +++ b/gb/src/audio/sdl_pull_audio_device.rs @@ -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; + +use super::{AudioResampler, SdlAudioDevice}; + +use crossbeam_channel::{Receiver, SendError, Sender, bounded}; + + +struct Data{ + rx: Receiver<[Sample;BUFFER_SIZE]>, + current_buf: Option<[Sample;BUFFER_SIZE]>, + current_buf_index:usize, +} + +pub struct SdlPullAudioDevice{ + resampler: AR, + buffer: [Sample;BUFFER_SIZE], + buffer_index:usize, + + tarnsmiter: Sender<[Sample;BUFFER_SIZE]>, + + userdata: Data +} + +impl SdlPullAudioDevice{ + pub fn new(frequency:i32, turbo_mul:u8)->Self{ + + // cap of less than 2 hurts the fps + let(s,r) = bounded(2); + 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 = 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); + }; + + return device; + } + + fn push_audio_to_device(&self, audio:&[Sample; BUFFER_SIZE])->Result<(), SendError<[Sample; BUFFER_SIZE]>>{ + self.tarnsmiter.send(audio.clone()) + } +} + +impl SdlAudioDevice for SdlPullAudioDevice{ + 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 AudioDevice for SdlPullAudioDevice{ + 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); + + 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::()) - 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; + } +} + diff --git a/gb/src/audio/sdl_push_audio_device.rs b/gb/src/audio/sdl_push_audio_device.rs new file mode 100644 index 00000000..964bf253 --- /dev/null +++ b/gb/src/audio/sdl_push_audio_device.rs @@ -0,0 +1,101 @@ +use std::{ffi::c_void, mem::MaybeUninit, str::FromStr}; +use lib_gb::{GB_FREQUENCY, apu::audio_device::{AudioDevice, BUFFER_SIZE, DEFAULT_SAPMPLE, Sample, StereoSample}}; +use sdl2::sys::*; +use super::{SdlAudioDevice, get_sdl_error_message}; +use super::AudioResampler; + + +//After twicking those numbers Iv reached this, this will affect fps which will affect sound tearing +const BYTES_TO_WAIT:u32 = BUFFER_SIZE as u32 * 16; + +pub struct SdlPushAudioDevice{ + device_id: SDL_AudioDeviceID, + resampler: AR, + + buffer: [Sample;BUFFER_SIZE], + buffer_index:usize, +} + +impl SdlPushAudioDevice{ + pub fn new(frequency:i32, turbo_mul:u8)->Self{ + 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::None, + userdata: std::ptr::null_mut() + }; + + + let mut uninit_audio_spec:MaybeUninit = MaybeUninit::uninit(); + + let device_id = 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); + + id + }; + return SdlPushAudioDevice{ + device_id: device_id, + buffer:[DEFAULT_SAPMPLE;BUFFER_SIZE], + buffer_index:0, + resampler: AudioResampler::new(GB_FREQUENCY * turbo_mul as u32, frequency as u32) + }; + } + + fn push_audio_to_device(&self, audio:&[Sample; BUFFER_SIZE])->Result<(),&str>{ + let audio_ptr: *const c_void = audio.as_ptr() as *const c_void; + let data_byte_len = (audio.len() * std::mem::size_of::()) as u32; + + unsafe{ + while SDL_GetQueuedAudioSize(self.device_id) > BYTES_TO_WAIT{ + SDL_Delay(1); + } + + SDL_ClearError(); + if SDL_QueueAudio(self.device_id, audio_ptr, data_byte_len) != 0{ + return Err(get_sdl_error_message()); + } + + Ok(()) + } + } +} + +impl SdlAudioDevice for SdlPushAudioDevice{ + 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|String::from_str(e).unwrap()) + } +} + +impl AudioDevice for SdlPushAudioDevice{ + fn push_buffer(&mut self, buffer:&[StereoSample; BUFFER_SIZE]) { + SdlAudioDevice::push_buffer(self, buffer); + } +} \ No newline at end of file diff --git a/gb/src/wav_file_audio_device.rs b/gb/src/audio/wav_file_audio_device.rs similarity index 76% rename from gb/src/wav_file_audio_device.rs rename to gb/src/audio/wav_file_audio_device.rs index 798d7ae4..e2377fcc 100644 --- a/gb/src/wav_file_audio_device.rs +++ b/gb/src/audio/wav_file_audio_device.rs @@ -1,18 +1,14 @@ use lib_gb::apu::audio_device::*; +use super::AudioResampler; -#[cfg(not(feature = "sdl-resample"))] -use crate::audio_resampler::AudioResampler; -#[cfg(feature = "sdl-resample")] -use crate::sdl_audio_resampler::SdlAudioResampler as AudioResampler; - -pub struct WavfileAudioDevice{ +pub struct WavfileAudioDevice{ target_frequency:u32, - resampler: AudioResampler, + resampler: AR, filename:&'static str, samples_buffer:Vec:: } -impl WavfileAudioDevice{ +impl WavfileAudioDevice{ pub fn new(target_freq:u32, original_freq:u32, filename:&'static str)->Self{ WavfileAudioDevice{ filename, @@ -23,13 +19,13 @@ impl WavfileAudioDevice{ } } -impl AudioDevice for WavfileAudioDevice{ +impl AudioDevice for WavfileAudioDevice{ fn push_buffer(&mut self, buffer:&[StereoSample; BUFFER_SIZE]) { self.samples_buffer.append(self.resampler.resample(buffer).as_mut()); } } -impl Drop for WavfileAudioDevice{ +impl Drop for WavfileAudioDevice{ fn drop(&mut self) { let header = wav::header::Header::new(wav::WAV_FORMAT_PCM, 2, self.target_frequency, 16); let mut samples = Vec::with_capacity(self.samples_buffer.len() * 2); diff --git a/gb/src/main.rs b/gb/src/main.rs index 7d3bcfe4..29cdd99c 100644 --- a/gb/src/main.rs +++ b/gb/src/main.rs @@ -1,15 +1,9 @@ mod mbc_handler; mod sdl_joypad_provider; -mod sdl_audio_device; -#[cfg(not(feature = "sdl-resample"))] -mod audio_resampler; -#[cfg(feature = "sdl-resample")] -mod sdl_audio_resampler; -mod wav_file_audio_device; -mod multi_device_audio; mod sdl_gfx_device; +mod audio; -use crate::{mbc_handler::*, sdl_joypad_provider::*, multi_device_audio::*}; +use crate::{audio::{ChosenResampler, multi_device_audio::*}, mbc_handler::*, sdl_joypad_provider::*}; use lib_gb::{GB_FREQUENCY, apu::audio_device::*, keypad::button::Button, machine::gameboy::GameBoy, mmu::gb_mmu::BOOT_ROM_SIZE, ppu::{gb_ppu::{BUFFERS_NUMBER, SCREEN_HEIGHT, SCREEN_WIDTH}, gfx_device::GfxDevice}}; use std::{fs, env, result::Result, vec::Vec}; use log::info; @@ -125,14 +119,14 @@ fn main() { fn emulation_thread_main(args: Vec, program_name: String, spsc_gfx_device: MpmcGfxDevice, running_ptr: usize) { #[cfg(feature = "push-audio")] - let audio_device = sdl_audio_device::SdlPushAudioDevice::new(44100, TURBO_MUL); + let audio_device = audio::sdl_push_audio_device::SdlPushAudioDevice::::new(44100, TURBO_MUL); #[cfg(not(feature = "push-audio"))] - let audio_device = sdl_audio_device::SdlPullAudioDevice::new(44100, TURBO_MUL); + let audio_device = audio::sdl_pull_audio_device::SdlPullAudioDevice::::new(44100, TURBO_MUL); let mut devices: Vec::> = Vec::new(); devices.push(Box::new(audio_device)); if check_for_terminal_feature_flag(&args, "--file-audio"){ - let wav_ad = wav_file_audio_device::WavfileAudioDevice::new(44100, GB_FREQUENCY, "output.wav"); + let wav_ad = audio::wav_file_audio_device::WavfileAudioDevice::::new(44100, GB_FREQUENCY, "output.wav"); devices.push(Box::new(wav_ad)); log::info!("Writing audio to file: output.wav"); } diff --git a/gb/src/sdl_audio_device.rs b/gb/src/sdl_audio_device.rs deleted file mode 100644 index a5bb44e3..00000000 --- a/gb/src/sdl_audio_device.rs +++ /dev/null @@ -1,242 +0,0 @@ -use std::{ffi::{CStr, c_void}, mem::MaybeUninit}; -use lib_gb::{GB_FREQUENCY, apu::audio_device::*}; -use sdl2::{sys::*,libc::c_char}; - -#[cfg(not(feature = "sdl-resample"))] -use crate::audio_resampler::AudioResampler; -#[cfg(feature = "sdl-resample")] -use crate::sdl_audio_resampler::SdlAudioResampler as AudioResampler; - -#[cfg(not(feature = "push-audio"))] -use crossbeam_channel::{Receiver, SendError, Sender, bounded}; - -//After twicking those numbers Iv reached this, this will affect fps which will affect sound tearing -#[cfg(feature = "push-audio")] -const BYTES_TO_WAIT:u32 = BUFFER_SIZE as u32 * 16; -const VOLUME:Sample = 10 as Sample; - -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(); - } -} - -#[cfg(not(feature = "push-audio"))] -struct Data{ - rx: Receiver<[Sample;BUFFER_SIZE]>, - current_buf: Option<[Sample;BUFFER_SIZE]>, - current_buf_index:usize, -} - -#[cfg(not(feature = "push-audio"))] -pub struct SdlPullAudioDevice{ - resampler: AudioResampler, - buffer: [Sample;BUFFER_SIZE], - buffer_index:usize, - - tarnsmiter: Sender<[Sample;BUFFER_SIZE]>, - - userdata: Data -} - -#[cfg(not(feature = "push-audio"))] -impl SdlPullAudioDevice{ - pub fn new(frequency:i32, turbo_mul:u8)->Self{ - - // cap of less than 2 hurts the fps - let(s,r) = bounded(2); - 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 = 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); - }; - - return device; - } - - fn push_audio_to_device(&self, audio:&[Sample; BUFFER_SIZE])->Result<(), SendError<[Sample; BUFFER_SIZE]>>{ - self.tarnsmiter.send(audio.clone()) - } -} - -#[cfg(not(feature = "push-audio"))] -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); - - 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::()) - 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; - } -} - -#[cfg(not(feature = "push-audio"))] -impl AudioDevice for SdlPullAudioDevice{ - fn push_buffer(&mut self, buffer:&[StereoSample; BUFFER_SIZE]){ - let resample = self.resampler.resample(buffer); - for sample in resample{ - - self.buffer[self.buffer_index] = sample.left_sample * VOLUME; - self.buffer[self.buffer_index + 1] = sample.right_sample * VOLUME; - self.buffer_index += 2; - if self.buffer_index == BUFFER_SIZE{ - self.push_audio_to_device(&self.buffer).unwrap(); - self.buffer_index = 0; - } - } - } -} - - -#[cfg(feature = "push-audio")] -pub struct SdlPushAudioDevice{ - device_id: SDL_AudioDeviceID, - resampler: AudioResampler, - - buffer: [Sample;BUFFER_SIZE], - buffer_index:usize, -} - -#[cfg(feature = "push-audio")] -impl SdlPushAudioDevice{ - pub fn new(frequency:i32, turbo_mul:u8)->Self{ - 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::None, - userdata: std::ptr::null_mut() - }; - - - let mut uninit_audio_spec:MaybeUninit = MaybeUninit::uninit(); - - let device_id = 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); - - id - }; - return SdlPushAudioDevice{ - device_id: device_id, - buffer:[DEFAULT_SAPMPLE;BUFFER_SIZE], - buffer_index:0, - resampler: AudioResampler::new(GB_FREQUENCY * turbo_mul as u32, frequency as u32) - }; - } - - fn push_audio_to_device(&self, audio:&[Sample; BUFFER_SIZE])->Result<(),&str>{ - let audio_ptr: *const c_void = audio.as_ptr() as *const c_void; - let data_byte_len = (audio.len() * std::mem::size_of::()) as u32; - - unsafe{ - while SDL_GetQueuedAudioSize(self.device_id) > BYTES_TO_WAIT{ - SDL_Delay(1); - } - - SDL_ClearError(); - if SDL_QueueAudio(self.device_id, audio_ptr, data_byte_len) != 0{ - return Err(get_sdl_error_message()); - } - - Ok(()) - } - } -} - -#[cfg(feature = "push-audio")] -impl AudioDevice for SdlPushAudioDevice{ - fn push_buffer(&mut self, buffer:&[StereoSample; BUFFER_SIZE]){ - let resample = self.resampler.resample(buffer); - for sample in resample{ - - self.buffer[self.buffer_index] = sample.left_sample * VOLUME; - self.buffer[self.buffer_index + 1] = sample.right_sample * VOLUME; - self.buffer_index += 2; - if self.buffer_index == BUFFER_SIZE{ - self.push_audio_to_device(&self.buffer).unwrap(); - self.buffer_index = 0; - } - } - } -} \ No newline at end of file From 4e74636419b729323d5f0b3e31d348a101b69d08 Mon Sep 17 00:00:00 2001 From: alloncm Date: Mon, 1 Nov 2021 21:35:30 +0200 Subject: [PATCH 30/36] FIx an outdated test The apu timer test was outdated. The apu timer counts in tcycles but increment itself each time by 4 cycles and not 1. --- lib_gb/tests/timer.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib_gb/tests/timer.rs b/lib_gb/tests/timer.rs index d9ceebff..8d67b282 100644 --- a/lib_gb/tests/timer.rs +++ b/lib_gb/tests/timer.rs @@ -3,15 +3,16 @@ use lib_gb::apu::timer::Timer; #[test] fn timer_test(){ - let mut timer = Timer::new(512); + let count = 512; + let mut timer = Timer::new(count); let mut counter = 0; - for _ in 0..511{ + for _ in 0..(count/4)-1{ counter+=1; assert!(timer.cycle() == false); } counter+=1; assert!(timer.cycle() == true); - assert!(counter == 512) + assert!(counter == 512/4) } \ No newline at end of file From f1d27538592b91c7d2223dbb618f1f5bcae99b9f Mon Sep 17 00:00:00 2001 From: alloncm Date: Thu, 4 Nov 2021 20:52:17 +0200 Subject: [PATCH 31/36] Cr fixes Some renaming and other cosmetic changes like extracting to files. Also using cfg-if for ifdef like code --- Cargo.lock | 5 +- gb/Cargo.toml | 3 +- ..._resampler.rs => magen_audio_resampler.rs} | 2 - gb/src/audio/mod.rs | 38 ++++++++----- gb/src/audio/sdl_audio_resampler.rs | 3 +- gb/src/audio/sdl_pull_audio_device.rs | 55 +++++++++---------- gb/src/audio/sdl_push_audio_device.rs | 48 ++++++++-------- gb/src/main.rs | 23 ++------ gb/src/mpmc_gfx_device.rs | 19 +++++++ lib_gb/Cargo.toml | 2 +- .../benches/{my_bench.rs => lib_gb_bench.rs} | 0 lib_gb/src/apu/sound_terminal.rs | 1 + lib_gb/src/utils/mod.rs | 1 + 13 files changed, 104 insertions(+), 96 deletions(-) rename gb/src/audio/{audio_resampler.rs => magen_audio_resampler.rs} (99%) create mode 100644 gb/src/mpmc_gfx_device.rs rename lib_gb/benches/{my_bench.rs => lib_gb_bench.rs} (100%) diff --git a/Cargo.lock b/Cargo.lock index ee7c6a38..5351c143 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -416,6 +416,7 @@ dependencies = [ name = "gb" version = "1.0.0" dependencies = [ + "cfg-if 1.0.0", "chrono", "crossbeam-channel", "fern", @@ -1505,9 +1506,9 @@ checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" [[package]] name = "wav" -version = "0.6.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "549df97e073e1f901a489f159eb451bbe9c790e2f217394f4ce01acda0380b0c" +checksum = "a65e199c799848b4f997072aa4d673c034f80f40191f97fe2f0a23f410be1609" dependencies = [ "riff", ] diff --git a/gb/Cargo.toml b/gb/Cargo.toml index d05dff1d..05b7cb6d 100644 --- a/gb/Cargo.toml +++ b/gb/Cargo.toml @@ -15,8 +15,9 @@ log = "0.4" fern = "0.6.0" chrono = "0.4" sdl2 = {version = "0.34", features = ["bundled","static-link"]} -wav = "0.6.0" +wav = "1.0" crossbeam-channel = "0.5" +cfg-if = "1.0" [features] sdl-resample = [] diff --git a/gb/src/audio/audio_resampler.rs b/gb/src/audio/magen_audio_resampler.rs similarity index 99% rename from gb/src/audio/audio_resampler.rs rename to gb/src/audio/magen_audio_resampler.rs index 6a59d0a0..69f868c1 100644 --- a/gb/src/audio/audio_resampler.rs +++ b/gb/src/audio/magen_audio_resampler.rs @@ -13,7 +13,6 @@ pub struct MagenAudioResampler{ impl MagenAudioResampler{ fn interpolate_sample(samples:&[StereoSample])->StereoSample{ - 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; @@ -76,6 +75,5 @@ impl AudioResampler for MagenAudioResampler{ } return output; - } } \ No newline at end of file diff --git a/gb/src/audio/mod.rs b/gb/src/audio/mod.rs index 31dd91e0..4889895c 100644 --- a/gb/src/audio/mod.rs +++ b/gb/src/audio/mod.rs @@ -1,25 +1,32 @@ 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; + +cfg_if::cfg_if!{ + if #[cfg(feature = "push-audio")]{ + pub mod sdl_push_audio_device; + pub type ChosenAudioDevice = sdl_push_audio_device::SdlPushAudioDevice; + } + else{ + pub mod sdl_pull_audio_device; + pub type ChosenAudioDevice = sdl_pull_audio_device::SdlPullAudioDevice; + } +} +cfg_if::cfg_if!{ + if #[cfg(feature = "sdl-resampler")]{ + pub mod sdl_audio_resampler; + pub type ChosenResampler = sdl_audio_resampler::SdlAudioResampler; + } + else{ + pub mod magen_audio_resampler; + pub type ChosenResampler = magen_audio_resampler::MagenAudioResampler; + } +} 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")] -pub type ChosenResampler = sdl_audio_resampler::SdlAudioResampler; -#[cfg(feature = "sdl-resampler")] -pub mod sdl_audio_resampler; - -#[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(); @@ -33,7 +40,7 @@ pub trait AudioResampler{ fn resample(&mut self, buffer:&[StereoSample; BUFFER_SIZE])->Vec; } -trait SdlAudioDevice : AudioDevice{ +pub trait ResampledAudioDevice : AudioDevice{ const VOLUME:Sample = 10 as Sample; fn push_buffer(&mut self, buffer:&[StereoSample; BUFFER_SIZE]){ @@ -53,4 +60,5 @@ trait SdlAudioDevice : AudioDevice{ 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>; + fn new(frequency:i32, turbo_mul:u8)->Self; } diff --git a/gb/src/audio/sdl_audio_resampler.rs b/gb/src/audio/sdl_audio_resampler.rs index 09bb02da..19691c8a 100644 --- a/gb/src/audio/sdl_audio_resampler.rs +++ b/gb/src/audio/sdl_audio_resampler.rs @@ -35,8 +35,7 @@ impl AudioResampler for SdlAudioResampler{ 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{ + if SDL_ConvertAudio(&mut cvt) != 0{ std::panic!("error while converting audio, status code: {}", status_code); } diff --git a/gb/src/audio/sdl_pull_audio_device.rs b/gb/src/audio/sdl_pull_audio_device.rs index c45fd7b5..527020a0 100644 --- a/gb/src/audio/sdl_pull_audio_device.rs +++ b/gb/src/audio/sdl_pull_audio_device.rs @@ -1,14 +1,12 @@ 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; - -use super::{AudioResampler, SdlAudioDevice}; +use super::{AudioResampler, ResampledAudioDevice, get_sdl_error_message}; +use sdl2::sys::*; use crossbeam_channel::{Receiver, SendError, Sender, bounded}; -struct Data{ +struct UserData{ rx: Receiver<[Sample;BUFFER_SIZE]>, current_buf: Option<[Sample;BUFFER_SIZE]>, current_buf_index:usize, @@ -21,15 +19,21 @@ pub struct SdlPullAudioDevice{ tarnsmiter: Sender<[Sample;BUFFER_SIZE]>, - userdata: Data + userdata: UserData } impl SdlPullAudioDevice{ - pub fn new(frequency:i32, turbo_mul:u8)->Self{ + fn push_audio_to_device(&self, audio:&[Sample; BUFFER_SIZE])->Result<(), SendError<[Sample; BUFFER_SIZE]>>{ + self.tarnsmiter.send(audio.clone()) + } +} + +impl ResampledAudioDevice for SdlPullAudioDevice{ + fn new(frequency:i32, turbo_mul:u8)->Self{ // cap of less than 2 hurts the fps let(s,r) = bounded(2); - let data = Data{ + let data = UserData{ current_buf:Option::None, current_buf_index:0, rx:r @@ -52,7 +56,7 @@ impl SdlPullAudioDevice{ padding: 0, size: 0, callback: Option::Some(audio_callback), - userdata: (&mut device.userdata) as *mut Data as *mut c_void + userdata: (&mut device.userdata) as *mut UserData as *mut c_void }; @@ -79,13 +83,6 @@ impl SdlPullAudioDevice{ return device; } - - fn push_audio_to_device(&self, audio:&[Sample; BUFFER_SIZE])->Result<(), SendError<[Sample; BUFFER_SIZE]>>{ - self.tarnsmiter.send(audio.clone()) - } -} - -impl SdlAudioDevice for SdlPullAudioDevice{ fn get_audio_buffer(&mut self) ->(&mut [Sample;BUFFER_SIZE], &mut usize) { (&mut self.buffer, &mut self.buffer_index) } @@ -99,35 +96,35 @@ impl SdlAudioDevice for SdlPullAudioDevice{ impl AudioDevice for SdlPullAudioDevice{ fn push_buffer(&mut self, buffer:&[StereoSample; BUFFER_SIZE]) { - SdlAudioDevice::push_buffer(self, buffer); + ResampledAudioDevice::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); + let safe_userdata = &mut *(userdata as *mut UserData); - if s.current_buf.is_none(){ - s.current_buf = Some(s.rx.recv().unwrap()); + if safe_userdata.current_buf.is_none(){ + safe_userdata.current_buf = Some(safe_userdata.rx.recv().unwrap()); } - let samples = s.current_buf.unwrap(); - let samples_size = (samples.len() * std::mem::size_of::()) - s.current_buf_index; - let samples_ptr = (samples.as_ptr() as *mut u8).add(s.current_buf_index); + let samples = safe_userdata.current_buf.unwrap(); + let samples_size = (samples.len() * std::mem::size_of::()) - safe_userdata.current_buf_index; + let samples_ptr = (samples.as_ptr() as *mut u8).add(safe_userdata.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; + if length > samples_size && safe_userdata.rx.is_empty(){ + safe_userdata.current_buf = Option::None; + safe_userdata.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; + safe_userdata.current_buf = Option::None; + safe_userdata.current_buf_index = 0; audio_callback(userdata, buffer.add(samples_size), (length - samples_size) as i32); } else{ - s.current_buf_index = length; + safe_userdata.current_buf_index = length; } } diff --git a/gb/src/audio/sdl_push_audio_device.rs b/gb/src/audio/sdl_push_audio_device.rs index 964bf253..33f1c5a1 100644 --- a/gb/src/audio/sdl_push_audio_device.rs +++ b/gb/src/audio/sdl_push_audio_device.rs @@ -1,9 +1,7 @@ use std::{ffi::c_void, mem::MaybeUninit, str::FromStr}; use lib_gb::{GB_FREQUENCY, apu::audio_device::{AudioDevice, BUFFER_SIZE, DEFAULT_SAPMPLE, Sample, StereoSample}}; use sdl2::sys::*; -use super::{SdlAudioDevice, get_sdl_error_message}; -use super::AudioResampler; - +use super::{AudioResampler, ResampledAudioDevice, get_sdl_error_message}; //After twicking those numbers Iv reached this, this will affect fps which will affect sound tearing const BYTES_TO_WAIT:u32 = BUFFER_SIZE as u32 * 16; @@ -17,7 +15,27 @@ pub struct SdlPushAudioDevice{ } impl SdlPushAudioDevice{ - pub fn new(frequency:i32, turbo_mul:u8)->Self{ + fn push_audio_to_device(&self, audio:&[Sample; BUFFER_SIZE])->Result<(),&str>{ + let audio_ptr: *const c_void = audio.as_ptr() as *const c_void; + let data_byte_len = (audio.len() * std::mem::size_of::()) as u32; + + unsafe{ + while SDL_GetQueuedAudioSize(self.device_id) > BYTES_TO_WAIT{ + SDL_Delay(1); + } + + SDL_ClearError(); + if SDL_QueueAudio(self.device_id, audio_ptr, data_byte_len) != 0{ + return Err(get_sdl_error_message()); + } + + Ok(()) + } + } +} + +impl ResampledAudioDevice for SdlPushAudioDevice{ + fn new(frequency:i32, turbo_mul:u8)->Self{ let desired_audio_spec = SDL_AudioSpec{ freq: frequency, format: AUDIO_S16SYS as u16, @@ -61,26 +79,6 @@ impl SdlPushAudioDevice{ }; } - fn push_audio_to_device(&self, audio:&[Sample; BUFFER_SIZE])->Result<(),&str>{ - let audio_ptr: *const c_void = audio.as_ptr() as *const c_void; - let data_byte_len = (audio.len() * std::mem::size_of::()) as u32; - - unsafe{ - while SDL_GetQueuedAudioSize(self.device_id) > BYTES_TO_WAIT{ - SDL_Delay(1); - } - - SDL_ClearError(); - if SDL_QueueAudio(self.device_id, audio_ptr, data_byte_len) != 0{ - return Err(get_sdl_error_message()); - } - - Ok(()) - } - } -} - -impl SdlAudioDevice for SdlPushAudioDevice{ fn get_audio_buffer(&mut self) ->(&mut [Sample;BUFFER_SIZE], &mut usize) { (&mut self.buffer, &mut self.buffer_index) } @@ -96,6 +94,6 @@ impl SdlAudioDevice for SdlPushAudioDevice{ impl AudioDevice for SdlPushAudioDevice{ fn push_buffer(&mut self, buffer:&[StereoSample; BUFFER_SIZE]) { - SdlAudioDevice::push_buffer(self, buffer); + ResampledAudioDevice::push_buffer(self, buffer); } } \ No newline at end of file diff --git a/gb/src/main.rs b/gb/src/main.rs index 29cdd99c..8d2b6a7f 100644 --- a/gb/src/main.rs +++ b/gb/src/main.rs @@ -1,9 +1,10 @@ mod mbc_handler; mod sdl_joypad_provider; mod sdl_gfx_device; +mod mpmc_gfx_device; mod audio; -use crate::{audio::{ChosenResampler, multi_device_audio::*}, mbc_handler::*, sdl_joypad_provider::*}; +use crate::{audio::{ChosenResampler, multi_device_audio::*, ResampledAudioDevice}, mbc_handler::*, mpmc_gfx_device::MpmcGfxDevice, sdl_joypad_provider::*}; use lib_gb::{GB_FREQUENCY, apu::audio_device::*, keypad::button::Button, machine::gameboy::GameBoy, mmu::gb_mmu::BOOT_ROM_SIZE, ppu::{gb_ppu::{BUFFERS_NUMBER, SCREEN_HEIGHT, SCREEN_WIDTH}, gfx_device::GfxDevice}}; use std::{fs, env, result::Result, vec::Vec}; use log::info; @@ -54,18 +55,6 @@ fn check_for_terminal_feature_flag(args:&Vec::, flag:&str)->bool{ args.len() >= 3 && args.contains(&String::from(flag)) } -struct MpmcGfxDevice{ - sender: crossbeam_channel::Sender -} - -impl GfxDevice for MpmcGfxDevice{ - fn swap_buffer(&mut self, buffer:&[u32; SCREEN_HEIGHT * SCREEN_WIDTH]) { - if self.sender.send(buffer.as_ptr() as usize).is_err(){ - log::debug!("The receiver endpoint has been closed"); - } - } -} - fn main() { let args: Vec = env::args().collect(); @@ -80,7 +69,7 @@ fn main() { "MagenBoy", SCREEN_SCALE, TURBO_MUL, check_for_terminal_feature_flag(&args, "--no-vsync")); let (s,r) = crossbeam_channel::bounded(BUFFERS_NUMBER - 1); - let mpmc_device = MpmcGfxDevice{sender:s}; + let mpmc_device = MpmcGfxDevice::new(s); let program_name = args[1].clone(); @@ -117,11 +106,7 @@ fn main() { // Receiving usize and not raw ptr cause in rust you cant pass a raw ptr to another thread fn emulation_thread_main(args: Vec, program_name: String, spsc_gfx_device: MpmcGfxDevice, running_ptr: usize) { - - #[cfg(feature = "push-audio")] - let audio_device = audio::sdl_push_audio_device::SdlPushAudioDevice::::new(44100, TURBO_MUL); - #[cfg(not(feature = "push-audio"))] - let audio_device = audio::sdl_pull_audio_device::SdlPullAudioDevice::::new(44100, TURBO_MUL); + let audio_device = audio::ChosenAudioDevice::::new(44100, TURBO_MUL); let mut devices: Vec::> = Vec::new(); devices.push(Box::new(audio_device)); diff --git a/gb/src/mpmc_gfx_device.rs b/gb/src/mpmc_gfx_device.rs new file mode 100644 index 00000000..a42aca95 --- /dev/null +++ b/gb/src/mpmc_gfx_device.rs @@ -0,0 +1,19 @@ +use lib_gb::ppu::{gb_ppu::{SCREEN_HEIGHT, SCREEN_WIDTH}, gfx_device::GfxDevice}; + +pub struct MpmcGfxDevice{ + sender: crossbeam_channel::Sender +} + +impl MpmcGfxDevice{ + pub fn new(sender:crossbeam_channel::Sender)->Self{ + Self{sender} + } +} + +impl GfxDevice for MpmcGfxDevice{ + fn swap_buffer(&mut self, buffer:&[u32; SCREEN_HEIGHT * SCREEN_WIDTH]) { + if self.sender.send(buffer.as_ptr() as usize).is_err(){ + log::debug!("The receiver endpoint has been closed"); + } + } +} \ No newline at end of file diff --git a/lib_gb/Cargo.toml b/lib_gb/Cargo.toml index 9ba19354..29631e6f 100644 --- a/lib_gb/Cargo.toml +++ b/lib_gb/Cargo.toml @@ -13,5 +13,5 @@ reqwest = { version = "0.11", features = ["blocking"] } zip = "0.5" [[bench]] -name = "my_bench" +name = "lib_gb_bench" harness = false \ No newline at end of file diff --git a/lib_gb/benches/my_bench.rs b/lib_gb/benches/lib_gb_bench.rs similarity index 100% rename from lib_gb/benches/my_bench.rs rename to lib_gb/benches/lib_gb_bench.rs diff --git a/lib_gb/src/apu/sound_terminal.rs b/lib_gb/src/apu/sound_terminal.rs index d0f09f3c..0d244859 100644 --- a/lib_gb/src/apu/sound_terminal.rs +++ b/lib_gb/src/apu/sound_terminal.rs @@ -39,6 +39,7 @@ impl SoundTerminal{ mixed_sample >>= 2; // Divide by 4 in order to normal the sample + // Adding +1 cause thats how to GB calculates the sound (0 still has volume) return mixed_sample * ((self.volume + 1) as Sample); } } \ No newline at end of file diff --git a/lib_gb/src/utils/mod.rs b/lib_gb/src/utils/mod.rs index 7c0f5cd0..722f0216 100644 --- a/lib_gb/src/utils/mod.rs +++ b/lib_gb/src/utils/mod.rs @@ -5,6 +5,7 @@ pub mod memory_registers; pub mod bit_masks; pub mod fixed_size_queue; +// Frequency in m_cycles (m_cycle = 4 t_cycles) pub const GB_FREQUENCY:u32 = 4_194_304 / 4; pub fn create_default_array()->[T;SIZE]{ From 7281d3dccd60852fd7187e77ffde8715c34739cb Mon Sep 17 00:00:00 2001 From: alloncm Date: Thu, 4 Nov 2021 21:10:58 +0200 Subject: [PATCH 32/36] Move the cvt struct to the sdl reasmpler Also fIxed a typo in the sdl-resample feature --- gb/src/audio/mod.rs | 2 +- gb/src/audio/sdl_audio_resampler.rs | 46 ++++++++++++++--------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/gb/src/audio/mod.rs b/gb/src/audio/mod.rs index 4889895c..bf4a157c 100644 --- a/gb/src/audio/mod.rs +++ b/gb/src/audio/mod.rs @@ -13,7 +13,7 @@ cfg_if::cfg_if!{ } } cfg_if::cfg_if!{ - if #[cfg(feature = "sdl-resampler")]{ + if #[cfg(feature = "sdl-resample")]{ pub mod sdl_audio_resampler; pub type ChosenResampler = sdl_audio_resampler::SdlAudioResampler; } diff --git a/gb/src/audio/sdl_audio_resampler.rs b/gb/src/audio/sdl_audio_resampler.rs index 19691c8a..00e1c339 100644 --- a/gb/src/audio/sdl_audio_resampler.rs +++ b/gb/src/audio/sdl_audio_resampler.rs @@ -4,43 +4,43 @@ use sdl2::sys::*; use super::AudioResampler; pub struct SdlAudioResampler{ - original_frequency:u32, - target_frequency:u32, + cvt: SDL_AudioCVT } impl AudioResampler for SdlAudioResampler{ fn new(original_frequency:u32, target_frequency:u32)->Self{ - Self{ - original_frequency, - target_frequency, + let mut cvt = unsafe{ + let mut cvt:MaybeUninit = MaybeUninit::uninit(); + SDL_BuildAudioCVT(cvt.as_mut_ptr(), AUDIO_S16 as u16, 2, original_frequency as i32, + AUDIO_S16 as u16, 2, 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::()) as i32; + + log::error!("help"); + + Self{cvt} } fn resample(&mut self, buffer:&[StereoSample; BUFFER_SIZE])->Vec{ + let mut buf:Vec:: = vec![0;(self.cvt.len * self.cvt.len_mult) as usize]; + unsafe{ - let mut cvt = { - let mut cvt:MaybeUninit = 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::()) as i32; - let mut buf:Vec:: = 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(); - if SDL_ConvertAudio(&mut cvt) != 0{ + self.cvt.buf = buf.as_mut_ptr(); + let status_code = SDL_ConvertAudio(&mut self.cvt) != 0; + if status_code{ 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::(); + let buf_ptr = self.cvt.buf as *mut StereoSample; + let length = self.cvt.len_cvt as usize / std::mem::size_of::(); let mut output = vec![StereoSample::const_defualt();length]; std::ptr::copy_nonoverlapping(buf_ptr, output.as_mut_ptr(), length); From 001ad18b44f0a45fe6e0bd306589ee7d936244ee Mon Sep 17 00:00:00 2001 From: alloncm Date: Thu, 4 Nov 2021 21:19:23 +0200 Subject: [PATCH 33/36] Extract sdl device initizalizing to a util --- gb/src/audio/mod.rs | 30 +++++++++++++++++++++++---- gb/src/audio/sdl_pull_audio_device.rs | 27 ++++-------------------- gb/src/audio/sdl_push_audio_device.rs | 26 +++-------------------- 3 files changed, 33 insertions(+), 50 deletions(-) diff --git a/gb/src/audio/mod.rs b/gb/src/audio/mod.rs index bf4a157c..fe91c04a 100644 --- a/gb/src/audio/mod.rs +++ b/gb/src/audio/mod.rs @@ -1,7 +1,5 @@ pub mod multi_device_audio; pub mod wav_file_audio_device; - - cfg_if::cfg_if!{ if #[cfg(feature = "push-audio")]{ pub mod sdl_push_audio_device; @@ -23,9 +21,9 @@ cfg_if::cfg_if!{ } } -use std::ffi::CStr; +use std::{ffi::CStr, mem::MaybeUninit}; use lib_gb::apu::audio_device::{AudioDevice, BUFFER_SIZE, Sample, StereoSample}; -use sdl2::{libc::c_char, sys::SDL_GetError}; +use sdl2::{libc::c_char, sys::*}; fn get_sdl_error_message()->&'static str{ unsafe{ @@ -35,6 +33,30 @@ fn get_sdl_error_message()->&'static str{ } } +fn init_sdl_audio_device(audio_spec:&SDL_AudioSpec)->SDL_AudioDeviceID{ + let mut uninit_audio_spec:MaybeUninit = MaybeUninit::uninit(); + + unsafe{ + SDL_Init(SDL_INIT_AUDIO); + SDL_ClearError(); + let id = SDL_OpenAudioDevice(std::ptr::null(), 0, 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 != audio_spec.freq { + std::panic!("Error initializing audio could not use the frequency: {}", audio_spec.freq); + } + + //This will start the audio processing + SDL_PauseAudioDevice(id, 0); + return id; + } +} + pub trait AudioResampler{ fn new(original_frequency:u32, target_frequency:u32)->Self; fn resample(&mut self, buffer:&[StereoSample; BUFFER_SIZE])->Vec; diff --git a/gb/src/audio/sdl_pull_audio_device.rs b/gb/src/audio/sdl_pull_audio_device.rs index 527020a0..3406e573 100644 --- a/gb/src/audio/sdl_pull_audio_device.rs +++ b/gb/src/audio/sdl_pull_audio_device.rs @@ -1,6 +1,6 @@ -use std::{ffi::c_void, mem::MaybeUninit}; +use std::ffi::c_void; use lib_gb::{GB_FREQUENCY, apu::audio_device::*}; -use super::{AudioResampler, ResampledAudioDevice, get_sdl_error_message}; +use super::{AudioResampler, ResampledAudioDevice, init_sdl_audio_device}; use sdl2::sys::*; use crossbeam_channel::{Receiver, SendError, Sender, bounded}; @@ -59,27 +59,8 @@ impl ResampledAudioDevice for SdlPullAudioDevice{ userdata: (&mut device.userdata) as *mut UserData as *mut c_void }; - - let mut uninit_audio_spec:MaybeUninit = 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); - }; + // Ignore device id + init_sdl_audio_device(&desired_audio_spec); return device; } diff --git a/gb/src/audio/sdl_push_audio_device.rs b/gb/src/audio/sdl_push_audio_device.rs index 33f1c5a1..d4c6ac58 100644 --- a/gb/src/audio/sdl_push_audio_device.rs +++ b/gb/src/audio/sdl_push_audio_device.rs @@ -1,7 +1,7 @@ -use std::{ffi::c_void, mem::MaybeUninit, str::FromStr}; +use std::{ffi::c_void, str::FromStr}; use lib_gb::{GB_FREQUENCY, apu::audio_device::{AudioDevice, BUFFER_SIZE, DEFAULT_SAPMPLE, Sample, StereoSample}}; use sdl2::sys::*; -use super::{AudioResampler, ResampledAudioDevice, get_sdl_error_message}; +use super::{AudioResampler, ResampledAudioDevice, get_sdl_error_message, init_sdl_audio_device}; //After twicking those numbers Iv reached this, this will affect fps which will affect sound tearing const BYTES_TO_WAIT:u32 = BUFFER_SIZE as u32 * 16; @@ -49,28 +49,8 @@ impl ResampledAudioDevice for SdlPushAudioDevice{ }; - let mut uninit_audio_spec:MaybeUninit = MaybeUninit::uninit(); + let device_id = init_sdl_audio_device(&desired_audio_spec); - let device_id = 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); - - id - }; return SdlPushAudioDevice{ device_id: device_id, buffer:[DEFAULT_SAPMPLE;BUFFER_SIZE], From 985cd8557cf0e6a263e2523eed6721ec54a909f1 Mon Sep 17 00:00:00 2001 From: alloncm Date: Thu, 4 Nov 2021 21:36:44 +0200 Subject: [PATCH 34/36] Replace the clone in the audio pull --- gb/src/audio/mod.rs | 2 +- gb/src/audio/sdl_pull_audio_device.rs | 39 ++++++++++++++------------- gb/src/audio/sdl_push_audio_device.rs | 2 +- 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/gb/src/audio/mod.rs b/gb/src/audio/mod.rs index fe91c04a..bb17c34e 100644 --- a/gb/src/audio/mod.rs +++ b/gb/src/audio/mod.rs @@ -81,6 +81,6 @@ pub trait ResampledAudioDevice : AudioDevice{ 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>; + fn full_buffer_callback(&mut self)->Result<(), String>; fn new(frequency:i32, turbo_mul:u8)->Self; } diff --git a/gb/src/audio/sdl_pull_audio_device.rs b/gb/src/audio/sdl_pull_audio_device.rs index 3406e573..ae9ec239 100644 --- a/gb/src/audio/sdl_pull_audio_device.rs +++ b/gb/src/audio/sdl_pull_audio_device.rs @@ -3,36 +3,32 @@ use lib_gb::{GB_FREQUENCY, apu::audio_device::*}; use super::{AudioResampler, ResampledAudioDevice, init_sdl_audio_device}; use sdl2::sys::*; -use crossbeam_channel::{Receiver, SendError, Sender, bounded}; +use crossbeam_channel::{Receiver, Sender, bounded}; +const BUFFERS_NUMBER:usize = 3; struct UserData{ - rx: Receiver<[Sample;BUFFER_SIZE]>, - current_buf: Option<[Sample;BUFFER_SIZE]>, + rx: Receiver, + current_buf: Option, current_buf_index:usize, } pub struct SdlPullAudioDevice{ resampler: AR, - buffer: [Sample;BUFFER_SIZE], + buffers: [[Sample;BUFFER_SIZE];BUFFERS_NUMBER], + buffer_number_index:usize, buffer_index:usize, - tarnsmiter: Sender<[Sample;BUFFER_SIZE]>, + tarnsmiter: Sender, userdata: UserData } -impl SdlPullAudioDevice{ - fn push_audio_to_device(&self, audio:&[Sample; BUFFER_SIZE])->Result<(), SendError<[Sample; BUFFER_SIZE]>>{ - self.tarnsmiter.send(audio.clone()) - } -} - impl ResampledAudioDevice for SdlPullAudioDevice{ fn new(frequency:i32, turbo_mul:u8)->Self{ // cap of less than 2 hurts the fps - let(s,r) = bounded(2); + let(s,r) = bounded(BUFFERS_NUMBER - 1); let data = UserData{ current_buf:Option::None, current_buf_index:0, @@ -40,8 +36,9 @@ impl ResampledAudioDevice for SdlPullAudioDevice{ }; let mut device = SdlPullAudioDevice{ - buffer:[DEFAULT_SAPMPLE;BUFFER_SIZE], + buffers:[[DEFAULT_SAPMPLE;BUFFER_SIZE];BUFFERS_NUMBER], buffer_index:0, + buffer_number_index:0, resampler: AudioResampler::new(GB_FREQUENCY * turbo_mul as u32, frequency as u32), tarnsmiter:s, userdata:data @@ -64,15 +61,21 @@ impl ResampledAudioDevice for SdlPullAudioDevice{ return device; } + + fn full_buffer_callback(&mut self) ->Result<(), String> { + let result = self.tarnsmiter.send(self.buffers[self.buffer_number_index].as_ptr() as usize).map_err(|e|e.to_string()); + self.buffer_number_index = (self.buffer_number_index + 1) % BUFFERS_NUMBER; + + return result; + } + fn get_audio_buffer(&mut self) ->(&mut [Sample;BUFFER_SIZE], &mut usize) { - (&mut self.buffer, &mut self.buffer_index) + (&mut self.buffers[self.buffer_number_index], &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 AudioDevice for SdlPullAudioDevice{ @@ -89,7 +92,7 @@ unsafe extern "C" fn audio_callback(userdata:*mut c_void, buffer:*mut u8, length safe_userdata.current_buf = Some(safe_userdata.rx.recv().unwrap()); } - let samples = safe_userdata.current_buf.unwrap(); + let samples = &*((safe_userdata.current_buf.unwrap()) as *const [Sample;BUFFER_SIZE]); let samples_size = (samples.len() * std::mem::size_of::()) - safe_userdata.current_buf_index; let samples_ptr = (samples.as_ptr() as *mut u8).add(safe_userdata.current_buf_index); std::ptr::copy_nonoverlapping(samples_ptr, buffer, std::cmp::min(length, samples_size)); diff --git a/gb/src/audio/sdl_push_audio_device.rs b/gb/src/audio/sdl_push_audio_device.rs index d4c6ac58..dbe1ec08 100644 --- a/gb/src/audio/sdl_push_audio_device.rs +++ b/gb/src/audio/sdl_push_audio_device.rs @@ -67,7 +67,7 @@ impl ResampledAudioDevice for SdlPushAudioDevice{ &mut self.resampler } - fn full_buffer_callback(&self)->Result<(), String> { + fn full_buffer_callback(&mut self)->Result<(), String> { self.push_audio_to_device(&self.buffer).map_err(|e|String::from_str(e).unwrap()) } } From 521d1ea7d74a205093007e972edf3cf719aa9c04 Mon Sep 17 00:00:00 2001 From: alloncm Date: Thu, 4 Nov 2021 21:47:03 +0200 Subject: [PATCH 35/36] I think it supposed to close it gracefuly --- gb/src/audio/sdl_pull_audio_device.rs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/gb/src/audio/sdl_pull_audio_device.rs b/gb/src/audio/sdl_pull_audio_device.rs index ae9ec239..efacca24 100644 --- a/gb/src/audio/sdl_pull_audio_device.rs +++ b/gb/src/audio/sdl_pull_audio_device.rs @@ -20,8 +20,8 @@ pub struct SdlPullAudioDevice{ buffer_index:usize, tarnsmiter: Sender, - - userdata: UserData + userdata: UserData, + device_id:SDL_AudioDeviceID, } impl ResampledAudioDevice for SdlPullAudioDevice{ @@ -41,7 +41,8 @@ impl ResampledAudioDevice for SdlPullAudioDevice{ buffer_number_index:0, resampler: AudioResampler::new(GB_FREQUENCY * turbo_mul as u32, frequency as u32), tarnsmiter:s, - userdata:data + userdata:data, + device_id:0 }; let desired_audio_spec = SDL_AudioSpec{ @@ -57,7 +58,7 @@ impl ResampledAudioDevice for SdlPullAudioDevice{ }; // Ignore device id - init_sdl_audio_device(&desired_audio_spec); + device.device_id = init_sdl_audio_device(&desired_audio_spec); return device; } @@ -84,6 +85,14 @@ impl AudioDevice for SdlPullAudioDevice{ } } +impl Drop for SdlPullAudioDevice{ + fn drop(&mut self) { + unsafe{ + SDL_CloseAudioDevice(self.device_id); + } + } +} + unsafe extern "C" fn audio_callback(userdata:*mut c_void, buffer:*mut u8, length:i32){ let length = length as usize; let safe_userdata = &mut *(userdata as *mut UserData); From 133caa1f30244d45564fde03927d9bc3c235b8d7 Mon Sep 17 00:00:00 2001 From: alloncm Date: Thu, 4 Nov 2021 21:49:57 +0200 Subject: [PATCH 36/36] Inline the apu timer According to the benchmark this has improved performance a bit :) --- lib_gb/src/apu/timer.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/lib_gb/src/apu/timer.rs b/lib_gb/src/apu/timer.rs index ea7d3b67..ba05996e 100644 --- a/lib_gb/src/apu/timer.rs +++ b/lib_gb/src/apu/timer.rs @@ -13,6 +13,7 @@ impl Timer{ } // This function is a hot spot for the APU, almost every component uses the timer + #[inline] pub fn cycle(&mut self)->bool{ if self.cycles_to_tick != 0{ // The calculation used to be this: