diff --git a/frontends/libretro/src/lib.rs b/frontends/libretro/src/lib.rs index 2f99b7da1b437e4f814726a61d1493a1cd53c967..d196b293e7396c6316af703befe92e7885fc4c24 100644 --- a/frontends/libretro/src/lib.rs +++ b/frontends/libretro/src/lib.rs @@ -12,7 +12,7 @@ use std::{ use boytacean::{ gb::{AudioProvider, GameBoy}, pad::PadKey, - ppu::{DISPLAY_HEIGHT, DISPLAY_WIDTH, FRAME_BUFFER_RGB155_SIZE, RGB1555_SIZE}, + ppu::{PpuMode, DISPLAY_HEIGHT, DISPLAY_WIDTH, FRAME_BUFFER_RGB155_SIZE, RGB1555_SIZE}, rom::Cartridge, }; use consts::{ @@ -39,6 +39,17 @@ static mut INPUT_STATE_CALLBACK: Option< extern "C" fn(port: u32, device: u32, index: u32, id: u32) -> i16, > = None; +const KEYS: [RetroJoypad; 8] = [ + RetroJoypad::RetroDeviceIdJoypadUp, + RetroJoypad::RetroDeviceIdJoypadDown, + RetroJoypad::RetroDeviceIdJoypadLeft, + RetroJoypad::RetroDeviceIdJoypadRight, + RetroJoypad::RetroDeviceIdJoypadStart, + RetroJoypad::RetroDeviceIdJoypadSelect, + RetroJoypad::RetroDeviceIdJoypadA, + RetroJoypad::RetroDeviceIdJoypadB, +]; + #[derive(Clone, Copy, PartialEq, Eq, Hash)] pub enum RetroJoypad { RetroDeviceIdJoypadB = RETRO_DEVICE_ID_JOYPAD_B, @@ -88,17 +99,6 @@ impl Display for RetroJoypad { } } -const KEYS: [RetroJoypad; 8] = [ - RetroJoypad::RetroDeviceIdJoypadUp, - RetroJoypad::RetroDeviceIdJoypadDown, - RetroJoypad::RetroDeviceIdJoypadLeft, - RetroJoypad::RetroDeviceIdJoypadRight, - RetroJoypad::RetroDeviceIdJoypadStart, - RetroJoypad::RetroDeviceIdJoypadSelect, - RetroJoypad::RetroDeviceIdJoypadA, - RetroJoypad::RetroDeviceIdJoypadB, -]; - #[repr(C)] pub struct RetroGameInfo { pub path: *const c_char, @@ -137,6 +137,12 @@ pub struct RetroSystemTiming { sample_rate: f64, } +#[no_mangle] +pub extern "C" fn retro_api_version() -> c_uint { + println!("retro_api_version()"); + RETRO_API_VERSION +} + #[no_mangle] pub extern "C" fn retro_init() { println!("retro_init()"); @@ -158,12 +164,6 @@ pub extern "C" fn retro_reset() { emulator.reload(); } -#[no_mangle] -pub extern "C" fn retro_api_version() -> c_uint { - println!("retro_api_version()"); - RETRO_API_VERSION -} - /// # Safety /// /// This function should not be called only within Lib Retro context. @@ -202,60 +202,6 @@ pub extern "C" fn retro_set_environment( } } -#[no_mangle] -pub extern "C" fn retro_set_video_refresh( - callback: Option<extern "C" fn(*const u8, c_uint, c_uint, usize)>, -) { - println!("retro_set_video_refresh()"); - unsafe { - VIDEO_REFRESH_CALLBACK = callback; - } -} - -#[no_mangle] -pub extern "C" fn retro_set_audio_sample(callback: Option<extern "C" fn(i16, i16)>) { - println!("retro_set_audio_sample()"); - unsafe { - AUDIO_SAMPLE_CALLBACK = callback; - } -} - -#[no_mangle] -pub extern "C" fn retro_set_audio_sample_batch(callback: Option<extern "C" fn(*const i16, usize)>) { - println!("retro_set_audio_sample_batch()"); - unsafe { - AUDIO_SAMPLE_BATCH_CALLBACK = callback; - } -} - -#[no_mangle] -pub extern "C" fn retro_set_input_poll(callback: Option<extern "C" fn()>) { - println!("retro_set_input_poll()"); - unsafe { - INPUT_POLL_CALLBACK = callback; - } -} - -#[no_mangle] -pub extern "C" fn retro_set_input_state( - callback: Option<extern "C" fn(port: u32, device: u32, index: u32, id: u32) -> i16>, -) { - println!("retro_set_input_state()"); - unsafe { - INPUT_STATE_CALLBACK = callback; - } -} - -#[no_mangle] -pub extern "C" fn retro_load_game_special( - _system: u32, - _info: *const RetroGameInfo, - _num_info: usize, -) -> bool { - println!("retro_load_game_special()"); - false -} - #[no_mangle] pub extern "C" fn retro_set_controller_port_device() { println!("retro_set_controller_port_device()"); @@ -271,6 +217,8 @@ pub extern "C" fn retro_run() { let key_states = unsafe { KEY_STATES.as_mut().unwrap() }; let channels = emulator.audio_channels(); + let mut last_frame = emulator.ppu_frame(); + let mut counter_cycles = 0_u32; let cycle_limit = (GameBoy::CPU_FREQ as f32 * emulator.multiplier() as f32 / GameBoy::VISUAL_FREQ) @@ -289,6 +237,25 @@ pub extern "C" fn retro_run() { // and any other frequency based component of the system counter_cycles += emulator.clock() as u32; + // in case a new frame is available in the emulator + // then the frame must be pushed into display + if emulator.ppu_frame() != last_frame { + let frame_buffer = emulator.frame_buffer_rgb1555(); + unsafe { + FRAME_BUFFER.copy_from_slice(&frame_buffer); + video_refresh_cb( + FRAME_BUFFER.as_ptr(), + DISPLAY_WIDTH as u32, + DISPLAY_HEIGHT as u32, + DISPLAY_WIDTH * RGB1555_SIZE, + ); + } + + // obtains the index of the current PPU frame, this value + // is going to be used to detect for new frame presence + last_frame = emulator.ppu_frame(); + } + // obtains the audio buffer reference and queues it // in a batch manner using the audio callback at the // the end of the operation clears the buffer @@ -320,17 +287,6 @@ pub extern "C" fn retro_run() { } key_states.insert(key, current); } - - let frame_buffer = emulator.frame_buffer_rgb1555(); - unsafe { - FRAME_BUFFER.copy_from_slice(&frame_buffer); - video_refresh_cb( - FRAME_BUFFER.as_ptr(), - DISPLAY_WIDTH as u32, - DISPLAY_HEIGHT as u32, - DISPLAY_WIDTH * RGB1555_SIZE, - ); - } } #[no_mangle] @@ -356,6 +312,16 @@ pub unsafe extern "C" fn retro_load_game(game: *const RetroGameInfo) -> bool { true } +#[no_mangle] +pub extern "C" fn retro_load_game_special( + _system: u32, + _info: *const RetroGameInfo, + _num_info: usize, +) -> bool { + println!("retro_load_game_special()"); + false +} + #[no_mangle] pub extern "C" fn retro_unload_game() { println!("retro_unload_game()"); @@ -398,6 +364,50 @@ pub extern "C" fn retro_cheat_set() { println!("retro_cheat_set()"); } +#[no_mangle] +pub extern "C" fn retro_set_video_refresh( + callback: Option<extern "C" fn(*const u8, c_uint, c_uint, usize)>, +) { + println!("retro_set_video_refresh()"); + unsafe { + VIDEO_REFRESH_CALLBACK = callback; + } +} + +#[no_mangle] +pub extern "C" fn retro_set_audio_sample(callback: Option<extern "C" fn(i16, i16)>) { + println!("retro_set_audio_sample()"); + unsafe { + AUDIO_SAMPLE_CALLBACK = callback; + } +} + +#[no_mangle] +pub extern "C" fn retro_set_audio_sample_batch(callback: Option<extern "C" fn(*const i16, usize)>) { + println!("retro_set_audio_sample_batch()"); + unsafe { + AUDIO_SAMPLE_BATCH_CALLBACK = callback; + } +} + +#[no_mangle] +pub extern "C" fn retro_set_input_poll(callback: Option<extern "C" fn()>) { + println!("retro_set_input_poll()"); + unsafe { + INPUT_POLL_CALLBACK = callback; + } +} + +#[no_mangle] +pub extern "C" fn retro_set_input_state( + callback: Option<extern "C" fn(port: u32, device: u32, index: u32, id: u32) -> i16>, +) { + println!("retro_set_input_state()"); + unsafe { + INPUT_STATE_CALLBACK = callback; + } +} + fn retro_key_to_pad(retro_key: RetroJoypad) -> Option<PadKey> { match retro_key { RetroJoypad::RetroDeviceIdJoypadUp => Some(PadKey::Up), diff --git a/frontends/sdl/src/main.rs b/frontends/sdl/src/main.rs index 01e3e2cb793fbfc4201826a77a3d3dc701e4845e..1faf3570f936272428522a0e87afcb26be8da8f8 100644 --- a/frontends/sdl/src/main.rs +++ b/frontends/sdl/src/main.rs @@ -423,11 +423,12 @@ impl Emulator { if current_time >= self.next_tick_time_i { // re-starts the counter cycles with the number of pending cycles - // from the previous tick and the last frame with a dummy value - // meant to be overridden in case there's at least one new frame + // from the previous tick and the last frame with the system PPU + // frame index to be overridden in case there's at least one new frame // being drawn in the current tick let mut counter_cycles = pending_cycles; - let mut last_frame = 0xffffu16; + let mut last_frame = self.system.ppu_frame(); + let mut frame_dirty = false; // calculates the number of cycles that are meant to be the target // for the current "tick" operation this is basically the current @@ -450,11 +451,9 @@ impl Emulator { // and any other frequency based component of the system counter_cycles += self.system.clock() as u32; - // in case a V-Blank state has been reached a new frame is available + // in case a new frame is available from the emulator // then the frame must be pushed into SDL for display - if self.system.ppu_mode() == PpuMode::VBlank - && self.system.ppu_frame() != last_frame - { + if self.system.ppu_frame() != last_frame { // obtains the frame buffer of the Game Boy PPU and uses it // to update the stream texture, that will latter be copied // to the canvas @@ -464,6 +463,7 @@ impl Emulator { // obtains the index of the current PPU frame, this value // is going to be used to detect for new frame presence last_frame = self.system.ppu_frame(); + frame_dirty = true; } // in case the audio subsystem is enabled, then the audio buffer @@ -488,7 +488,7 @@ impl Emulator { // this separation between texture creation and canvas flush prevents // resources from being over-used in situations where multiple frames // are generated during the same tick cycle - if last_frame != 0xffffu16 { + if frame_dirty { // clears the graphics canvas, making sure that no garbage // pixel data remaining in the pixel buffer, not doing this would // create visual glitches in OSs like Mac OS X diff --git a/frontends/web/ts/gb.ts b/frontends/web/ts/gb.ts index f36ccc09396666772a1a992740413250e612452b..5a96259ffc8ba0c2caeecd7cee1ab9d0d6957093 100644 --- a/frontends/web/ts/gb.ts +++ b/frontends/web/ts/gb.ts @@ -274,7 +274,7 @@ export class GameboyEmulator extends EmulatorBase implements Emulator { // of cycles coming from the previous tick let counterCycles = pending; - let lastFrame = -1; + let lastFrame = this.gameBoy.ppu_frame(); while (true) { // limits the number of cycles to the provided @@ -288,13 +288,9 @@ export class GameboyEmulator extends EmulatorBase implements Emulator { const tickCycles = this.gameBoy.clock(); counterCycles += tickCycles; - // in case the current PPU mode is VBlank and the - // frame is different from the previously rendered - // one then it's time to update the canvas - if ( - this.gameBoy.ppu_mode() === PpuMode.VBlank && - this.gameBoy.ppu_frame() !== lastFrame - ) { + // in case the frame is different from the previously + // rendered one then it's time to update the canvas + if (this.gameBoy.ppu_frame() !== lastFrame) { // updates the reference to the last frame index // to be used for comparison in the next tick lastFrame = this.gameBoy.ppu_frame();