From 2d8f50a4023597cc8e262ccfa9a2fb43c14981d0 Mon Sep 17 00:00:00 2001 From: alloncm Date: Fri, 4 Jul 2025 14:02:52 +0300 Subject: [PATCH 01/11] Initial impl for the SDL frontend --- common/src/menu.rs | 52 +++++++++++++++++++++++++++++++-------- sdl/src/main.rs | 13 +++++++--- sdl/src/sdl_gfx_device.rs | 36 +++++++++++++++++++-------- 3 files changed, 77 insertions(+), 24 deletions(-) diff --git a/common/src/menu.rs b/common/src/menu.rs index a982fb75..26d68f9b 100644 --- a/common/src/menu.rs +++ b/common/src/menu.rs @@ -8,12 +8,14 @@ pub struct MenuOption>{ #[derive(Debug, Clone, Copy)] pub enum EmulatorMenuOption{ Resume, + Turbo, Restart, Shutdown } -pub const GAME_MENU_OPTIONS:[MenuOption;3] = [ +pub const GAME_MENU_OPTIONS:[MenuOption; 4] = [ MenuOption{prompt:"Resume", value:EmulatorMenuOption::Resume}, + MenuOption{prompt:"Turbo", value:EmulatorMenuOption::Turbo}, MenuOption{prompt:"Restart", value:EmulatorMenuOption::Restart}, MenuOption{prompt:"Shutdown", value:EmulatorMenuOption::Shutdown} ]; @@ -25,15 +27,22 @@ cfg_if::cfg_if!{ if #[cfg(feature = "std")]{ pub struct MagenBoyState{ // Use atomic bool, normal bool doesnt works on arm (probably cause of the memory model) - pub running:AtomicBool, - pub pause:AtomicBool, - pub exit:AtomicBool, - pub state_mutex:Mutex<()> + pub running: AtomicBool, + pub turbo: AtomicBool, + pub pause: AtomicBool, + pub exit: AtomicBool, + pub state_mutex: Mutex<()> } impl MagenBoyState{ pub const fn new() -> Self { - Self { running: AtomicBool::new(true), pause: AtomicBool::new(false), exit: AtomicBool::new(false), state_mutex: Mutex::new(()) } + Self { + running: AtomicBool::new(true), + turbo: AtomicBool::new(false), + pause: AtomicBool::new(false), + exit: AtomicBool::new(false), + state_mutex: Mutex::new(()) + } } } @@ -47,9 +56,19 @@ cfg_if::cfg_if!{ if #[cfg(feature = "std")]{ Self { provider, header } } - pub fn pop_game_menu(&mut self, state:&MagenBoyState, gfx_device:&mut GFX, receiver:crossbeam_channel::Receiver){ + pub fn pop_game_menu( + &mut self, + state: &MagenBoyState, + gfx_device: &mut GFX, + receiver: crossbeam_channel::Receiver + ) { match self.get_game_menu_selection(state, gfx_device, receiver){ EmulatorMenuOption::Resume => {}, + EmulatorMenuOption::Turbo => { + let new_turbo_state = !state.turbo.load(std::sync::atomic::Ordering::Relaxed); + state.turbo.store(new_turbo_state, std::sync::atomic::Ordering::Relaxed); + log::info!("Turbo mode is: {new_turbo_state}"); + }, EmulatorMenuOption::Restart => state.running.store(false, std::sync::atomic::Ordering::Relaxed), EmulatorMenuOption::Shutdown => { state.running.store(false, std::sync::atomic::Ordering::Relaxed); @@ -58,7 +77,12 @@ cfg_if::cfg_if!{ if #[cfg(feature = "std")]{ } } - fn get_game_menu_selection(&mut self, state:&MagenBoyState,gfx_device:&mut GFX, emulation_framebuffer_channel:crossbeam_channel::Receiver)->&EmulatorMenuOption{ + fn get_game_menu_selection( + &mut self, + state: &MagenBoyState, + gfx_device: &mut GFX, + emulation_framebuffer_channel: crossbeam_channel::Receiver + ) -> &EmulatorMenuOption { let menu_renderer = joypad_gfx_menu::GfxDeviceMenuRenderer::new(gfx_device); let mut menu = JoypadMenu::new(&GAME_MENU_OPTIONS, &self.header, menu_renderer); @@ -67,10 +91,14 @@ cfg_if::cfg_if!{ if #[cfg(feature = "std")]{ state.pause.store(true, std::sync::atomic::Ordering::SeqCst); loop{ if let Ok(_lock) = state.state_mutex.try_lock(){ + // Turn off turbo to have a regular speed menu + let last_turbo = state.turbo.swap(false, core::sync::atomic::Ordering::Relaxed); let selection = menu.get_menu_selection(&mut self.provider); + // restore turbo state + state.turbo.store(last_turbo, core::sync::atomic::Ordering::Relaxed); state.pause.store(false, std::sync::atomic::Ordering::SeqCst); return selection; - }else{ + } else { // try recv in order to clear frames from the channel // in order to not block the emualtion thread and allow it to finish the frame let _ = emulation_framebuffer_channel.try_recv(); @@ -80,7 +108,11 @@ cfg_if::cfg_if!{ if #[cfg(feature = "std")]{ } - pub fn get_rom_selection, JP:MenuJoypadProvider + JoypadProvider>(roms_path:&str, menu_renderer:MR, jp:&mut JP)->String{ + pub fn get_rom_selection, JP:MenuJoypadProvider + JoypadProvider>( + roms_path: &str, + menu_renderer: MR, + jp:&mut JP + ) -> String { let mut menu_options = Vec::new(); let dir_entries = std::fs::read_dir(roms_path).expect(std::format!("Error openning the roms directory: {}",roms_path).as_str()); for entry in dir_entries{ diff --git a/sdl/src/main.rs b/sdl/src/main.rs index 18e7df0b..1f71f5cd 100644 --- a/sdl/src/main.rs +++ b/sdl/src/main.rs @@ -13,7 +13,7 @@ use sdl2::sys::*; use crate::{sdl_gfx_device::SdlGfxDevice, audio::*, SdlAudioDevice}; -const TURBO_MUL:u8 = 1; +const TURBO_FACTOR:u8 = 4; const SCREEN_SCALE:usize = 4; use sdl2::sys::SDL_Scancode; @@ -38,8 +38,13 @@ fn main() { } // Initialize the gfx first cause it initialize both the screen and the sdl context for the joypad - let mut gfx_device: SdlGfxDevice = SdlGfxDevice::new(header.as_str(), SCREEN_SCALE, TURBO_MUL, - check_for_terminal_feature_flag(&args, "--no-vsync"), check_for_terminal_feature_flag(&args, "--full-screen")); + let mut gfx_device: SdlGfxDevice = SdlGfxDevice::new( + header.as_str(), + SCREEN_SCALE, + TURBO_FACTOR, + check_for_terminal_feature_flag(&args, "--no-vsync"), + check_for_terminal_feature_flag(&args, "--full-screen") + ); while !(EMULATOR_STATE.exit.load(std::sync::atomic::Ordering::Relaxed)){ let mut provider = sdl_joypad_provider::SdlJoypadProvider::new(KEYBOARD_MAPPING, true); @@ -112,7 +117,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, #[cfg(feature = "dbg")] debugger_sender: crossbeam_channel::Sender) { let mut devices: Vec::> = Vec::new(); - let audio_device = SdlAudioDevice::::new(44100, TURBO_MUL); + let audio_device = SdlAudioDevice::::new(44100, TURBO_FACTOR); devices.push(Box::new(audio_device)); if check_for_terminal_feature_flag(&args, "--file-audio"){ diff --git a/sdl/src/sdl_gfx_device.rs b/sdl/src/sdl_gfx_device.rs index d762db27..c43233bb 100644 --- a/sdl/src/sdl_gfx_device.rs +++ b/sdl/src/sdl_gfx_device.rs @@ -1,6 +1,10 @@ use std::ffi::{CString, c_void}; + use sdl2::sys::*; + +use magenboy_common::EMULATOR_STATE; use magenboy_core::{ppu::gb_ppu::{SCREEN_HEIGHT, SCREEN_WIDTH}, utils::vec2::Vec2, GfxDevice, Pixel}; + use super::utils::get_sdl_error_message; // The bit order is high bits -> low bits as opposed to RGB555 in the gbdev docs which is low -> high. @@ -75,23 +79,32 @@ impl Drop for SdlWindow{ pub struct SdlGfxDevice{ sdl_window:SdlWindow, - discard:u8, - turbo_mul:u8, + turbo_counter:u8, + turbo_factor:u8, } impl SdlGfxDevice{ - pub fn new(window_name:&str, screen_scale: usize, turbo_mul:u8, disable_vsync:bool, full_screen:bool)->Self{ + pub fn new(window_name: &str, screen_scale: usize, turbo_factor:u8, disable_vsync:bool, full_screen:bool)->Self{ - let window_flags = if full_screen{ + let window_flags = if full_screen { // Hide cursor unsafe{SDL_ShowCursor(0);} SDL_WindowFlags::SDL_WINDOW_FULLSCREEN_DESKTOP as u32 - } - else{ + } else { SDL_WindowFlags::SDL_WINDOW_RESIZABLE as u32 }; - return Self{discard:0, turbo_mul, sdl_window: SdlWindow::new(window_name, Vec2{x:SCREEN_WIDTH, y:SCREEN_HEIGHT}, screen_scale, disable_vsync, window_flags)}; + return Self{ + turbo_counter: 0, + turbo_factor, + sdl_window: SdlWindow::new( + window_name, + Vec2{x: SCREEN_WIDTH, y: SCREEN_HEIGHT}, + screen_scale, + disable_vsync, + window_flags + ) + }; } pub fn poll_event(&self)->Option{ @@ -109,10 +122,13 @@ impl SdlGfxDevice{ impl GfxDevice for SdlGfxDevice{ fn swap_buffer(&mut self, buffer:&[Pixel; SCREEN_HEIGHT * SCREEN_WIDTH]) { - self.discard = (self.discard + 1) % self.turbo_mul; - if self.discard != 0{ - return; + if EMULATOR_STATE.turbo.load(std::sync::atomic::Ordering::Relaxed) { + self.turbo_counter = (self.turbo_counter + 1) % self.turbo_factor; + if self.turbo_counter != 0{ + return; + } } + self.sdl_window.render(buffer); } } From bae82481c4f7b34a6efeea5c39692aee68c90a03 Mon Sep 17 00:00:00 2001 From: alloncm Date: Sun, 17 Aug 2025 14:52:13 +0300 Subject: [PATCH 02/11] Move to arc (not sure why I havent commited this) --- sdl/src/audio/sdl_audio_device.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sdl/src/audio/sdl_audio_device.rs b/sdl/src/audio/sdl_audio_device.rs index 745077e8..a7854990 100644 --- a/sdl/src/audio/sdl_audio_device.rs +++ b/sdl/src/audio/sdl_audio_device.rs @@ -1,4 +1,4 @@ -use std::{ffi::c_void, mem::{ManuallyDrop, MaybeUninit}}; +use std::{ffi::c_void, mem::{ManuallyDrop, MaybeUninit}, sync::Arc}; use crossbeam_channel::{bounded, Receiver, Sender}; use sdl2::sys::*; @@ -18,7 +18,7 @@ struct UserData{ pub struct SdlAudioDevice{ resampler: AR, - buffers: [[Sample;BUFFER_SIZE];BUFFERS_NUMBER], + buffers: [Arc<[Sample;BUFFER_SIZE]>; BUFFERS_NUMBER], buffer_number_index:usize, buffer_index:usize, @@ -42,7 +42,7 @@ impl ResampledAudioDevice for SdlAudioDevice{ }); let mut device = SdlAudioDevice{ - buffers:[[DEFAULT_SAPMPLE;BUFFER_SIZE];BUFFERS_NUMBER], + buffers:[Arc::new([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), From d48923e4027b56f88446858b2e236bd8b76e2dff Mon Sep 17 00:00:00 2001 From: alloncm Date: Tue, 23 Sep 2025 00:08:00 +0300 Subject: [PATCH 03/11] Improve the nx target compilation speed --- .dockerignore | 4 ---- Makefile.toml | 11 ++++++++++- nx/Dockerfile | 26 +++++--------------------- nx/Makefile | 2 +- 4 files changed, 16 insertions(+), 27 deletions(-) delete mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 487e7668..00000000 --- a/.dockerignore +++ /dev/null @@ -1,4 +0,0 @@ -*target/ -.github/ -docs/ -*Dockerfile \ No newline at end of file diff --git a/Makefile.toml b/Makefile.toml index 0543769c..4d73502e 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -84,9 +84,18 @@ args = ["ndk", "--target=aarch64-linux-android", "build", "--release", "--packag dependencies = ["add_android_target"] [tasks.add_android_target] +private = true command = "rustup" args = ["target", "add", "aarch64-linux-android"] +[tasks.build_mabenboy_nx] +private = true +toolchain = "${nightly_version}" +command = "cargo" +args = ["build", "--release", "--package", "magenboy_nx", "--target", "aarch64-nintendo-switch-freestanding", "-Z", "build-std=core,compiler_builtins,alloc", "-Z", "build-std-features=compiler-builtins-mem"] +dependencies = ["nightly-install", "install_rust_src"] + [tasks.nx] command = "docker" -args = ["build", "--progress=plain", ".", "--file", "nx/Dockerfile", "--target", "export", "--output=.", "--build-arg", "NIGHTLY=${nightly_version}"] \ No newline at end of file +args = ["build", "--progress=plain", ".", "--file", "nx/Dockerfile", "--target", "export", "--output=." ] +dependencies = ["build_mabenboy_nx"] \ No newline at end of file diff --git a/nx/Dockerfile b/nx/Dockerfile index 7f133fc4..82c29f69 100644 --- a/nx/Dockerfile +++ b/nx/Dockerfile @@ -1,33 +1,17 @@ -# We are installing the Rust toolchain so the version does not matter -FROM rust:latest AS builder - -# Nightly version - entered as a build argument -ARG NIGHTLY - -RUN rustup toolchain install ${NIGHTLY} -RUN rustup +${NIGHTLY} component add rust-src - -WORKDIR /magenboy - -COPY . . - -RUN cargo +${NIGHTLY} build --release --package magenboy_nx --target aarch64-nintendo-switch-freestanding \ - -Z build-std=core,compiler_builtins,alloc -Z build-std-features=compiler-builtins-mem - FROM devkitpro/devkita64 AS final WORKDIR /magenboy_nx COPY nx/Makefile ./ +# C code, copies also the Rust code cause im lazy to filter COPY nx/src ./src - -# Copy the built Rust library from the builder stage -COPY --from=builder /magenboy/target/ target/ +# aarch64-nintendo-switch-freestanding Rust artifacts +COPY target/aarch64-nintendo-switch-freestanding/ target/ # Needs to be run as the same RUN statement since the shell session is not shared between RUN statements # Without this the Makefile will not be able to find the version and authors -RUN export VERSION=$(cat target/aarch64-nintendo-switch-freestanding/release/version.txt) && \ - export AUTHORS=$(cat target/aarch64-nintendo-switch-freestanding/release/authors.txt) && \ +RUN export VERSION=$(cat target/release/version.txt) && \ + export AUTHORS=$(cat target/release/authors.txt) && \ make --always-make # Export the built files using a scratch image, this is the best practice for multi-stage builds diff --git a/nx/Makefile b/nx/Makefile index cbf2bd4a..7fc08864 100644 --- a/nx/Makefile +++ b/nx/Makefile @@ -114,7 +114,7 @@ export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ -I$(CURDIR)/$(BUILD) export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) \ - -L$(abspath target/aarch64-nintendo-switch-freestanding/release) + -L$(abspath target/release) ifeq ($(strip $(CONFIG_JSON)),) jsons := $(wildcard *.json) From 37518ac1445f406a969924384be0f29d9da817a0 Mon Sep 17 00:00:00 2001 From: alloncm Date: Tue, 23 Sep 2025 01:47:15 +0300 Subject: [PATCH 04/11] Fix sound issues in the sdl FE and move the turbo frame skipper to a common struct --- common/src/audio/audio_resampler.rs | 3 +- common/src/audio/manual_audio_resampler.rs | 79 +++++++++++++++------- common/src/initialization.rs | 3 +- common/src/menu.rs | 51 +++++++++++--- sdl/src/audio/sdl_audio_device.rs | 14 ++-- sdl/src/main.rs | 7 +- sdl/src/sdl_gfx_device.rs | 15 +--- 7 files changed, 114 insertions(+), 58 deletions(-) diff --git a/common/src/audio/audio_resampler.rs b/common/src/audio/audio_resampler.rs index 6ed046a0..abe9d1b8 100644 --- a/common/src/audio/audio_resampler.rs +++ b/common/src/audio/audio_resampler.rs @@ -5,6 +5,7 @@ use magenboy_core::apu::audio_device::{Sample, AudioDevice, StereoSample, BUFFER pub trait AudioResampler{ fn new(original_frequency:u32, target_frequency:u32)->Self; fn resample(&mut self, buffer:&[StereoSample; BUFFER_SIZE])->Vec; + fn set_original_frequency(&mut self, original_frequency:u32); } pub trait ResampledAudioDevice : AudioDevice{ @@ -25,5 +26,5 @@ 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(&mut self)->Result<(), String>; - fn new(frequency:i32, turbo_mul:u8)->Self; + fn new(frequency:i32)->Self; } diff --git a/common/src/audio/manual_audio_resampler.rs b/common/src/audio/manual_audio_resampler.rs index e11fda80..2524fc60 100644 --- a/common/src/audio/manual_audio_resampler.rs +++ b/common/src/audio/manual_audio_resampler.rs @@ -5,6 +5,9 @@ use magenboy_core::apu::audio_device::{BUFFER_SIZE, StereoSample}; use super::audio_resampler::AudioResampler; pub struct ManualAudioResampler{ + target_frequency:u32, + original_frequency:u32, + to_skip:u32, sampling_buffer:Vec, sampling_counter:u32, @@ -16,35 +19,22 @@ pub struct ManualAudioResampler{ impl AudioResampler for ManualAudioResampler{ 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; - - // Sicne we dont have many f32 methods without std we are implementing them ourself - let lower_to_skip = libm::floorf(div) as u32; - let upper_to_skip = libm::ceilf(div) as u32; - let mut reminder = div - (div as u32 as f32); // Acts as f32::fracts (since inputs are unsigned) - 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) + let mut resampler = ManualAudioResampler{ + target_frequency, + original_frequency: 0, // Will be set in set_original_frequency + to_skip: 0, + sampling_buffer: Vec::new(), + sampling_counter: 0, + reminder_steps: 0.0, + reminder_counter: 0.0, + alternate_to_skip: 0, + skip_to_use: 0, }; - if lower_to_skip == 0{ - core::panic!("target freqency is too high: {}", target_frequency); - } + resampler.set_original_frequency(original_frequency); - ManualAudioResampler{ - to_skip:to_skip, - sampling_buffer:Vec::with_capacity(upper_to_skip as usize), - sampling_counter: 0, - reminder_steps:reminder, - reminder_counter:0.0, - alternate_to_skip: alt_to_skip, - skip_to_use:to_skip - } + return resampler; } fn resample(&mut self, buffer:&[StereoSample; BUFFER_SIZE])->Vec{ @@ -72,4 +62,43 @@ impl AudioResampler for ManualAudioResampler{ return output; } + + fn set_original_frequency(&mut self, original_frequency:u32) { + if original_frequency == self.original_frequency{ + // No need to reconfigure resampler parameters + return; + } + + log::info!("Reconfiguring audio resampler from {} to {}", self.original_frequency, original_frequency); + + // Calling round in order to get the nearest integer and resample as precise as possible + let div = original_frequency as f32 / self.target_frequency as f32; + + // Sicne we dont have many f32 methods without std we are implementing them ourself + let lower_to_skip = libm::floorf(div) as u32; + let upper_to_skip = libm::ceilf(div) as u32; + let mut reminder = div - (div as u32 as f32); // Acts as f32::fracts (since inputs are unsigned) + + 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{ + core::panic!("target freqency is too high: {} for original frequency: {}", self.target_frequency, original_frequency); + } + + self.to_skip = to_skip; + self.alternate_to_skip = alt_to_skip; + self.skip_to_use = to_skip; + self.reminder_steps = reminder; + self.original_frequency = original_frequency; + // Reset buffers to avoid issues + self.sampling_buffer.clear(); + self.sampling_counter = 0; + self.reminder_counter = 0.0; + } } \ No newline at end of file diff --git a/common/src/initialization.rs b/common/src/initialization.rs index ee8569a0..19758573 100644 --- a/common/src/initialization.rs +++ b/common/src/initialization.rs @@ -15,8 +15,9 @@ pub fn get_terminal_feature_flag_value(args:&Vec, flag:&str, error_messa return args.get(index + 1).expect(error_message).clone(); } +const TURBO_FACTOR:u32 = 4; // This is static and not local for the unix signal handler to access it -pub static EMULATOR_STATE:MagenBoyState = MagenBoyState::new(); +pub static EMULATOR_STATE:MagenBoyState = MagenBoyState::new(TURBO_FACTOR); pub fn init_and_run_gameboy( args: Vec, diff --git a/common/src/menu.rs b/common/src/menu.rs index 26d68f9b..f1d6f287 100644 --- a/common/src/menu.rs +++ b/common/src/menu.rs @@ -1,3 +1,5 @@ +use core::sync::atomic::{AtomicBool, Ordering, AtomicU32}; + #[derive(Default, Clone, Copy)] pub struct MenuOption>{ pub value:T, @@ -20,25 +22,58 @@ pub const GAME_MENU_OPTIONS:[MenuOption; 4] = [ MenuOption{prompt:"Shutdown", value:EmulatorMenuOption::Shutdown} ]; +pub struct Turbo { + pub enabled: AtomicBool, + factor: u32, + counter: AtomicU32, +} + +impl Turbo { + pub const fn new(factor: u32) -> Self { + Self { enabled: AtomicBool::new(false), factor, counter: AtomicU32::new(0) } + } + + /// Updates the internal counter and returns true if a frame should be rendered + /// Returns true if turbo is disabled + pub fn update_and_check(&self) -> bool { + if self.enabled.load(Ordering::Relaxed) { + let value = self.counter.load(Ordering::SeqCst); + let updated_value = (value + 1) % self.factor; + self.counter.store(updated_value, Ordering::SeqCst); + return updated_value == 0; + } + return true; + } + + pub fn get_factor(&self) -> u32 { + if self.enabled.load(Ordering::Relaxed) { + self.factor + } else { + 1 + } + } +} + + cfg_if::cfg_if!{ if #[cfg(feature = "std")]{ - use std::{sync::{atomic::AtomicBool, Mutex}, path::PathBuf}; + use std::{sync::Mutex, path::PathBuf}; use magenboy_core::{ppu::gfx_device::GfxDevice, keypad::joypad_provider::JoypadProvider}; use super::joypad_menu::{MenuJoypadProvider, joypad_gfx_menu, JoypadMenu, MenuRenderer}; pub struct MagenBoyState{ // Use atomic bool, normal bool doesnt works on arm (probably cause of the memory model) pub running: AtomicBool, - pub turbo: AtomicBool, + pub turbo: Turbo, pub pause: AtomicBool, pub exit: AtomicBool, pub state_mutex: Mutex<()> } impl MagenBoyState{ - pub const fn new() -> Self { + pub const fn new(turbo_factor: u32) -> Self { Self { running: AtomicBool::new(true), - turbo: AtomicBool::new(false), + turbo: Turbo::new(turbo_factor), pause: AtomicBool::new(false), exit: AtomicBool::new(false), state_mutex: Mutex::new(()) @@ -65,8 +100,8 @@ cfg_if::cfg_if!{ if #[cfg(feature = "std")]{ match self.get_game_menu_selection(state, gfx_device, receiver){ EmulatorMenuOption::Resume => {}, EmulatorMenuOption::Turbo => { - let new_turbo_state = !state.turbo.load(std::sync::atomic::Ordering::Relaxed); - state.turbo.store(new_turbo_state, std::sync::atomic::Ordering::Relaxed); + let new_turbo_state = !state.turbo.enabled.load(std::sync::atomic::Ordering::Relaxed); + state.turbo.enabled.store(new_turbo_state, std::sync::atomic::Ordering::Relaxed); log::info!("Turbo mode is: {new_turbo_state}"); }, EmulatorMenuOption::Restart => state.running.store(false, std::sync::atomic::Ordering::Relaxed), @@ -92,10 +127,10 @@ cfg_if::cfg_if!{ if #[cfg(feature = "std")]{ loop{ if let Ok(_lock) = state.state_mutex.try_lock(){ // Turn off turbo to have a regular speed menu - let last_turbo = state.turbo.swap(false, core::sync::atomic::Ordering::Relaxed); + let last_turbo = state.turbo.enabled.swap(false, core::sync::atomic::Ordering::Relaxed); let selection = menu.get_menu_selection(&mut self.provider); // restore turbo state - state.turbo.store(last_turbo, core::sync::atomic::Ordering::Relaxed); + state.turbo.enabled.store(last_turbo, core::sync::atomic::Ordering::Relaxed); state.pause.store(false, std::sync::atomic::Ordering::SeqCst); return selection; } else { diff --git a/sdl/src/audio/sdl_audio_device.rs b/sdl/src/audio/sdl_audio_device.rs index a7854990..da248828 100644 --- a/sdl/src/audio/sdl_audio_device.rs +++ b/sdl/src/audio/sdl_audio_device.rs @@ -1,10 +1,10 @@ -use std::{ffi::c_void, mem::{ManuallyDrop, MaybeUninit}, sync::Arc}; +use std::{ffi::c_void, mem::{ManuallyDrop, MaybeUninit}}; use crossbeam_channel::{bounded, Receiver, Sender}; use sdl2::sys::*; use magenboy_core::{GB_FREQUENCY, apu::audio_device::*}; -use magenboy_common::audio::{AudioResampler, ResampledAudioDevice}; +use magenboy_common::{audio::{AudioResampler, ResampledAudioDevice}, EMULATOR_STATE}; use crate::utils::get_sdl_error_message; @@ -18,7 +18,7 @@ struct UserData{ pub struct SdlAudioDevice{ resampler: AR, - buffers: [Arc<[Sample;BUFFER_SIZE]>; BUFFERS_NUMBER], + buffers: [[Sample;BUFFER_SIZE]; BUFFERS_NUMBER], buffer_number_index:usize, buffer_index:usize, @@ -32,7 +32,7 @@ pub struct SdlAudioDevice{ } impl ResampledAudioDevice for SdlAudioDevice{ - fn new(frequency:i32, turbo_mul:u8)->Self{ + fn new(frequency:i32)->Self{ // cap of less than 2 hurts the fps let(s,r) = bounded(BUFFERS_NUMBER - 1); let data = Box::new(UserData{ @@ -42,10 +42,10 @@ impl ResampledAudioDevice for SdlAudioDevice{ }); let mut device = SdlAudioDevice{ - buffers:[Arc::new([DEFAULT_SAPMPLE; BUFFER_SIZE]); BUFFERS_NUMBER], + 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), + resampler: AudioResampler::new(GB_FREQUENCY, frequency as u32), tarnsmiter: ManuallyDrop::new(s), userdata_ptr:Box::into_raw(data), device_id:0 @@ -99,6 +99,8 @@ impl ResampledAudioDevice for SdlAudioDevice{ } fn get_resampler(&mut self) ->&mut AR { + let factor = EMULATOR_STATE.turbo.get_factor(); + self.resampler.set_original_frequency(GB_FREQUENCY * factor); &mut self.resampler } } diff --git a/sdl/src/main.rs b/sdl/src/main.rs index 1f71f5cd..f357d33e 100644 --- a/sdl/src/main.rs +++ b/sdl/src/main.rs @@ -13,8 +13,6 @@ use sdl2::sys::*; use crate::{sdl_gfx_device::SdlGfxDevice, audio::*, SdlAudioDevice}; -const TURBO_FACTOR:u8 = 4; - const SCREEN_SCALE:usize = 4; use sdl2::sys::SDL_Scancode; const KEYBOARD_MAPPING:[SDL_Scancode; NUM_OF_KEYS] = [ @@ -40,8 +38,7 @@ fn main() { // Initialize the gfx first cause it initialize both the screen and the sdl context for the joypad let mut gfx_device: SdlGfxDevice = SdlGfxDevice::new( header.as_str(), - SCREEN_SCALE, - TURBO_FACTOR, + SCREEN_SCALE, check_for_terminal_feature_flag(&args, "--no-vsync"), check_for_terminal_feature_flag(&args, "--full-screen") ); @@ -117,7 +114,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, #[cfg(feature = "dbg")] debugger_sender: crossbeam_channel::Sender) { let mut devices: Vec::> = Vec::new(); - let audio_device = SdlAudioDevice::::new(44100, TURBO_FACTOR); + let audio_device = SdlAudioDevice::::new(44100); devices.push(Box::new(audio_device)); if check_for_terminal_feature_flag(&args, "--file-audio"){ diff --git a/sdl/src/sdl_gfx_device.rs b/sdl/src/sdl_gfx_device.rs index c43233bb..bd1ded43 100644 --- a/sdl/src/sdl_gfx_device.rs +++ b/sdl/src/sdl_gfx_device.rs @@ -79,12 +79,10 @@ impl Drop for SdlWindow{ pub struct SdlGfxDevice{ sdl_window:SdlWindow, - turbo_counter:u8, - turbo_factor:u8, } impl SdlGfxDevice{ - pub fn new(window_name: &str, screen_scale: usize, turbo_factor:u8, disable_vsync:bool, full_screen:bool)->Self{ + pub fn new(window_name: &str, screen_scale: usize, disable_vsync:bool, full_screen:bool)->Self{ let window_flags = if full_screen { // Hide cursor @@ -95,8 +93,6 @@ impl SdlGfxDevice{ }; return Self{ - turbo_counter: 0, - turbo_factor, sdl_window: SdlWindow::new( window_name, Vec2{x: SCREEN_WIDTH, y: SCREEN_HEIGHT}, @@ -122,14 +118,9 @@ impl SdlGfxDevice{ impl GfxDevice for SdlGfxDevice{ fn swap_buffer(&mut self, buffer:&[Pixel; SCREEN_HEIGHT * SCREEN_WIDTH]) { - if EMULATOR_STATE.turbo.load(std::sync::atomic::Ordering::Relaxed) { - self.turbo_counter = (self.turbo_counter + 1) % self.turbo_factor; - if self.turbo_counter != 0{ - return; - } + if EMULATOR_STATE.turbo.update_and_check() { + self.sdl_window.render(buffer); } - - self.sdl_window.render(buffer); } } From fd622614b852f20b8c867db2b95b977c75ae4472 Mon Sep 17 00:00:00 2001 From: alloncm Date: Tue, 23 Sep 2025 03:21:34 +0300 Subject: [PATCH 05/11] POC working on the switch --- common/src/menu.rs | 20 +++++++++++--------- nx/src/devices.rs | 27 +++++++++++++++------------ nx/src/lib.rs | 26 +++++++++++++++++--------- nx/src/magenboy.h | 4 +--- 4 files changed, 44 insertions(+), 33 deletions(-) diff --git a/common/src/menu.rs b/common/src/menu.rs index f1d6f287..c23cdc22 100644 --- a/common/src/menu.rs +++ b/common/src/menu.rs @@ -9,10 +9,10 @@ pub struct MenuOption>{ #[repr(u32)] #[derive(Debug, Clone, Copy)] pub enum EmulatorMenuOption{ - Resume, - Turbo, - Restart, - Shutdown + Resume = 0, + Restart = 1, + Shutdown = 2, + Turbo = 3, } pub const GAME_MENU_OPTIONS:[MenuOption; 4] = [ @@ -33,6 +33,12 @@ impl Turbo { Self { enabled: AtomicBool::new(false), factor, counter: AtomicU32::new(0) } } + pub fn toggle(&self) { + let new_turbo_state = !self.enabled.load(Ordering::Relaxed); + self.enabled.store(new_turbo_state, Ordering::Relaxed); + log::info!("Turbo mode is: {new_turbo_state}"); + } + /// Updates the internal counter and returns true if a frame should be rendered /// Returns true if turbo is disabled pub fn update_and_check(&self) -> bool { @@ -99,11 +105,7 @@ cfg_if::cfg_if!{ if #[cfg(feature = "std")]{ ) { match self.get_game_menu_selection(state, gfx_device, receiver){ EmulatorMenuOption::Resume => {}, - EmulatorMenuOption::Turbo => { - let new_turbo_state = !state.turbo.enabled.load(std::sync::atomic::Ordering::Relaxed); - state.turbo.enabled.store(new_turbo_state, std::sync::atomic::Ordering::Relaxed); - log::info!("Turbo mode is: {new_turbo_state}"); - }, + EmulatorMenuOption::Turbo => state.turbo.toggle(), EmulatorMenuOption::Restart => state.running.store(false, std::sync::atomic::Ordering::Relaxed), EmulatorMenuOption::Shutdown => { state.running.store(false, std::sync::atomic::Ordering::Relaxed); diff --git a/nx/src/devices.rs b/nx/src/devices.rs index cae271c7..0d4086d9 100644 --- a/nx/src/devices.rs +++ b/nx/src/devices.rs @@ -1,7 +1,7 @@ use core::ffi::c_int; -use magenboy_common::{audio::{ManualAudioResampler, AudioResampler}, joypad_menu::MenuJoypadProvider}; -use magenboy_core::{AudioDevice, GfxDevice, self, JoypadProvider, keypad::button::Button}; +use magenboy_common::{audio::{ManualAudioResampler, AudioResampler}, joypad_menu::MenuJoypadProvider, menu::Turbo}; +use magenboy_core::{AudioDevice, GfxDevice, self, JoypadProvider, keypad::button::Button, GB_FREQUENCY}; pub type JoypadProviderCallback = unsafe extern "C" fn() -> u64; pub type PollJoypadProviderCallback = unsafe extern "C" fn() -> u64; @@ -53,31 +53,34 @@ impl MenuJoypadProvider for NxJoypadProvider { pub type GfxDeviceCallback = unsafe extern "C" fn(buffer:*const u16) -> (); -pub(crate) struct NxGfxDevice{ +pub(crate) struct NxGfxDevice<'a>{ pub cb: GfxDeviceCallback, - pub turbo: u32, - pub frame_counter: u32, + pub turbo: &'a Turbo } -impl GfxDevice for NxGfxDevice{ +impl<'a> GfxDevice for NxGfxDevice<'a>{ fn swap_buffer(&mut self, buffer:&[magenboy_core::Pixel; magenboy_core::ppu::gb_ppu::SCREEN_HEIGHT * magenboy_core::ppu::gb_ppu::SCREEN_WIDTH]) { - if self.frame_counter % self.turbo == 0{ + if self.turbo.update_and_check() { unsafe{(self.cb)(buffer.as_ptr())}; } - self.frame_counter = (self.frame_counter + 1) % self.turbo; } } -pub type AudioDeviceCallback = unsafe extern "C" fn(buffer:*const magenboy_core::apu::audio_device::StereoSample, size:c_int) -> (); +// Since StereoSample is repr(C, packed), we can safely transmute it to a slice of i16 +// SAFETY: StereoSample is repr(C, packed) +pub type AudioDeviceCallback = unsafe extern "C" fn(buffer:*const magenboy_core::apu::audio_device::Sample, size:c_int) -> (); -pub(crate) struct NxAudioDevice{ +pub(crate) struct NxAudioDevice<'a>{ pub cb: AudioDeviceCallback, pub resampler: ManualAudioResampler, + pub turbo: &'a Turbo } -impl AudioDevice for NxAudioDevice{ +impl<'a> AudioDevice for NxAudioDevice<'a>{ fn push_buffer(&mut self, buffer:&[magenboy_core::apu::audio_device::StereoSample; magenboy_core::apu::audio_device::BUFFER_SIZE]) { + self.resampler.set_original_frequency(self.turbo.get_factor() * GB_FREQUENCY); let resampled = self.resampler.resample(buffer); - unsafe{(self.cb)(resampled.as_ptr(), (resampled.len() * 2) as c_int)}; + let resampled_ptr = resampled.as_ptr() as *const magenboy_core::apu::audio_device::Sample; + unsafe{(self.cb)(resampled_ptr, (resampled.len() * 2) as c_int)}; } } diff --git a/nx/src/lib.rs b/nx/src/lib.rs index 9a807015..235c5cbb 100644 --- a/nx/src/lib.rs +++ b/nx/src/lib.rs @@ -9,17 +9,18 @@ mod allocator; use core::{ffi::{c_char, c_ulonglong, c_void, CStr}, panic}; use alloc::{vec::Vec, boxed::Box, string::String}; -use magenboy_common::{audio::*, joypad_menu::{joypad_gfx_menu::GfxDeviceMenuRenderer, JoypadMenu}, menu::{MenuOption, GAME_MENU_OPTIONS}, VERSION}; +use magenboy_common::{audio::*, joypad_menu::{joypad_gfx_menu::GfxDeviceMenuRenderer, JoypadMenu}, menu::{MenuOption, GAME_MENU_OPTIONS, Turbo, EmulatorMenuOption}, VERSION}; use magenboy_core::{machine, GameBoy, Mode, GB_FREQUENCY}; use devices::*; use logging::{LogCallback, NxLogger}; -const TURBO: u32 = 2; +const TURBO_FACTOR: u32 = 4; +static TURBO: Turbo = Turbo::new(TURBO_FACTOR); struct NxGbContext<'a>{ - gb: GameBoy<'a, NxJoypadProvider, NxAudioDevice, NxGfxDevice>, - sram_fat_pointer: (*mut u8, usize) + gb: GameBoy<'a, NxJoypadProvider, NxAudioDevice<'a>, NxGfxDevice<'a>>, + sram_fat_pointer: (*mut u8, usize), } #[global_allocator] @@ -56,8 +57,8 @@ pub unsafe extern "C" fn magenboy_init(rom: *const c_char, rom_size: c_ulonglong let gameboy = GameBoy::new_with_mode( mbc, NxJoypadProvider{provider_cb: joypad_cb, poll_cb: poll_joypad_cb}, - NxAudioDevice{cb: audio_cb, resampler: ManualAudioResampler::new(GB_FREQUENCY * TURBO, 48000)}, - NxGfxDevice {cb: gfx_cb, turbo: TURBO, frame_counter: 0}, + NxAudioDevice{cb: audio_cb, turbo: &TURBO, resampler: ManualAudioResampler::new(GB_FREQUENCY, 48000)}, + NxGfxDevice {cb: gfx_cb, turbo: &TURBO}, mode, ); @@ -105,16 +106,23 @@ pub unsafe extern "C" fn magenboy_menu_trigger(gfx_cb: GfxDeviceCallback, joypad } #[no_mangle] -pub unsafe extern "C" fn magenboy_pause_trigger(gfx_cb: GfxDeviceCallback, joypad_cb: JoypadProviderCallback, poll_joypad_cb: PollJoypadProviderCallback) -> u32 { - +pub unsafe extern "C" fn magenboy_pause_trigger( + gfx_cb: GfxDeviceCallback, + joypad_cb: JoypadProviderCallback, + poll_joypad_cb: PollJoypadProviderCallback +) -> u32 { log::info!("Starting pause menu"); let header: String = alloc::format!("Magenboy {VERSION}"); let selection= render_menu(gfx_cb, joypad_cb, poll_joypad_cb, &GAME_MENU_OPTIONS, header.as_str()); + if let EmulatorMenuOption::Turbo = selection { + TURBO.toggle(); + } return *selection as u32; } fn render_menu<'a, T>(gfx_cb: GfxDeviceCallback, joypad_cb: JoypadProviderCallback, poll_joypad_cb: PollJoypadProviderCallback, options: &'a [MenuOption], header: &'a str) -> &'a T { - let mut gfx_device = NxGfxDevice {cb: gfx_cb, turbo: 1, frame_counter: 0}; + let turbo = Turbo::new(1); // No turbo in menu + let mut gfx_device = NxGfxDevice {cb: gfx_cb, turbo: &turbo}; let menu_renderer = GfxDeviceMenuRenderer::new(&mut gfx_device); let mut provider = NxJoypadProvider{provider_cb: joypad_cb, poll_cb: poll_joypad_cb}; let mut menu = JoypadMenu::new(&options, header, menu_renderer); diff --git a/nx/src/magenboy.h b/nx/src/magenboy.h index 8b61e3fa..d527192f 100644 --- a/nx/src/magenboy.h +++ b/nx/src/magenboy.h @@ -8,8 +8,6 @@ extern "C" { #include #include -// Define a callback type for logging. -// Adjust the signature as needed. typedef void (*LogCallback)(const char* message, int len); typedef void (*GfxDeviceCallback)(const uint16_t* buffer); typedef uint64_t (*JoypadDeviceCallback)(); @@ -29,7 +27,7 @@ void magenboy_deinit(void* ctx); const char* magenboy_menu_trigger(GfxDeviceCallback gfx_cb, JoypadDeviceCallback joypad_cb, PollJoypadDeviceCallback poll_cb, const char** roms, uint32_t roms_count); -const uint32_t magenboy_pause_trigger(GfxDeviceCallback gfx_cb, JoypadDeviceCallback joypad_cb, PollJoypadDeviceCallback poll_cb); +uint32_t magenboy_pause_trigger(GfxDeviceCallback gfx_cb, JoypadDeviceCallback joypad_cb, PollJoypadDeviceCallback poll_cb); // Cycle a frame for the given GameBoy instance. // ctx: pointer to a GameBoy instance returned by magenboy_init. From ec3c4afefd7e649e92a91dc6784a53d0c8c3cdfd Mon Sep 17 00:00:00 2001 From: alloncm Date: Tue, 23 Sep 2025 03:50:06 +0300 Subject: [PATCH 06/11] Move bindings to cbindgen and upgrade to rust 1.74 --- Cargo.lock | 346 +++++++++++++++++++++++++++++++++++++++----- Cargo.toml | 2 +- Makefile.toml | 2 +- nx/Cargo.toml | 5 +- nx/build.rs | 13 ++ nx/src/lib.rs | 4 +- nx/src/magenboy.h | 79 ++++++---- nx/src/main.c | 8 +- rust-toolchain.toml | 2 +- 9 files changed, 387 insertions(+), 74 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dfb3f1c2..5ff01c38 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -38,6 +38,56 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +dependencies = [ + "windows-sys 0.60.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.60.2", +] + [[package]] name = "arbitrary" version = "1.4.1" @@ -99,7 +149,7 @@ checksum = "6d7a33e7b9505a52e33ed0ad66db6434f18cda0b1c72665fabf14e85cdd39e43" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.106", ] [[package]] @@ -159,6 +209,25 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "cbindgen" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "975982cdb7ad6a142be15bdf84aea7ec6a9e5d4d797c004d43185b24cfe4e684" +dependencies = [ + "clap 4.5.48", + "heck", + "indexmap", + "log", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn 2.0.106", + "tempfile", + "toml", +] + [[package]] name = "cc" version = "1.0.99" @@ -182,7 +251,7 @@ dependencies = [ "js-sys", "num-traits", "wasm-bindgen", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -196,6 +265,33 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "clap" +version = "4.5.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_lex" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" + [[package]] name = "cmake" version = "0.1.49" @@ -205,6 +301,12 @@ dependencies = [ "cc", ] +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + [[package]] name = "core-foundation" version = "0.9.4" @@ -238,7 +340,7 @@ checksum = "1604dafd25fba2fe2d5895a9da139f8dc9b319a5fe5354ca137cbbce4e178d10" dependencies = [ "atty", "cast", - "clap", + "clap 2.33.3", "criterion-plot", "csv", "itertools", @@ -336,7 +438,7 @@ checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.106", ] [[package]] @@ -347,7 +449,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.106", ] [[package]] @@ -501,9 +603,15 @@ checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" [[package]] name = "hashbrown" -version = "0.14.5" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" @@ -753,7 +861,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.106", ] [[package]] @@ -790,9 +898,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.6" +version = "2.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" dependencies = [ "equivalent", "hashbrown", @@ -804,6 +912,12 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9" +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "itertools" version = "0.10.1" @@ -926,6 +1040,7 @@ dependencies = [ name = "magenboy_nx" version = "4.2.0" dependencies = [ + "cbindgen", "log", "magenboy_common", "magenboy_core", @@ -1064,6 +1179,12 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + [[package]] name = "oorandom" version = "11.1.3" @@ -1093,7 +1214,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.106", ] [[package]] @@ -1188,9 +1309,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] @@ -1454,7 +1575,7 @@ checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.106", ] [[package]] @@ -1468,6 +1589,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -1514,6 +1644,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "syn" version = "1.0.77" @@ -1527,9 +1663,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.67" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff8655ed1d86f3af4ee3fd3263786bc14245ad17c4c7e85ba7187fb3ae028c90" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", @@ -1553,7 +1689,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.106", ] [[package]] @@ -1595,7 +1731,7 @@ checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.106", ] [[package]] @@ -1643,6 +1779,47 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "toml_write", + "winnow", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + [[package]] name = "tower" version = "0.4.13" @@ -1736,6 +1913,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "vcpkg" version = "0.2.15" @@ -1797,7 +1980,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.106", "wasm-bindgen-shared", ] @@ -1831,7 +2014,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.106", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -1898,9 +2081,15 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + [[package]] name = "windows-registry" version = "0.2.0" @@ -1909,7 +2098,7 @@ checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" dependencies = [ "windows-result", "windows-strings", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -1918,7 +2107,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -1928,7 +2117,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" dependencies = [ "windows-result", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -1937,7 +2126,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -1946,7 +2135,16 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.3", ] [[package]] @@ -1955,14 +2153,31 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", ] [[package]] @@ -1971,48 +2186,105 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "winnow" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +dependencies = [ + "memchr", +] + [[package]] name = "write16" version = "1.0.0" @@ -2045,7 +2317,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.106", "synstructure", ] @@ -2066,7 +2338,7 @@ checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.106", "synstructure", ] @@ -2089,7 +2361,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.106", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index a444aa79..7af2d232 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ members = [ [workspace.package] version = "4.2.0" authors = ["alloncm "] -rust-version = "1.73" # cause of cargo-ndk used to build for android platform +rust-version = "1.74" # cause of cbindgen used to build for nx edition = "2021" [profile.release] diff --git a/Makefile.toml b/Makefile.toml index 4d73502e..7dd30354 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -2,7 +2,7 @@ default_to_workspace = false [env] -nightly_version = "nightly-2023-10-05" # 1.73.0 toolchain nightly version +nightly_version = "nightly-2023-11-16" # 1.73.0 toolchain nightly version [tasks.test] command = "cargo" diff --git a/nx/Cargo.toml b/nx/Cargo.toml index 62e78057..cbb63f9b 100644 --- a/nx/Cargo.toml +++ b/nx/Cargo.toml @@ -13,4 +13,7 @@ crate-type = ["staticlib"] [dependencies] magenboy_core = { path = "../core", features = ["apu"] } magenboy_common = { path = "../common", features = ["alloc"] } -log = "0.4" \ No newline at end of file +log = "0.4" + +[build-dependencies] +cbindgen = "0.29" \ No newline at end of file diff --git a/nx/build.rs b/nx/build.rs index 5bfbaccd..3ebd4ba1 100644 --- a/nx/build.rs +++ b/nx/build.rs @@ -9,4 +9,17 @@ fn main() { out_vars_path.pop(); std::fs::write(out_vars_path.join("version.txt"), version).expect("Unable to write version file"); std::fs::write(out_vars_path.join("authors.txt"), authors).expect("Unable to write authors file"); + + // Generate C bindings + let crate_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + cbindgen::Builder::new() + .with_crate(crate_dir) + .with_parse_deps(true) + .with_parse_include(&["magenboy_core", "magenboy_common"]) + .with_include_guard("MAGENBOY_H") + .with_language(cbindgen::Language::C) + .with_autogen_warning("// Do not edit this file directly, it is autogenerated by cbindgen during build process") + .generate() + .expect("Unable to generate bindings") + .write_to_file("src/magenboy.h"); } \ No newline at end of file diff --git a/nx/src/lib.rs b/nx/src/lib.rs index 235c5cbb..a94f2954 100644 --- a/nx/src/lib.rs +++ b/nx/src/lib.rs @@ -110,14 +110,14 @@ pub unsafe extern "C" fn magenboy_pause_trigger( gfx_cb: GfxDeviceCallback, joypad_cb: JoypadProviderCallback, poll_joypad_cb: PollJoypadProviderCallback -) -> u32 { +) -> EmulatorMenuOption { log::info!("Starting pause menu"); let header: String = alloc::format!("Magenboy {VERSION}"); let selection= render_menu(gfx_cb, joypad_cb, poll_joypad_cb, &GAME_MENU_OPTIONS, header.as_str()); if let EmulatorMenuOption::Turbo = selection { TURBO.toggle(); } - return *selection as u32; + return *selection; } fn render_menu<'a, T>(gfx_cb: GfxDeviceCallback, joypad_cb: JoypadProviderCallback, poll_joypad_cb: PollJoypadProviderCallback, options: &'a [MenuOption], header: &'a str) -> &'a T { diff --git a/nx/src/magenboy.h b/nx/src/magenboy.h index d527192f..3e51b01d 100644 --- a/nx/src/magenboy.h +++ b/nx/src/magenboy.h @@ -1,45 +1,68 @@ #ifndef MAGENBOY_H #define MAGENBOY_H -#ifdef __cplusplus -extern "C" { -#endif +// Do not edit this file directly, it is autogenerated by cbindgen during build process -#include +#include +#include #include +#include -typedef void (*LogCallback)(const char* message, int len); -typedef void (*GfxDeviceCallback)(const uint16_t* buffer); -typedef uint64_t (*JoypadDeviceCallback)(); -typedef uint64_t (*PollJoypadDeviceCallback)(); -typedef void (*AudioDeviceCallback)(const int16_t* buffer, int size); +enum EmulatorMenuOption { + Resume = 0, + Restart = 1, + Shutdown = 2, + Turbo = 3, +}; +typedef uint32_t EmulatorMenuOption; + +typedef void (*LogCallback)(const char*, int len); + +typedef void (*GfxDeviceCallback)(const uint16_t *buffer); + +typedef uint64_t (*JoypadProviderCallback)(void); + +typedef uint64_t (*PollJoypadProviderCallback)(void); + +typedef int16_t Sample; + +typedef void (*AudioDeviceCallback)(const Sample *buffer, int size); void magenboy_init_logger(LogCallback log_cb); -// Initialize the GameBoy instance. -// rom: pointer to ROM data -// rom_size: size of ROM data in bytes -// Returns: a pointer to the statically allocated GameBoy instance. -void* magenboy_init(const uint8_t* rom, uint64_t rom_size, GfxDeviceCallback gfx_cb, JoypadDeviceCallback joypad_cb, PollJoypadDeviceCallback poll_cb, - AudioDeviceCallback audio_cb); +/** + * SAFETY: rom size must be the size of rom + */ +void *magenboy_init(const char *rom, + unsigned long long rom_size, + GfxDeviceCallback gfx_cb, + JoypadProviderCallback joypad_cb, + PollJoypadProviderCallback poll_joypad_cb, + AudioDeviceCallback audio_cb); + +void magenboy_deinit(void *ctx); -void magenboy_deinit(void* ctx); +const char *magenboy_menu_trigger(GfxDeviceCallback gfx_cb, + JoypadProviderCallback joypad_cb, + PollJoypadProviderCallback poll_joypad_cb, + const char *const *roms, + uint32_t roms_count); -const char* magenboy_menu_trigger(GfxDeviceCallback gfx_cb, JoypadDeviceCallback joypad_cb, PollJoypadDeviceCallback poll_cb, const char** roms, uint32_t roms_count); +EmulatorMenuOption magenboy_pause_trigger(GfxDeviceCallback gfx_cb, + JoypadProviderCallback joypad_cb, + PollJoypadProviderCallback poll_joypad_cb); -uint32_t magenboy_pause_trigger(GfxDeviceCallback gfx_cb, JoypadDeviceCallback joypad_cb, PollJoypadDeviceCallback poll_cb); +/** + * SAFETY: ctx is a valid pointer to a GameBoy instance + */ +void magenboy_cycle_frame(void *ctx); -// Cycle a frame for the given GameBoy instance. -// ctx: pointer to a GameBoy instance returned by magenboy_init. -void magenboy_cycle_frame(void* ctx); +void magenboy_get_dimensions(uint32_t *width, uint32_t *height); -// Get the GB display dimensions. -void magenboy_get_dimensions(uint32_t* width, uint32_t* height); +void magenboy_get_sram(void *ctx, uint8_t **ptr, uintptr_t *size); -void magenboy_get_sram(void* ctx, uint8_t** sram_buffer, size_t* sram_size); +extern void *malloc(uintptr_t size); -#ifdef __cplusplus -} -#endif +extern void free(void *ptr); -#endif // MAGENBOY_H \ No newline at end of file +#endif /* MAGENBOY_H */ diff --git a/nx/src/main.c b/nx/src/main.c index 12d71524..12cc056f 100644 --- a/nx/src/main.c +++ b/nx/src/main.c @@ -375,15 +375,17 @@ int main(int argc, char* argv[]) { if ((kDown & HidNpadButton_L) != 0 && (kDown & HidNpadButton_R) != 0) { int shutdown = 0; switch (magenboy_pause_trigger(render_buffer_cb, get_joycon_state, poll_until_joycon_pressed)) { - case 0: // Resume + case Resume: break; - case 1: // Restart + case Restart: printf("Restarting\n"); goto restart; - case 2: // Shutdon + case Shutdown: printf("Shutting down\n"); shutdown = 1; break; + default: + break; } if (shutdown) { break; // Exit the main loop diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 10aa4b2d..8517c274 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "1.73.0" +channel = "1.74.0" components = [ "rust-src", "rust-analyzer" ] profile = "minimal" \ No newline at end of file From 9c3dbbcce28a595a5035f7e7fe03daa04892655b Mon Sep 17 00:00:00 2001 From: alloncm Date: Tue, 23 Sep 2025 16:29:29 +0300 Subject: [PATCH 07/11] Add version with git hash to nx target --- nx/Cargo.toml | 1 + nx/build.rs | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/nx/Cargo.toml b/nx/Cargo.toml index cbb63f9b..dad845c1 100644 --- a/nx/Cargo.toml +++ b/nx/Cargo.toml @@ -16,4 +16,5 @@ magenboy_common = { path = "../common", features = ["alloc"] } log = "0.4" [build-dependencies] +magenboy_common = {path = "../common"} cbindgen = "0.29" \ No newline at end of file diff --git a/nx/build.rs b/nx/build.rs index 3ebd4ba1..549e17bd 100644 --- a/nx/build.rs +++ b/nx/build.rs @@ -1,13 +1,14 @@ +use magenboy_common::VERSION; + // Export values for the C application to use fn main() { - let version = env!("CARGO_PKG_VERSION"); let authors = env!("CARGO_PKG_AUTHORS"); let out_dir = std::env::var("OUT_DIR").unwrap(); let mut out_vars_path = std::path::Path::new(&out_dir).to_path_buf(); out_vars_path.pop(); out_vars_path.pop(); out_vars_path.pop(); - std::fs::write(out_vars_path.join("version.txt"), version).expect("Unable to write version file"); + std::fs::write(out_vars_path.join("version.txt"), VERSION).expect("Unable to write version file"); std::fs::write(out_vars_path.join("authors.txt"), authors).expect("Unable to write authors file"); // Generate C bindings From acb8cd0da716b5fa0885c86fa16ec3a0dbe9aa93 Mon Sep 17 00:00:00 2001 From: alloncm Date: Wed, 15 Oct 2025 10:49:48 +0300 Subject: [PATCH 08/11] WIP --- .vscode/launch.json | 21 +++++++ common/src/initialization.rs | 35 ++++------- common/src/joypad_menu/joypad_gfx_menu.rs | 4 +- common/src/joypad_menu/mod.rs | 4 +- common/src/lib.rs | 13 +++- common/src/menu.rs | 12 ++-- common/src/mpmc_gfx_device.rs | 19 ------ .../{keypad/joypad_handler.rs => keypad.rs} | 33 ++++++++--- core/src/keypad/button.rs | 11 ---- core/src/keypad/joypad.rs | 13 ---- core/src/keypad/joypad_provider.rs | 5 -- core/src/keypad/mod.rs | 4 -- core/src/lib.rs | 2 - core/src/machine/gameboy.rs | 27 +++++---- core/src/mmu/gb_mmu.rs | 22 +++---- core/src/mmu/io_bus.rs | 18 +++--- core/src/mmu/oam_dma_controller.rs | 4 +- core/src/mmu/vram_dma_controller.rs | 8 +-- core/src/ppu/color.rs | 2 +- core/src/ppu/gb_ppu.rs | 32 ++++------ core/src/ppu/gfx_device.rs | 9 --- core/src/ppu/mod.rs | 11 +++- core/tests/vram_dma_controller_tests.rs | 11 +--- sdl/src/main.rs | 59 +++++++------------ sdl/src/sdl_gfx_device.rs | 6 +- sdl/src/sdl_joypad_provider.rs | 4 +- 26 files changed, 169 insertions(+), 220 deletions(-) create mode 100644 .vscode/launch.json delete mode 100644 common/src/mpmc_gfx_device.rs rename core/src/{keypad/joypad_handler.rs => keypad.rs} (74%) delete mode 100644 core/src/keypad/button.rs delete mode 100644 core/src/keypad/joypad.rs delete mode 100644 core/src/keypad/joypad_provider.rs delete mode 100644 core/src/keypad/mod.rs delete mode 100644 core/src/ppu/gfx_device.rs diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..9d36cb03 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,21 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Debug executable 'magenboy_sdl'", + "type": "lldb", + "request": "launch", + "cargo": { + "args": [ + "run", + "--bin=magenboy_sdl", + "--package=magenboy_sdl" + ] + }, + "args": ["..\\..\\Downloads\\PokemonCrystal.gbc"] + } + ] +} \ No newline at end of file diff --git a/common/src/initialization.rs b/common/src/initialization.rs index 19758573..c0d82538 100644 --- a/common/src/initialization.rs +++ b/common/src/initialization.rs @@ -1,10 +1,10 @@ use log::info; -use magenboy_core::{AudioDevice, Bootrom, GameBoy, JoypadProvider, Mode, GBC_BOOT_ROM_SIZE, GB_BOOT_ROM_SIZE}; +use magenboy_core::{AudioDevice, Bootrom, GameBoy, Mode, GBC_BOOT_ROM_SIZE, GB_BOOT_ROM_SIZE}; #[cfg(feature = "dbg")] use magenboy_core::debugger::DebuggerInterface; -use crate::{mbc_handler::{initialize_mbc, release_mbc}, menu::MagenBoyState, mpmc_gfx_device::MpmcGfxDevice}; +use crate::{mbc_handler::initialize_mbc, menu::MagenBoyState}; pub fn check_for_terminal_feature_flag(args:&Vec::, flag:&str)->bool{ args.len() >= 3 && args.contains(&String::from(flag)) @@ -19,14 +19,12 @@ const TURBO_FACTOR:u32 = 4; // This is static and not local for the unix signal handler to access it pub static EMULATOR_STATE:MagenBoyState = MagenBoyState::new(TURBO_FACTOR); -pub fn init_and_run_gameboy( - args: Vec, - program_name: String, - spsc_gfx_device: MpmcGfxDevice, - joypad_provider: impl JoypadProvider, - audio_devices: impl AudioDevice, +pub fn init_gameboy<'a, AD: AudioDevice>( + args: &Vec, + program_name: String, + audio_devices: AD, #[cfg(feature = "dbg")] dui: impl DebuggerInterface -){ +) -> GameBoy<'a, AD> { let bootrom_path = if check_for_terminal_feature_flag(&args, "--bootrom"){ Some(get_terminal_feature_flag_value(&args, "--bootrom", "Error! you must specify a value for the --bootrom parameter")) }else{ @@ -52,8 +50,8 @@ pub fn init_and_run_gameboy( let mbc = initialize_mbc(&program_name); - let mut gameboy = match bootrom{ - Some(b) => GameBoy::new_with_bootrom(mbc, joypad_provider, audio_devices, spsc_gfx_device, b, #[cfg(feature = "dbg")] dui), + let gameboy = match bootrom{ + Some(b) => GameBoy::new_with_bootrom(mbc, audio_devices, b, #[cfg(feature = "dbg")] dui), None => { let mode = if check_for_terminal_feature_flag(&args, "--mode"){ let mode = get_terminal_feature_flag_value(&args, "--mode", "Error: Must specify a mode"); @@ -65,22 +63,11 @@ pub fn init_and_run_gameboy( log::info!("Could not find a mode flag, auto detected {}", >::into(mode)); mode }; - GameBoy::new_with_mode(mbc, joypad_provider, audio_devices, spsc_gfx_device, mode, #[cfg(feature = "dbg")] dui) + GameBoy::new_with_mode(mbc, audio_devices, mode, #[cfg(feature = "dbg")] dui) } }; info!("initialized gameboy successfully!"); - EMULATOR_STATE.running.store(true, std::sync::atomic::Ordering::Relaxed); - while EMULATOR_STATE.running.load(std::sync::atomic::Ordering::Relaxed){ - if !EMULATOR_STATE.pause.load(std::sync::atomic::Ordering::SeqCst){ - // Locking the state mutex in order to signal the menu that we are cycling a frame now - let state = &EMULATOR_STATE; - let _mutex_ctx = state.state_mutex.lock().unwrap(); - gameboy.cycle_frame(); - } - } - drop(gameboy); - release_mbc(&program_name, mbc); - log::info!("released the gameboy succefully"); + return gameboy; } \ No newline at end of file diff --git a/common/src/joypad_menu/joypad_gfx_menu.rs b/common/src/joypad_menu/joypad_gfx_menu.rs index 7125744c..3a3fd6c8 100644 --- a/common/src/joypad_menu/joypad_gfx_menu.rs +++ b/common/src/joypad_menu/joypad_gfx_menu.rs @@ -1,4 +1,6 @@ -use magenboy_core::ppu::{gfx_device::*, gb_ppu::{SCREEN_HEIGHT, SCREEN_WIDTH}, color::{BLACK, WHITE}, color::Color}; +use magenboy_core::ppu::{color::{Color, BLACK, WHITE}, gb_ppu::{SCREEN_HEIGHT, SCREEN_WIDTH}, Pixel}; + +use crate::GfxDevice; use super::{font::*, MenuRenderer}; diff --git a/common/src/joypad_menu/mod.rs b/common/src/joypad_menu/mod.rs index aaf34aab..7f417882 100644 --- a/common/src/joypad_menu/mod.rs +++ b/common/src/joypad_menu/mod.rs @@ -1,9 +1,9 @@ mod font; pub mod joypad_gfx_menu; -use magenboy_core::keypad::{button::Button, joypad::Joypad, joypad_provider::JoypadProvider}; +use magenboy_core::keypad::{Button, Joypad}; -use crate::menu::MenuOption; +use crate::{menu::MenuOption, JoypadProvider}; pub trait MenuRenderer>{ fn render_menu(&mut self,header:&S, menu:&[MenuOption], selection:usize); diff --git a/common/src/lib.rs b/common/src/lib.rs index 545fe05c..3202efbe 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -1,8 +1,9 @@ #![cfg_attr(not(feature = "std"), no_std)] +use magenboy_core::{keypad::Joypad, ppu::FrameBuffer}; + cfg_if::cfg_if!{ if #[cfg(feature = "std")] { pub mod mbc_handler; - pub mod mpmc_gfx_device; pub mod logging; pub mod initialization; pub use initialization::*; @@ -24,4 +25,12 @@ pub mod joypad_menu; pub mod interpolation; pub mod synchronization; -pub const VERSION:&str = env!("MAGENBOY_VERSION"); \ No newline at end of file +pub const VERSION:&str = env!("MAGENBOY_VERSION"); + +pub trait GfxDevice { + fn swap_buffer(&mut self, buffer: &FrameBuffer); +} + +pub trait JoypadProvider{ + fn provide(&mut self, joypad:&mut Joypad); +} \ No newline at end of file diff --git a/common/src/menu.rs b/common/src/menu.rs index c23cdc22..7914e706 100644 --- a/common/src/menu.rs +++ b/common/src/menu.rs @@ -63,9 +63,11 @@ impl Turbo { cfg_if::cfg_if!{ if #[cfg(feature = "std")]{ use std::{sync::Mutex, path::PathBuf}; - use magenboy_core::{ppu::gfx_device::GfxDevice, keypad::joypad_provider::JoypadProvider}; + use super::joypad_menu::{MenuJoypadProvider, joypad_gfx_menu, JoypadMenu, MenuRenderer}; + use crate::{GfxDevice, JoypadProvider}; + pub struct MagenBoyState{ // Use atomic bool, normal bool doesnt works on arm (probably cause of the memory model) pub running: AtomicBool, @@ -101,9 +103,8 @@ cfg_if::cfg_if!{ if #[cfg(feature = "std")]{ &mut self, state: &MagenBoyState, gfx_device: &mut GFX, - receiver: crossbeam_channel::Receiver ) { - match self.get_game_menu_selection(state, gfx_device, receiver){ + match self.get_game_menu_selection(state, gfx_device){ EmulatorMenuOption::Resume => {}, EmulatorMenuOption::Turbo => state.turbo.toggle(), EmulatorMenuOption::Restart => state.running.store(false, std::sync::atomic::Ordering::Relaxed), @@ -118,7 +119,6 @@ cfg_if::cfg_if!{ if #[cfg(feature = "std")]{ &mut self, state: &MagenBoyState, gfx_device: &mut GFX, - emulation_framebuffer_channel: crossbeam_channel::Receiver ) -> &EmulatorMenuOption { let menu_renderer = joypad_gfx_menu::GfxDeviceMenuRenderer::new(gfx_device); @@ -135,10 +135,6 @@ cfg_if::cfg_if!{ if #[cfg(feature = "std")]{ state.turbo.enabled.store(last_turbo, core::sync::atomic::Ordering::Relaxed); state.pause.store(false, std::sync::atomic::Ordering::SeqCst); return selection; - } else { - // try recv in order to clear frames from the channel - // in order to not block the emualtion thread and allow it to finish the frame - let _ = emulation_framebuffer_channel.try_recv(); } } } diff --git a/common/src/mpmc_gfx_device.rs b/common/src/mpmc_gfx_device.rs deleted file mode 100644 index eb4f8887..00000000 --- a/common/src/mpmc_gfx_device.rs +++ /dev/null @@ -1,19 +0,0 @@ -use magenboy_core::ppu::{gb_ppu::{SCREEN_HEIGHT, SCREEN_WIDTH}, gfx_device::{GfxDevice, Pixel}}; - -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:&[Pixel; 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/core/src/keypad/joypad_handler.rs b/core/src/keypad.rs similarity index 74% rename from core/src/keypad/joypad_handler.rs rename to core/src/keypad.rs index d3563a98..c528834e 100644 --- a/core/src/keypad/joypad_handler.rs +++ b/core/src/keypad.rs @@ -1,24 +1,39 @@ -use crate::utils::bit_masks::*; -use super::{joypad_provider::JoypadProvider, joypad::Joypad, button::Button}; +use crate::utils::bit_masks::{flip_bit_u8, BIT_4_MASK, BIT_5_MASK}; +#[repr(u8)] +pub enum Button{ + A, + B, + Start, + Select, + Up, + Down, + Right, + Left +} + +pub const NUM_OF_KEYS: usize = 8; + +#[derive(Default, Clone, Copy)] +pub struct Joypad { + pub buttons: [bool; NUM_OF_KEYS] +} -pub struct JoypadHandler{ +pub struct JoypadHandler{ register:u8, joypad:Joypad, - joypad_provider:JP, } -impl JoypadHandler{ - pub fn new(provider: JP)->Self{ +impl JoypadHandler{ + pub fn new()->Self{ Self{ - joypad_provider:provider, register:0xFF, joypad: Joypad::default() } } - pub fn poll_joypad_state(&mut self){ - self.joypad_provider.provide(&mut self.joypad); + pub fn update_joypad(&mut self, joypad: Joypad) { + self.joypad = joypad; } pub fn get_register(&mut self) -> u8{ diff --git a/core/src/keypad/button.rs b/core/src/keypad/button.rs deleted file mode 100644 index d8fad1dc..00000000 --- a/core/src/keypad/button.rs +++ /dev/null @@ -1,11 +0,0 @@ -#[repr(u8)] -pub enum Button{ - A, - B, - Start, - Select, - Up, - Down, - Right, - Left -} \ No newline at end of file diff --git a/core/src/keypad/joypad.rs b/core/src/keypad/joypad.rs deleted file mode 100644 index fb447e51..00000000 --- a/core/src/keypad/joypad.rs +++ /dev/null @@ -1,13 +0,0 @@ -pub const NUM_OF_KEYS: usize = 8; - -pub struct Joypad{ - pub buttons:[bool;NUM_OF_KEYS] -} - -impl Default for Joypad{ - fn default()->Self{ - Joypad{ - buttons:[false;NUM_OF_KEYS] - } - } -} \ No newline at end of file diff --git a/core/src/keypad/joypad_provider.rs b/core/src/keypad/joypad_provider.rs deleted file mode 100644 index 1fe9f2e6..00000000 --- a/core/src/keypad/joypad_provider.rs +++ /dev/null @@ -1,5 +0,0 @@ -use super::joypad::Joypad; - -pub trait JoypadProvider{ - fn provide(&mut self, joypad:&mut Joypad); -} \ No newline at end of file diff --git a/core/src/keypad/mod.rs b/core/src/keypad/mod.rs deleted file mode 100644 index 932652c7..00000000 --- a/core/src/keypad/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod joypad; -pub mod joypad_provider; -pub mod button; -pub mod joypad_handler; \ No newline at end of file diff --git a/core/src/lib.rs b/core/src/lib.rs index f4072a93..8e448877 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -13,9 +13,7 @@ pub mod debugger; pub use { machine::{gameboy::GameBoy, Mode}, - ppu::gfx_device::*, apu::audio_device::AudioDevice, - keypad::joypad_provider::JoypadProvider, utils::GB_FREQUENCY, mmu::external_memory_bus::{Bootrom, GB_BOOT_ROM_SIZE, GBC_BOOT_ROM_SIZE} }; diff --git a/core/src/machine/gameboy.rs b/core/src/machine/gameboy.rs index 3a2be3ef..a9657994 100644 --- a/core/src/machine/gameboy.rs +++ b/core/src/machine/gameboy.rs @@ -1,11 +1,11 @@ -use crate::{*, apu::gb_apu::GbApu, cpu::gb_cpu::GbCpu, mmu::{Memory, carts::Mbc, gb_mmu::GbMmu, external_memory_bus::Bootrom}}; +use crate::{apu::gb_apu::GbApu, cpu::gb_cpu::GbCpu, keypad::Joypad, mmu::{carts::Mbc, external_memory_bus::Bootrom, gb_mmu::GbMmu, Memory}, ppu::FrameBuffer, *}; use super::Mode; #[cfg(feature = "dbg")] use crate::debugger::*; -pub struct GameBoy<'a, JP: JoypadProvider, AD:AudioDevice, GFX:GfxDevice, #[cfg(feature = "dbg")] DI:DebuggerInterface>{ +pub struct GameBoy<'a, AD:AudioDevice, #[cfg(feature = "dbg")] DI:DebuggerInterface>{ pub(crate) cpu: GbCpu, - pub(crate) mmu:GbMmu<'a, AD, GFX, JP>, + pub(crate) mmu:GbMmu<'a, AD>, #[cfg(feature = "dbg")] pub(crate) debugger:Debugger } @@ -14,15 +14,14 @@ pub struct GameBoy<'a, JP: JoypadProvider, AD:AudioDevice, GFX:GfxDevice, #[cfg( macro_rules! impl_gameboy { ($implementations:tt) => { #[cfg(feature = "dbg")] - impl<'a, JP:JoypadProvider, AD:AudioDevice, GFX:GfxDevice, DUI:DebuggerInterface> GameBoy<'a, JP, AD, GFX, DUI> $implementations + impl<'a, JP:JoypadProvider, AD:AudioDevice, DUI:DebuggerInterface> GameBoy<'a, JP, AD, DUI> $implementations #[cfg(not(feature = "dbg"))] - impl<'a, JP:JoypadProvider, AD:AudioDevice, GFX:GfxDevice> GameBoy<'a, JP, AD, GFX> $implementations + impl<'a, AD:AudioDevice> GameBoy<'a, AD> $implementations }; } -pub(crate) use impl_gameboy; impl_gameboy! {{ - pub fn new_with_mode(mbc:&'a mut dyn Mbc, joypad_provider:JP, audio_device:AD, gfx_device:GFX, mode:Mode, #[cfg(feature = "dbg")]dui:DUI)->Self{ + pub fn new_with_mode(mbc:&'a mut dyn Mbc, audio_device:AD, mode:Mode, #[cfg(feature = "dbg")]dui:DUI)->Self{ let mut cpu = GbCpu::default(); match mode{ Mode::DMG=>{ @@ -43,13 +42,13 @@ impl_gameboy! {{ return Self{ cpu: cpu, - mmu: GbMmu::new(mbc, None, GbApu::new(audio_device), gfx_device, joypad_provider, mode), + mmu: GbMmu::new(mbc, None, GbApu::new(audio_device), mode), #[cfg(feature = "dbg")] debugger: Debugger::new(dui), }; } - pub fn new_with_bootrom(mbc:&'a mut dyn Mbc, joypad_provider:JP, audio_device:AD, gfx_device:GFX, bootrom:Bootrom, #[cfg(feature = "dbg")]dui:DUI)->Self{ + pub fn new_with_bootrom(mbc:&'a mut dyn Mbc, audio_device:AD, bootrom:Bootrom, #[cfg(feature = "dbg")]dui:DUI)->Self{ let mode = match bootrom{ Bootrom::Gb(_) => Mode::DMG, Bootrom::Gbc(_) => Mode::CGB @@ -57,20 +56,22 @@ impl_gameboy! {{ return Self{ cpu: GbCpu::default(), - mmu: GbMmu::new(mbc, Some(bootrom), GbApu::new(audio_device), gfx_device, joypad_provider, mode), + mmu: GbMmu::new(mbc, Some(bootrom), GbApu::new(audio_device), mode), #[cfg(feature = "dbg")] debugger: Debugger::new(dui), }; } - pub fn cycle_frame(&mut self){ - self.mmu.poll_joypad_state(); - + pub fn cycle_frame(&mut self, joypad: Joypad) -> &FrameBuffer { + self.mmu.update_joypad_state(joypad); + while !self.mmu.consume_vblank_event() { #[cfg(feature = "dbg")] self.run_debugger(); self.step(); } + + return self.mmu.consume_framebuffer(); } pub(crate) fn step(&mut self) { diff --git a/core/src/mmu/gb_mmu.rs b/core/src/mmu/gb_mmu.rs index e01ac7b7..50ff1b4b 100644 --- a/core/src/mmu/gb_mmu.rs +++ b/core/src/mmu/gb_mmu.rs @@ -1,12 +1,12 @@ use super::{access_bus::AccessBus, carts::{Mbc, CGB_FLAG_ADDRESS}, external_memory_bus::{Bootrom, ExternalMemoryBus}, interrupts_handler::InterruptRequest, io_bus::IoBus, Memory}; -use crate::{apu::{audio_device::AudioDevice, gb_apu::GbApu}, keypad::joypad_provider::JoypadProvider, machine::Mode, ppu::{color::Color, gfx_device::GfxDevice, ppu_state::PpuState}, utils::{bit_masks::{flip_bit_u8, BIT_7_MASK}, memory_registers::*}}; +use crate::{apu::{audio_device::AudioDevice, gb_apu::GbApu}, keypad::Joypad, machine::Mode, ppu::{color::Color, ppu_state::PpuState, FrameBuffer}, utils::{bit_masks::{flip_bit_u8, BIT_7_MASK}, memory_registers::*}}; const HRAM_SIZE:usize = 0x7F; const BAD_READ_VALUE:u8 = 0xFF; -pub struct GbMmu<'a, D:AudioDevice, G:GfxDevice, J:JoypadProvider>{ - io_bus: IoBus, +pub struct GbMmu<'a, D:AudioDevice>{ + io_bus: IoBus, external_memory_bus:ExternalMemoryBus<'a>, occupied_access_bus:Option, hram: [u8;HRAM_SIZE], @@ -19,7 +19,7 @@ pub struct GbMmu<'a, D:AudioDevice, G:GfxDevice, J:JoypadProvider>{ //DMA only locks the used bus. there 2 possible used buses: extrnal (wram, rom, sram) and video (vram) -impl<'a, D:AudioDevice, G:GfxDevice, J:JoypadProvider> Memory for GbMmu<'a, D, G, J>{ +impl<'a, D:AudioDevice> Memory for GbMmu<'a, D>{ fn read(&mut self, address:u16, m_cycles:u8)->u8{ self.cycle(m_cycles); let value = if let Some (bus) = &self.occupied_access_bus{ @@ -117,7 +117,7 @@ impl<'a, D:AudioDevice, G:GfxDevice, J:JoypadProvider> Memory for GbMmu<'a, D, G } } -impl<'a, D:AudioDevice, G:GfxDevice, J:JoypadProvider> GbMmu<'a, D, G, J>{ +impl<'a, D:AudioDevice> GbMmu<'a, D>{ fn read_unprotected(&mut self, address:u16) ->u8 { return match address{ 0x0..=0x7FFF=>self.external_memory_bus.read(address), @@ -167,12 +167,12 @@ impl<'a, D:AudioDevice, G:GfxDevice, J:JoypadProvider> GbMmu<'a, D, G, J>{ } } -impl<'a, D:AudioDevice, G:GfxDevice, J:JoypadProvider> GbMmu<'a, D, G, J>{ - pub fn new(mbc:&'a mut dyn Mbc, boot_rom:Option, apu:GbApu, gfx_device:G, joypad_proider:J, mode:Mode)->Self{ +impl<'a, D:AudioDevice> GbMmu<'a, D>{ + pub fn new(mbc:&'a mut dyn Mbc, boot_rom:Option, apu:GbApu, mode:Mode)->Self{ let bootrom_missing = boot_rom.is_none(); let cgb_reg = mbc.read_bank0(CGB_FLAG_ADDRESS as u16); let mut mmu = GbMmu{ - io_bus:IoBus::new(apu, gfx_device, joypad_proider, mode), + io_bus:IoBus::new(apu, mode), external_memory_bus: ExternalMemoryBus::new(mbc, boot_rom), occupied_access_bus:None, hram:[0;HRAM_SIZE], @@ -233,8 +233,8 @@ impl<'a, D:AudioDevice, G:GfxDevice, J:JoypadProvider> GbMmu<'a, D, G, J>{ return self.io_bus.interrupt_handler.handle_interrupts(master_interrupt_enable, self.io_bus.ppu.stat_register); } - pub fn poll_joypad_state(&mut self){ - self.io_bus.joypad_handler.poll_joypad_state(); + pub fn update_joypad_state(&mut self, joypad: Joypad){ + self.io_bus.joypad_handler.update_joypad(joypad); } pub fn dma_block_cpu(&self)->bool{ @@ -246,6 +246,8 @@ impl<'a, D:AudioDevice, G:GfxDevice, J:JoypadProvider> GbMmu<'a, D, G, J>{ pub fn consume_vblank_event(&mut self)->bool{self.io_bus.ppu.consume_vblank_event()} + pub fn consume_framebuffer(&mut self) -> &FrameBuffer {self.io_bus.ppu.consume_framebuffer()} + #[cfg(feature = "dbg")] pub fn get_ppu(&self)->&crate::ppu::gb_ppu::GbPpu{&self.io_bus.ppu} diff --git a/core/src/mmu/io_bus.rs b/core/src/mmu/io_bus.rs index 5ca28a16..d35ecf74 100644 --- a/core/src/mmu/io_bus.rs +++ b/core/src/mmu/io_bus.rs @@ -1,8 +1,8 @@ use crate::{ apu::{audio_device::AudioDevice, gb_apu::GbApu, *}, - keypad::{joypad_handler::JoypadHandler, joypad_provider::JoypadProvider}, + keypad::JoypadHandler, machine::Mode, - ppu::{gb_ppu::GbPpu, gfx_device::GfxDevice}, + ppu::gb_ppu::GbPpu, timer::{gb_timer::GbTimer, timer_register_updater::*}, utils::bit_masks::BIT_2_MASK }; use super::{interrupts_handler::*, io_ports::*, oam_dma_controller::OamDmaController, vram_dma_controller::VramDmaController, external_memory_bus::ExternalMemoryBus, access_bus::AccessBus}; @@ -11,14 +11,14 @@ pub const IO_PORTS_SIZE:usize = 0x80; const WAVE_RAM_START_INDEX:u16 = 0x30; const WAVE_RAM_END_INDEX:u16 = 0x3F; -pub struct IoBus{ +pub struct IoBus{ pub apu: GbApu, pub timer: GbTimer, - pub ppu:GbPpu, + pub ppu:GbPpu, pub oam_dma_controller:OamDmaController, pub vram_dma_controller: VramDmaController, pub interrupt_handler:InterruptsHandler, - pub joypad_handler: JoypadHandler, + pub joypad_handler: JoypadHandler, pub speed_switch_register:u8, mode: Mode, key0_register:u8, @@ -38,7 +38,7 @@ pub struct IoBus{ ppu_event:Option, } -impl IoBus{ +impl IoBus{ pub fn read(&mut self, address:u16)->u8 { match address{ @@ -201,15 +201,15 @@ impl IoBus{ } } - pub fn new(apu:GbApu, gfx_device:GFX, joypad_provider:JP, mode:Mode)->Self{ + pub fn new(apu:GbApu, mode:Mode)->Self{ Self{ apu, timer:GbTimer::default(), - ppu:GbPpu::new(gfx_device, mode), + ppu:GbPpu::new(mode), oam_dma_controller: OamDmaController::new(), vram_dma_controller: VramDmaController::new(), interrupt_handler: InterruptsHandler::default(), - joypad_handler: JoypadHandler::new(joypad_provider), + joypad_handler: JoypadHandler::new(), speed_switch_register:0, speed_cycle_reminder:0, apu_cycles_counter:0, diff --git a/core/src/mmu/oam_dma_controller.rs b/core/src/mmu/oam_dma_controller.rs index 6594ef2d..79b1060b 100644 --- a/core/src/mmu/oam_dma_controller.rs +++ b/core/src/mmu/oam_dma_controller.rs @@ -1,4 +1,4 @@ -use crate::ppu::{gb_ppu::GbPpu, gfx_device::GfxDevice}; +use crate::ppu::gb_ppu::GbPpu; use super::{external_memory_bus::ExternalMemoryBus, access_bus::AccessBus}; const DMA_SIZE:u16 = 0xA0; @@ -14,7 +14,7 @@ impl OamDmaController{ pub fn new()->Self{ Self{dma_cycle_counter:0, enable:None, soure_address:0} } - pub fn cycle(&mut self, m_cycles:u32, external_bus: &mut ExternalMemoryBus, ppu:&mut GbPpu)->Option{ + pub fn cycle(&mut self, m_cycles:u32, external_bus: &mut ExternalMemoryBus, ppu:&mut GbPpu) -> Option { if let Some(bus) = self.enable{ let cycles_to_run = core::cmp::min(self.dma_cycle_counter + m_cycles as u16, DMA_SIZE); match bus{ diff --git a/core/src/mmu/vram_dma_controller.rs b/core/src/mmu/vram_dma_controller.rs index c0635153..cb7c26ce 100644 --- a/core/src/mmu/vram_dma_controller.rs +++ b/core/src/mmu/vram_dma_controller.rs @@ -1,4 +1,4 @@ -use crate::{utils::bit_masks::BIT_7_MASK, ppu::{gb_ppu::GbPpu, gfx_device::GfxDevice, ppu_state::PpuState}}; +use crate::{utils::bit_masks::BIT_7_MASK, ppu::{gb_ppu::GbPpu, ppu_state::PpuState}}; use super::external_memory_bus::ExternalMemoryBus; @@ -81,7 +81,7 @@ impl VramDmaController{ }; } - pub fn cycle(&mut self, m_cycles:u32, exteranl_memory_bus:&mut ExternalMemoryBus, ppu:&mut GbPpu){ + pub fn cycle(&mut self, m_cycles:u32, exteranl_memory_bus:&mut ExternalMemoryBus, ppu:&mut GbPpu) { match self.mode{ TransferMode::Hblank=>self.handle_hblank_transfer(ppu, m_cycles, exteranl_memory_bus), TransferMode::GeneralPurpose=>self.handle_general_purpose_transfer(exteranl_memory_bus, ppu, m_cycles), @@ -89,7 +89,7 @@ impl VramDmaController{ } } - fn handle_general_purpose_transfer(&mut self, exteranl_memory_bus: &mut ExternalMemoryBus, ppu: &mut GbPpu, m_cycles:u32) { + fn handle_general_purpose_transfer(&mut self, exteranl_memory_bus: &mut ExternalMemoryBus, ppu: &mut GbPpu, m_cycles:u32) { for _ in 0..m_cycles { for _ in 0..BYTES_TRASNFERED_PER_M_CYCLE{ let source_value = exteranl_memory_bus.read(self.source_address); @@ -111,7 +111,7 @@ impl VramDmaController{ } } - fn handle_hblank_transfer(&mut self, ppu: &mut GbPpu, m_cycles: u32, exteranl_memory_bus: &mut ExternalMemoryBus) { + fn handle_hblank_transfer(&mut self, ppu: &mut GbPpu, m_cycles: u32, exteranl_memory_bus: &mut ExternalMemoryBus) { if self.last_ly.is_some_and(|v|v == ppu.ly_register) || ppu.state != PpuState::Hblank { return; } diff --git a/core/src/ppu/color.rs b/core/src/ppu/color.rs index 4a6fee0d..b021c280 100644 --- a/core/src/ppu/color.rs +++ b/core/src/ppu/color.rs @@ -1,4 +1,4 @@ -use super::gfx_device::Pixel; +use super::Pixel; pub const WHITE:Color = Color {r: 255,g: 255,b: 255}; pub const LIGHT_GRAY:Color = Color {r: 160,g: 160,b: 160}; diff --git a/core/src/ppu/gb_ppu.rs b/core/src/ppu/gb_ppu.rs index 83c8a2a8..bbd32e73 100644 --- a/core/src/ppu/gb_ppu.rs +++ b/core/src/ppu/gb_ppu.rs @@ -1,13 +1,12 @@ use core::cmp; -use crate::{machine::Mode, utils::{bit_masks::*, vec2::Vec2}}; -use super::{fifo::{SPRITE_WIDTH, background_fetcher::*, FIFO_SIZE, sprite_fetcher::*}, VRam, gfx_device::*, ppu_state::PpuState, attributes::SpriteAttributes, color::*}; +use crate::{machine::Mode, ppu::{FrameBuffer, Pixel}, utils::{bit_masks::*, vec2::Vec2}}; +use super::{fifo::{SPRITE_WIDTH, background_fetcher::*, FIFO_SIZE, sprite_fetcher::*}, VRam, ppu_state::PpuState, attributes::SpriteAttributes, color::*}; const WX_OFFSET:u8 = 7; 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; @@ -16,7 +15,7 @@ const OAM_SEARCH_M_CYCLES_LENGTH: u16 = 80 / 4; const HBLANK_M_CYCLES_LENGTH: u16 = 456 / 4; const VBLANK_M_CYCLES_LENGTH: u16 = 4560 / 4; -pub struct GbPpu{ +pub struct GbPpu { pub vram: VRam, pub oam:[u8;OAM_MEMORY_SIZE], pub state:PpuState, @@ -49,10 +48,8 @@ pub struct GbPpu{ vblank_occurred:bool, // a way to signal the rest of the system a vblank occurred - gfx_device: GFX, m_cycles_passed:u16, - screen_buffers: [[Pixel; SCREEN_HEIGHT * SCREEN_WIDTH];BUFFERS_NUMBER], - current_screen_buffer_index:usize, + screen_buffer: FrameBuffer, screen_buffer_index:usize, pixel_x_pos:u8, scanline_started:bool, @@ -64,10 +61,9 @@ pub struct GbPpu{ mode: Mode, } -impl GbPpu{ - pub fn new(device:GFX, mode: Mode) -> Self { +impl GbPpu { + pub fn new(mode: Mode) -> Self { Self{ - gfx_device: device, vram: VRam::default(), oam: [0;OAM_MEMORY_SIZE], stat_register: 0, @@ -75,8 +71,7 @@ impl GbPpu{ lcd_control: 0, bg_pos: Vec2::{x:0, y:0}, window_pos: Vec2::{x:0,y:0}, - screen_buffers:[[0;SCREEN_HEIGHT * SCREEN_WIDTH];BUFFERS_NUMBER], - current_screen_buffer_index:0, + screen_buffer: [0;SCREEN_HEIGHT * SCREEN_WIDTH], bg_palette_register:0, bg_color_mapping:[WHITE, LIGHT_GRAY, DARK_GRAY, BLACK], obj_pallete_0_register:0, @@ -114,8 +109,7 @@ impl GbPpu{ pub fn turn_off(&mut self){ self.m_cycles_passed = 0; //This is an expensive operation! - unsafe{core::ptr::write_bytes(self.screen_buffers[self.current_screen_buffer_index].as_mut_ptr(), 0xFF, SCREEN_HEIGHT * SCREEN_WIDTH)}; - self.swap_buffer(); + unsafe{core::ptr::write_bytes(self.screen_buffer.as_mut_ptr(), 0xFF, SCREEN_HEIGHT * SCREEN_WIDTH)}; self.state = PpuState::Hblank; self.update_stat_ppu_mode(); self.ly_register = 0; @@ -152,10 +146,9 @@ impl GbPpu{ return last_vblank_state; } - fn swap_buffer(&mut self){ - self.gfx_device.swap_buffer(&self.screen_buffers[self.current_screen_buffer_index]); + pub fn consume_framebuffer(&mut self) -> &FrameBuffer { self.screen_buffer_index = 0; - self.current_screen_buffer_index = (self.current_screen_buffer_index + 1) % BUFFERS_NUMBER; + return &self.screen_buffer; } fn update_stat_register(&mut self, if_register: &mut u8) -> u32{ @@ -239,7 +232,6 @@ impl GbPpu{ self.trigger_stat_interrupt = true; } self.vblank_occurred = true; - self.swap_buffer(); } else{ self.next_state = PpuState::OamSearch; @@ -453,7 +445,7 @@ impl GbPpu{ } fn push_pixel(&mut self, pixel: Pixel) { - self.screen_buffers[self.current_screen_buffer_index][self.screen_buffer_index] = pixel; + self.screen_buffer[self.screen_buffer_index] = pixel; self.screen_buffer_index += 1; } @@ -467,7 +459,7 @@ impl GbPpu{ } } -impl GbPpu{ +impl GbPpu { pub fn set_lcdcontrol_register(&mut self, register:u8){ if self.lcd_control & BIT_7_MASK != 0 && register & BIT_7_MASK == 0{ self.turn_off(); diff --git a/core/src/ppu/gfx_device.rs b/core/src/ppu/gfx_device.rs deleted file mode 100644 index 49973b92..00000000 --- a/core/src/ppu/gfx_device.rs +++ /dev/null @@ -1,9 +0,0 @@ -use super::gb_ppu::{SCREEN_HEIGHT, SCREEN_WIDTH}; - -/// Pixel is in the format of RGB565 even though the CGB stores pixels as BGR555 as the gbdev docs indicates, RGB565 is much more used format now days -/// The bits are represented as: RGB565 (low bits (BLue) -> high bits (Red)) -pub type Pixel = u16; - -pub trait GfxDevice{ - fn swap_buffer(&mut self, buffer:&[Pixel; SCREEN_HEIGHT * SCREEN_WIDTH]); -} \ No newline at end of file diff --git a/core/src/ppu/mod.rs b/core/src/ppu/mod.rs index b512a597..9c6a9add 100644 --- a/core/src/ppu/mod.rs +++ b/core/src/ppu/mod.rs @@ -2,8 +2,15 @@ pub mod gb_ppu; pub mod ppu_state; pub mod color; pub mod fifo; -pub mod gfx_device; mod attributes; mod vram; -pub use vram::VRam; \ No newline at end of file +pub use vram::VRam; + +use crate::ppu::gb_ppu::{SCREEN_HEIGHT, SCREEN_WIDTH}; + +/// Pixel is in the format of RGB565 even though the CGB stores pixels as BGR555 as the gbdev docs indicates, RGB565 is much more used format now days +/// The bits are represented as: RGB565 (low bits (BLue) -> high bits (Red)) +pub type Pixel = u16; + +pub type FrameBuffer = [Pixel; SCREEN_WIDTH * SCREEN_HEIGHT]; \ No newline at end of file diff --git a/core/tests/vram_dma_controller_tests.rs b/core/tests/vram_dma_controller_tests.rs index d0abf1fe..f87e6475 100644 --- a/core/tests/vram_dma_controller_tests.rs +++ b/core/tests/vram_dma_controller_tests.rs @@ -1,9 +1,4 @@ -use magenboy_core::{mmu::{vram_dma_controller::VramDmaController, external_memory_bus::*, carts::Mbc}, ppu::{gb_ppu::*, gfx_device::*, ppu_state::PpuState}, machine::Mode}; - -struct StubGfxDevice; -impl GfxDevice for StubGfxDevice{ - fn swap_buffer(&mut self, _:&[Pixel; SCREEN_HEIGHT * SCREEN_WIDTH]) {} -} +use magenboy_core::{mmu::{vram_dma_controller::VramDmaController, external_memory_bus::*, carts::Mbc}, ppu::{gb_ppu::*, ppu_state::PpuState}, machine::Mode}; const MEMORY_SIZE:usize = 0x1000; @@ -23,7 +18,7 @@ impl Mbc for EmptyMbc{ #[test] fn vram_dma_transfer_test(){ let mut controller = VramDmaController::new(); - let mut ppu = GbPpu::new(StubGfxDevice, Mode::CGB); + let mut ppu = GbPpu::new(Mode::CGB); let mut mbc = EmptyMbc{memory:[22;MEMORY_SIZE]}; let mut memory = ExternalMemoryBus::new(&mut mbc, None); let dma_len_reg = 100; @@ -44,7 +39,7 @@ fn vram_dma_transfer_test(){ #[test] fn vram_hblank_dma_transfer_test<'a>(){ let mut controller = VramDmaController::new(); - let mut ppu = GbPpu::new(StubGfxDevice, Mode::CGB); + let mut ppu = GbPpu::new(Mode::CGB); let mut mbc = EmptyMbc{memory:[22;MEMORY_SIZE]}; let mut memory = ExternalMemoryBus::new(&mut mbc, None); let dma_len_reg = 100; diff --git a/sdl/src/main.rs b/sdl/src/main.rs index f357d33e..de6b4a2b 100644 --- a/sdl/src/main.rs +++ b/sdl/src/main.rs @@ -5,8 +5,8 @@ mod sdl_joypad_provider; #[cfg(feature = "dbg")] mod terminal_debugger; -use magenboy_common::{audio::{ManualAudioResampler, ResampledAudioDevice}, check_for_terminal_feature_flag, get_terminal_feature_flag_value, init_and_run_gameboy, joypad_menu::*, menu::*, mpmc_gfx_device::*, EMULATOR_STATE}; -use magenboy_core::{apu::audio_device::*, keypad::joypad::NUM_OF_KEYS, ppu::{gb_ppu::{BUFFERS_NUMBER, SCREEN_HEIGHT, SCREEN_WIDTH}, gfx_device::{GfxDevice, Pixel}}, GB_FREQUENCY}; +use magenboy_common::{audio::{ManualAudioResampler, ResampledAudioDevice}, check_for_terminal_feature_flag, get_terminal_feature_flag_value, init_gameboy, joypad_menu::*, menu::*, GfxDevice, JoypadProvider, EMULATOR_STATE}; +use magenboy_core::{apu::audio_device::*, keypad::NUM_OF_KEYS, GB_FREQUENCY}; use std::{env, result::Result, vec::Vec}; use sdl2::sys::*; @@ -57,28 +57,33 @@ fn main() { let mut emulation_menu = MagenBoyMenu::new(provider, header.clone()); - let (s,r) = crossbeam_channel::bounded(BUFFERS_NUMBER - 1); - let mpmc_device = MpmcGfxDevice::new(s); - #[cfg(feature = "dbg")] let (debugger_ppu_layer_sender, debugger_ppu_layer_receiver) = crossbeam_channel::bounded::(0); - let args_clone = args.clone(); - let emualation_thread = std::thread::Builder::new() - .name("Emualtion Thread".to_string()) - .stack_size(0x100_0000) - .spawn(move || emulation_thread_main(args_clone, program_name, mpmc_device, #[cfg(feature = "dbg")]debugger_ppu_layer_sender)) - .unwrap(); + let mut devices: Vec::> = Vec::new(); + let audio_device = SdlAudioDevice::::new(44100); + devices.push(Box::new(audio_device)); + + if check_for_terminal_feature_flag(&args, "--file-audio"){ + let wav_ad = 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 joypad_provider = sdl_joypad_provider::SdlJoypadProvider::new(KEYBOARD_MAPPING, false); + + let mut gameboy = init_gameboy(&args, program_name, audio_devices, #[cfg(feature = "dbg")] terminal_debugger::TerminalDebugger::new(debugger_sender)); unsafe{ 'main:loop{ - while let Some(event) = gfx_device.poll_event(){ + while let Some(event) = SdlGfxDevice::poll_event() { if event.type_ == SDL_EventType::SDL_QUIT as u32{ EMULATOR_STATE.exit.store(true, std::sync::atomic::Ordering::Relaxed); break 'main; } else if event.type_ == SDL_EventType::SDL_KEYDOWN as u32 && event.key.keysym.scancode == SDL_Scancode::SDL_SCANCODE_ESCAPE{ - emulation_menu.pop_game_menu(&EMULATOR_STATE, &mut gfx_device, r.clone()); + emulation_menu.pop_game_menu(&EMULATOR_STATE, &mut gfx_device); } } @@ -95,14 +100,12 @@ fn main() { } } }else{ - let Ok(buffer) = r.recv() else {break}; - gfx_device.swap_buffer(&*(buffer as *const [Pixel; SCREEN_WIDTH * SCREEN_HEIGHT])); + let mut joypad = Default::default(); + joypad_provider.provide(&mut joypad); + let frame = gameboy.cycle_frame(joypad); + gfx_device.swap_buffer(frame); }} } - - drop(r); - EMULATOR_STATE.running.store(false, std::sync::atomic::Ordering::Relaxed); - emualation_thread.join().unwrap(); } } @@ -110,21 +113,3 @@ fn main() { unsafe{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: MpmcGfxDevice, #[cfg(feature = "dbg")] debugger_sender: crossbeam_channel::Sender) { - let mut devices: Vec::> = Vec::new(); - let audio_device = SdlAudioDevice::::new(44100); - devices.push(Box::new(audio_device)); - - if check_for_terminal_feature_flag(&args, "--file-audio"){ - let wav_ad = 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 joypad_provider = sdl_joypad_provider::SdlJoypadProvider::new(KEYBOARD_MAPPING, false); - - init_and_run_gameboy(args, program_name, spsc_gfx_device, joypad_provider, audio_devices, #[cfg(feature = "dbg")] terminal_debugger::TerminalDebugger::new(debugger_sender)); -} \ No newline at end of file diff --git a/sdl/src/sdl_gfx_device.rs b/sdl/src/sdl_gfx_device.rs index bd1ded43..95596bbe 100644 --- a/sdl/src/sdl_gfx_device.rs +++ b/sdl/src/sdl_gfx_device.rs @@ -2,8 +2,8 @@ use std::ffi::{CString, c_void}; use sdl2::sys::*; -use magenboy_common::EMULATOR_STATE; -use magenboy_core::{ppu::gb_ppu::{SCREEN_HEIGHT, SCREEN_WIDTH}, utils::vec2::Vec2, GfxDevice, Pixel}; +use magenboy_common::{GfxDevice, EMULATOR_STATE}; +use magenboy_core::{ppu::{gb_ppu::{SCREEN_HEIGHT, SCREEN_WIDTH}, Pixel}, utils::vec2::Vec2}; use super::utils::get_sdl_error_message; @@ -103,7 +103,7 @@ impl SdlGfxDevice{ }; } - pub fn poll_event(&self)->Option{ + pub fn poll_event()->Option{ unsafe{ let mut event: std::mem::MaybeUninit = std::mem::MaybeUninit::uninit(); // updating the events for the whole app diff --git a/sdl/src/sdl_joypad_provider.rs b/sdl/src/sdl_joypad_provider.rs index 60f8215e..b5179c30 100644 --- a/sdl/src/sdl_joypad_provider.rs +++ b/sdl/src/sdl_joypad_provider.rs @@ -1,6 +1,6 @@ use sdl2::sys::*; -use magenboy_core::keypad::{joypad::{Joypad, NUM_OF_KEYS}, joypad_provider::JoypadProvider}; -use magenboy_common::joypad_menu::MenuJoypadProvider; +use magenboy_core::keypad::{Joypad, NUM_OF_KEYS}; +use magenboy_common::{joypad_menu::MenuJoypadProvider, JoypadProvider}; use super::utils::get_sdl_error_message; From b28c9df8f746299f997053c384fff5939d571d12 Mon Sep 17 00:00:00 2001 From: alloncm Date: Sat, 18 Oct 2025 17:43:30 +0300 Subject: [PATCH 09/11] WIP more fixes On windows 11 the fps is not stable even in version 4.0.0 --- core/src/machine/gameboy.rs | 2 +- core/src/ppu/gb_ppu.rs | 9 +++++++++ sdl/src/main.rs | 10 +++++----- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/core/src/machine/gameboy.rs b/core/src/machine/gameboy.rs index a9657994..61293e71 100644 --- a/core/src/machine/gameboy.rs +++ b/core/src/machine/gameboy.rs @@ -14,7 +14,7 @@ pub struct GameBoy<'a, AD:AudioDevice, #[cfg(feature = "dbg")] DI:DebuggerInterf macro_rules! impl_gameboy { ($implementations:tt) => { #[cfg(feature = "dbg")] - impl<'a, JP:JoypadProvider, AD:AudioDevice, DUI:DebuggerInterface> GameBoy<'a, JP, AD, DUI> $implementations + impl<'a, AD:AudioDevice, DUI:DebuggerInterface> GameBoy<'a, AD, DUI> $implementations #[cfg(not(feature = "dbg"))] impl<'a, AD:AudioDevice> GameBoy<'a, AD> $implementations }; diff --git a/core/src/ppu/gb_ppu.rs b/core/src/ppu/gb_ppu.rs index bbd32e73..08e1c85e 100644 --- a/core/src/ppu/gb_ppu.rs +++ b/core/src/ppu/gb_ppu.rs @@ -49,6 +49,7 @@ pub struct GbPpu { vblank_occurred:bool, // a way to signal the rest of the system a vblank occurred m_cycles_passed:u16, + m_cycles_left: u32, screen_buffer: FrameBuffer, screen_buffer_index:usize, pixel_x_pos:u8, @@ -95,6 +96,7 @@ impl GbPpu { vblank_occurred:false, screen_buffer_index:0, m_cycles_passed:0, + m_cycles_left: 0, stat_triggered:false, trigger_stat_interrupt:false, bg_fetcher:BackgroundFetcher::new(), @@ -120,6 +122,9 @@ impl GbPpu { self.bg_fetcher.reset(); self.sprite_fetcher.reset(); self.pixel_x_pos = 0; + + // Trigger the vblank event + self.vblank_occurred = true; } pub fn turn_on(&mut self){ @@ -192,6 +197,8 @@ impl GbPpu { } fn cycle_fetcher(&mut self, m_cycles:u32, if_register:&mut u8)->u16{ + let m_cycles = m_cycles + self.m_cycles_left; + self.m_cycles_left = 0; let mut m_cycles_counter = 0; while m_cycles_counter < m_cycles{ @@ -232,6 +239,8 @@ impl GbPpu { self.trigger_stat_interrupt = true; } self.vblank_occurred = true; + self.m_cycles_left = m_cycles - m_cycles_counter; + break; } else{ self.next_state = PpuState::OamSearch; diff --git a/sdl/src/main.rs b/sdl/src/main.rs index de6b4a2b..0f7c2eac 100644 --- a/sdl/src/main.rs +++ b/sdl/src/main.rs @@ -5,16 +5,16 @@ mod sdl_joypad_provider; #[cfg(feature = "dbg")] mod terminal_debugger; -use magenboy_common::{audio::{ManualAudioResampler, ResampledAudioDevice}, check_for_terminal_feature_flag, get_terminal_feature_flag_value, init_gameboy, joypad_menu::*, menu::*, GfxDevice, JoypadProvider, EMULATOR_STATE}; -use magenboy_core::{apu::audio_device::*, keypad::NUM_OF_KEYS, GB_FREQUENCY}; - use std::{env, result::Result, vec::Vec}; + use sdl2::sys::*; +use magenboy_common::{audio::{ManualAudioResampler, ResampledAudioDevice}, check_for_terminal_feature_flag, get_terminal_feature_flag_value, init_gameboy, joypad_menu::*, menu::*, GfxDevice, JoypadProvider, EMULATOR_STATE}; +use magenboy_core::{apu::audio_device::*, keypad::NUM_OF_KEYS, GB_FREQUENCY}; + use crate::{sdl_gfx_device::SdlGfxDevice, audio::*, SdlAudioDevice}; const SCREEN_SCALE:usize = 4; -use sdl2::sys::SDL_Scancode; const KEYBOARD_MAPPING:[SDL_Scancode; NUM_OF_KEYS] = [ SDL_Scancode::SDL_SCANCODE_X, SDL_Scancode::SDL_SCANCODE_Z, @@ -99,7 +99,7 @@ fn main() { window.run(&result.0); } } - }else{ + } else { let mut joypad = Default::default(); joypad_provider.provide(&mut joypad); let frame = gameboy.cycle_frame(joypad); From b6ea36e92017fb14e396e12498bf3510ac82fc40 Mon Sep 17 00:00:00 2001 From: alloncm Date: Sat, 18 Oct 2025 19:43:03 +0300 Subject: [PATCH 10/11] Fixed core tests --- core/src/ppu/gb_ppu.rs | 1 + core/tests/integration_tests.rs | 53 ++++++++++++++++----------------- 2 files changed, 26 insertions(+), 28 deletions(-) diff --git a/core/src/ppu/gb_ppu.rs b/core/src/ppu/gb_ppu.rs index 08e1c85e..92b37dbd 100644 --- a/core/src/ppu/gb_ppu.rs +++ b/core/src/ppu/gb_ppu.rs @@ -239,6 +239,7 @@ impl GbPpu { self.trigger_stat_interrupt = true; } self.vblank_occurred = true; + // Save the remaining cycles and return early to avoid the framebuffer being modified before rendered self.m_cycles_left = m_cycles - m_cycles_counter; break; } diff --git a/core/tests/integration_tests.rs b/core/tests/integration_tests.rs index dcb01908..7b945400 100644 --- a/core/tests/integration_tests.rs +++ b/core/tests/integration_tests.rs @@ -1,21 +1,21 @@ -use std::{collections::hash_map::DefaultHasher, convert::TryInto, hash::{Hash, Hasher}, io::Read, sync::atomic::AtomicBool}; +use std::{collections::hash_map::DefaultHasher, convert::TryInto, hash::{Hash, Hasher}, io::Read}; -use magenboy_core::{keypad::{joypad::Joypad, joypad_provider::JoypadProvider}, machine::{Mode, gameboy::GameBoy, mbc_initializer::initialize_mbc}, mmu::{external_memory_bus::Bootrom, carts::Mbc}, ppu::{gb_ppu::{SCREEN_HEIGHT, SCREEN_WIDTH}, gfx_device::*}, apu::audio_device::*}; +use magenboy_core::{apu::audio_device::*, keypad::Joypad, machine::{gameboy::GameBoy, mbc_initializer::initialize_mbc, Mode}, mmu::{carts::Mbc, external_memory_bus::Bootrom}, ppu::{gb_ppu::{SCREEN_HEIGHT, SCREEN_WIDTH}, FrameBuffer}}; -struct CheckHashGfxDevice<'a>{ - hash: u64, +struct HashChecker { + expected_hash: u64, last_hash: u64, - found: &'a AtomicBool, } -impl<'a> GfxDevice for CheckHashGfxDevice<'a>{ - fn swap_buffer(&mut self, buffer:&[Pixel; SCREEN_HEIGHT * SCREEN_WIDTH]) { +impl HashChecker { + fn check(&mut self, buffer:&FrameBuffer) -> bool { let mut s = DefaultHasher::new(); buffer.hash(&mut s); let hash = s.finish(); - if self.last_hash == hash && hash == self.hash{ - self.found.store(true, std::sync::atomic::Ordering::Relaxed); + if self.last_hash == hash && hash == self.expected_hash{ + return true; } self.last_hash = hash; + return false; } } @@ -24,11 +24,6 @@ impl AudioDevice for StubAudioDevice{ fn push_buffer(&mut self, _buffer:&[StereoSample; BUFFER_SIZE]) {} } -struct StubJoypadProvider; -impl JoypadProvider for StubJoypadProvider{ - fn provide(&mut self, _joypad:&mut Joypad) {} -} - #[test] fn test_cpu_instrs(){ let file_url = "https://raw.githubusercontent.com/retrio/gb-test-roms/master/cpu_instrs/cpu_instrs.gb"; @@ -194,22 +189,21 @@ fn run_integration_test_from_url(program_url:&str, frames_to_execute:u32, expect fn run_integration_test(program:Vec, boot_rom:Option, frames_to_execute:u32, expected_hash:u64, fail_message:String, mode:Option){ let mbc:&'static mut dyn Mbc = initialize_mbc(&program, None); - let found = AtomicBool::new(false); + let mut checker = HashChecker{expected_hash, last_hash: 0}; let mut gameboy = match boot_rom { Some(b)=>GameBoy::new_with_bootrom( mbc, - StubJoypadProvider{}, StubAudioDevice{}, - CheckHashGfxDevice{hash:expected_hash,last_hash: 0, found: &found}, b), + b), None => GameBoy::new_with_mode(mbc, - StubJoypadProvider{}, StubAudioDevice{}, - CheckHashGfxDevice{hash:expected_hash,last_hash: 0, found: &found}, mode.unwrap()) + mode.unwrap()) }; for _ in 0..frames_to_execute { - gameboy.cycle_frame(); - if found.load(std::sync::atomic::Ordering::Relaxed){ + let frame = gameboy.cycle_frame(Joypad {..Default::default()}); + + if checker.check(frame) { return; } } @@ -238,13 +232,13 @@ fn generate_hash(){ } fn calc_hash(rom_path:&str, boot_rom_path:Option<&str>, mode:Option){ - struct GetHashGfxDevice{ + struct HashCalculator{ last_hash:u64, last_hash_counter:u32, frames_counter:u32 } - impl GfxDevice for GetHashGfxDevice{ - fn swap_buffer(&mut self, buffer:&[Pixel; SCREEN_HEIGHT * SCREEN_WIDTH]) { + impl HashCalculator{ + fn try_calc(&mut self, buffer:&FrameBuffer) { if self.frames_counter < 700{ self.frames_counter += 1; return; @@ -276,14 +270,17 @@ fn calc_hash(rom_path:&str, boot_rom_path:Option<&str>, mode:Option){ let mbc = initialize_mbc(&program, None); - let test_gfx_device = GetHashGfxDevice{ last_hash: 0, last_hash_counter: 0, frames_counter: 0 }; + let mut test_gfx_device = HashCalculator{ last_hash: 0, last_hash_counter: 0, frames_counter: 0 }; let mut gameboy = if let Some(boot_rom_path) = boot_rom_path{ let boot_rom = std::fs::read(boot_rom_path).expect("Cant find bootrom"); - GameBoy::new_with_bootrom(mbc, StubJoypadProvider{}, StubAudioDevice{}, test_gfx_device,Bootrom::Gb(boot_rom.try_into().unwrap())) + GameBoy::new_with_bootrom(mbc,StubAudioDevice{}, Bootrom::Gb(boot_rom.try_into().unwrap())) } else{ - GameBoy::new_with_mode(mbc, StubJoypadProvider{}, StubAudioDevice{}, test_gfx_device, mode.unwrap()) + GameBoy::new_with_mode(mbc, StubAudioDevice{}, mode.unwrap()) }; - loop {gameboy.cycle_frame();} + loop { + let buffer = gameboy.cycle_frame(Joypad{..Default::default()}); + test_gfx_device.try_calc(buffer); + } } \ No newline at end of file From 859f676372ddfaf857ed93a8ce37a4114b636ee1 Mon Sep 17 00:00:00 2001 From: alloncm Date: Fri, 31 Oct 2025 16:17:02 +0200 Subject: [PATCH 11/11] WIP moving apu to return value --- Cargo.lock | 60 +++++++++++++++++++----------------- Cargo.toml | 8 ++++- core/src/apu/audio_device.rs | 4 ++- nx/Cargo.toml | 2 +- nx/build.rs | 1 + 5 files changed, 44 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5ff01c38..b8072526 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -211,9 +211,8 @@ dependencies = [ [[package]] name = "cbindgen" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "975982cdb7ad6a142be15bdf84aea7ec6a9e5d4d797c004d43185b24cfe4e684" +version = "0.29.2" +source = "git+https://github.com/alloncm/cbindgen.git?branch=builder_with_line_endings#09d32e45f2c4639e603d65afbd219bd99b06cc27" dependencies = [ "clap 4.5.48", "heck", @@ -1550,10 +1549,11 @@ checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012" [[package]] name = "serde" -version = "1.0.203" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ + "serde_core", "serde_derive", ] @@ -1567,11 +1567,20 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + [[package]] name = "serde_derive" -version = "1.0.203" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -1591,9 +1600,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.9" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" dependencies = [ "serde", ] @@ -1781,44 +1790,42 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.23" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +checksum = "ed0aee96c12fa71097902e0bb061a5e1ebd766a6636bb605ba401c45c1650eac" dependencies = [ + "indexmap", "serde", "serde_spanned", "toml_datetime", - "toml_edit", + "toml_parser", + "toml_writer", + "winnow", ] [[package]] name = "toml_datetime" -version = "0.6.11" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" dependencies = [ "serde", ] [[package]] -name = "toml_edit" -version = "0.22.27" +name = "toml_parser" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +checksum = "97200572db069e74c512a14117b296ba0a80a30123fbbb5aa1f4a348f639ca30" dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime", - "toml_write", "winnow", ] [[package]] -name = "toml_write" -version = "0.1.2" +name = "toml_writer" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" +checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" [[package]] name = "tower" @@ -2281,9 +2288,6 @@ name = "winnow" version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" -dependencies = [ - "memchr", -] [[package]] name = "write16" diff --git a/Cargo.toml b/Cargo.toml index 7af2d232..d73593bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,4 +17,10 @@ rust-version = "1.74" # cause of cbindgen used to build for nx edition = "2021" [profile.release] -lto = true # Samller binaries and faster code \ No newline at end of file +lto = true # Samller binaries and faster code + +# Without this profile getting stackoverflow on debug builds +[profile.dev] +codegen-units = 1 +opt-level = 2 +debug = true diff --git a/core/src/apu/audio_device.rs b/core/src/apu/audio_device.rs index c90132ec..f11ac00c 100644 --- a/core/src/apu/audio_device.rs +++ b/core/src/apu/audio_device.rs @@ -1,3 +1,5 @@ +use crate::GB_FREQUENCY; + use super::NUMBER_OF_CHANNELS; pub type Sample = i16; @@ -5,7 +7,7 @@ pub const DEFAULT_SAPMPLE:Sample = 0 as Sample; const MAX_MASTER_VOLUME:Sample = 8; pub const SAMPLE_MAX: Sample = Sample::MAX / (MAX_MASTER_VOLUME * NUMBER_OF_CHANNELS as Sample); -pub const BUFFER_SIZE:usize = 0x2000; +pub const BUFFER_SIZE:usize = GB_FREQUENCY as usize / 60; #[derive(Clone, Copy)] #[repr(C, packed)] diff --git a/nx/Cargo.toml b/nx/Cargo.toml index dad845c1..eed4b8b0 100644 --- a/nx/Cargo.toml +++ b/nx/Cargo.toml @@ -17,4 +17,4 @@ log = "0.4" [build-dependencies] magenboy_common = {path = "../common"} -cbindgen = "0.29" \ No newline at end of file +cbindgen = { git = "https://github.com/alloncm/cbindgen.git", branch = "builder_with_line_endings" } \ No newline at end of file diff --git a/nx/build.rs b/nx/build.rs index 549e17bd..3e3ac80d 100644 --- a/nx/build.rs +++ b/nx/build.rs @@ -20,6 +20,7 @@ fn main() { .with_include_guard("MAGENBOY_H") .with_language(cbindgen::Language::C) .with_autogen_warning("// Do not edit this file directly, it is autogenerated by cbindgen during build process") + .with_line_endings(cbindgen::LineEndingStyle::CRLF) .generate() .expect("Unable to generate bindings") .write_to_file("src/magenboy.h");