Skip to content

Commit

Permalink
Extract libretro devices and logging to another modules
Browse files Browse the repository at this point in the history
  • Loading branch information
alloncm committed May 4, 2024
1 parent 126ac96 commit eb6f11e
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 116 deletions.
70 changes: 70 additions & 0 deletions libretro/src/devices.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
use std::{ffi::c_void, mem::size_of};

use libretro_sys::*;

use magenboy_common::audio::*;
use magenboy_core::{apu::audio_device::*, keypad::{button::*, joypad::*, joypad_provider::*}, ppu::{gb_ppu::*, gfx_device::*}, GB_FREQUENCY};

use super::GAMEBOY_CORE;

pub struct RetroGfxDevice;
impl GfxDevice for RetroGfxDevice{
fn swap_buffer(&mut self, buffer:&[Pixel; SCREEN_HEIGHT * SCREEN_WIDTH]) {
unsafe{(GAMEBOY_CORE.video_cb.unwrap())(buffer.as_ptr() as *const c_void, SCREEN_WIDTH as u32, SCREEN_HEIGHT as u32, SCREEN_WIDTH * size_of::<Pixel>())};
}
}

pub struct RetroJoypadProvider;
impl JoypadProvider for RetroJoypadProvider{
fn provide(&mut self, joypad:&mut Joypad) {
unsafe{
(GAMEBOY_CORE.input_poll_cb.unwrap())();

let input_cb: unsafe extern fn(port:u32, device:u32, index:u32, id:u32) -> i16 = GAMEBOY_CORE.input_cb.unwrap();
joypad.buttons[Button::A as usize] = input_cb(0, DEVICE_JOYPAD, 0, DEVICE_ID_JOYPAD_A) != 0 ||
input_cb(0, DEVICE_JOYPAD, 0, DEVICE_ID_JOYPAD_X) != 0;
joypad.buttons[Button::B as usize] = input_cb(0, DEVICE_JOYPAD, 0, DEVICE_ID_JOYPAD_B) != 0 ||
input_cb(0, DEVICE_JOYPAD, 0, DEVICE_ID_JOYPAD_Y) != 0;
joypad.buttons[Button::Start as usize] = input_cb(0, DEVICE_JOYPAD, 0, DEVICE_ID_JOYPAD_START) != 0;
joypad.buttons[Button::Select as usize] = input_cb(0, DEVICE_JOYPAD, 0, DEVICE_ID_JOYPAD_SELECT) != 0;
joypad.buttons[Button::Up as usize] = input_cb(0, DEVICE_JOYPAD, 0, DEVICE_ID_JOYPAD_UP) != 0;
joypad.buttons[Button::Down as usize] = input_cb(0, DEVICE_JOYPAD, 0, DEVICE_ID_JOYPAD_DOWN) != 0;
joypad.buttons[Button::Right as usize] = input_cb(0, DEVICE_JOYPAD, 0, DEVICE_ID_JOYPAD_RIGHT) != 0;
joypad.buttons[Button::Left as usize] = input_cb(0, DEVICE_JOYPAD, 0, DEVICE_ID_JOYPAD_LEFT) != 0;
}
}
}

pub struct RetroAudioDevice{
resampler: ManualAudioResampler
}

impl RetroAudioDevice{
pub const OUTPUT_FREQUENCY:u32 = 48000;
}

impl Default for RetroAudioDevice{
fn default()->Self{
Self { resampler: ManualAudioResampler::new(GB_FREQUENCY, Self::OUTPUT_FREQUENCY) }
}
}

impl AudioDevice for RetroAudioDevice{
fn push_buffer(&mut self, buffer:&[StereoSample; BUFFER_SIZE]) {
let mut resampled = self.resampler.resample(buffer);
unsafe{GAMEBOY_CORE.audio_buffer.append(&mut resampled)};
}
}

impl RetroAudioDevice{
pub unsafe fn push_audio_buffer_to_libretro(){
let mut remaining_frames = GAMEBOY_CORE.audio_buffer.len();
let mut buffer_pos_ptr = GAMEBOY_CORE.audio_buffer.as_ptr() as *const Sample;
while remaining_frames > 0 {
let uploaded_frames = (GAMEBOY_CORE.audio_cb.unwrap())(buffer_pos_ptr, remaining_frames);
remaining_frames -= uploaded_frames;
buffer_pos_ptr = buffer_pos_ptr.add(uploaded_frames);
}
GAMEBOY_CORE.audio_buffer.clear();
}
}
139 changes: 23 additions & 116 deletions libretro/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
use std::{ffi::{c_char, c_uint, c_void, CString}, mem::{size_of, MaybeUninit}, ptr::null_mut, slice, sync::{Mutex, OnceLock}};
mod devices;
mod logging;

use std::{ffi::{c_char, c_uint, c_void}, mem::MaybeUninit, ptr::null_mut, slice};

use libretro_sys::*;
use log::Log;

use magenboy_common::audio::*;
use magenboy_core::{apu::audio_device::*, keypad::{button::Button, joypad::*, joypad_provider::JoypadProvider}, machine::{gameboy::GameBoy, mbc_initializer}, mmu::external_memory_bus::Bootrom, ppu::{gb_ppu::*, gfx_device::*}, GB_FREQUENCY};
use magenboy_core::{apu::audio_device::*, machine::{gameboy::GameBoy, mbc_initializer}, mmu::external_memory_bus::Bootrom, ppu::gb_ppu::*};

use crate::{devices::*, logging::*};

pub struct MagenBoyRetroCore<'a>{
gameboy: Option<GameBoy<'a, RetroJoypadProvider, RetroAudioDevice, RetroGfxDevice>>,
Expand All @@ -17,113 +20,26 @@ pub struct MagenBoyRetroCore<'a>{
environment_cb: Option<EnvironmentFn>
}

struct RetroGfxDevice;
impl GfxDevice for RetroGfxDevice{
fn swap_buffer(&mut self, buffer:&[Pixel; SCREEN_HEIGHT * SCREEN_WIDTH]) {
unsafe{(GAMEBOY_CORE.video_cb.unwrap())(buffer.as_ptr() as *const c_void, SCREEN_WIDTH as u32, SCREEN_HEIGHT as u32, SCREEN_WIDTH * size_of::<Pixel>())};
}
}

struct RetroJoypadProvider;

impl JoypadProvider for RetroJoypadProvider{
fn provide(&mut self, joypad:&mut Joypad) {
unsafe{
(GAMEBOY_CORE.input_poll_cb.unwrap())();

let input_cb: unsafe extern fn(port:u32, device:u32, index:u32, id:u32) -> i16 = GAMEBOY_CORE.input_cb.unwrap();
joypad.buttons[Button::A as usize] = input_cb(0, DEVICE_JOYPAD, 0, DEVICE_ID_JOYPAD_A) != 0 ||
input_cb(0, DEVICE_JOYPAD, 0, DEVICE_ID_JOYPAD_X) != 0;
joypad.buttons[Button::B as usize] = input_cb(0, DEVICE_JOYPAD, 0, DEVICE_ID_JOYPAD_B) != 0 ||
input_cb(0, DEVICE_JOYPAD, 0, DEVICE_ID_JOYPAD_Y) != 0;
joypad.buttons[Button::Start as usize] = input_cb(0, DEVICE_JOYPAD, 0, DEVICE_ID_JOYPAD_START) != 0;
joypad.buttons[Button::Select as usize] = input_cb(0, DEVICE_JOYPAD, 0, DEVICE_ID_JOYPAD_SELECT) != 0;
joypad.buttons[Button::Up as usize] = input_cb(0, DEVICE_JOYPAD, 0, DEVICE_ID_JOYPAD_UP) != 0;
joypad.buttons[Button::Down as usize] = input_cb(0, DEVICE_JOYPAD, 0, DEVICE_ID_JOYPAD_DOWN) != 0;
joypad.buttons[Button::Right as usize] = input_cb(0, DEVICE_JOYPAD, 0, DEVICE_ID_JOYPAD_RIGHT) != 0;
joypad.buttons[Button::Left as usize] = input_cb(0, DEVICE_JOYPAD, 0, DEVICE_ID_JOYPAD_LEFT) != 0;
}
}
}

struct RetroAudioDevice{
resampler: ManualAudioResampler
}

impl RetroAudioDevice{
const OUTPUT_FREQUENCY:u32 = 48000;
}

impl Default for RetroAudioDevice{
fn default()->Self{
Self { resampler: ManualAudioResampler::new(GB_FREQUENCY, Self::OUTPUT_FREQUENCY) }
}
}

impl AudioDevice for RetroAudioDevice{
fn push_buffer(&mut self, buffer:&[StereoSample; BUFFER_SIZE]) {
let mut resampled = self.resampler.resample(buffer);
unsafe{GAMEBOY_CORE.audio_buffer.append(&mut resampled)};
}
}

struct RetroLogger{
retro_logger: Option<Mutex<LogCallback>>
}

impl RetroLogger{
pub fn init(max_log_level:log::LevelFilter, log_cb: Option<LogCallback>){
static LOGGER: OnceLock<RetroLogger> = OnceLock::new();
let logger = match log_cb{
Some(cb) => LOGGER.get_or_init(||Self{ retro_logger: Some(Mutex::new(cb))}),
None => LOGGER.get_or_init(||Self { retro_logger: None }),
};
log::set_logger(logger).unwrap();
log::set_max_level(max_log_level);
}
}

impl Log for RetroLogger{
fn enabled(&self, metadata: &log::Metadata) -> bool {
return self.retro_logger.is_some() && metadata.level() >= log::max_level();
}

fn log(&self, record: &log::Record) {
let logger = self.retro_logger.as_ref().unwrap().lock().unwrap();
let level = match record.level(){
log::Level::Error => LogLevel::Error,
log::Level::Warn => LogLevel::Warn,
log::Level::Info => LogLevel::Info,
log::Level::Debug |
log::Level::Trace => LogLevel::Debug,
};
let message = CString::new(format!("{}\n", record.args())).unwrap();
unsafe{(logger.log)(level, message.as_ptr())};
}

fn flush(&self) {}
}

static mut GAMEBOY_CORE: MagenBoyRetroCore = MagenBoyRetroCore{
pub(crate) static mut GAMEBOY_CORE: MagenBoyRetroCore = MagenBoyRetroCore{
gameboy: None, save_data_fat_ptr: None, audio_buffer:Vec::new(), video_cb: None, audio_cb: None, input_poll_cb: None, input_cb: None, environment_cb: None,
};

const NAME:*const c_char = b"MagenBoy\0".as_ptr() as _;
const VERSION_SIZE:usize = magenboy_common::VERSION.len() + 1;
const VERSION:[u8;VERSION_SIZE] = {
let mut result:[u8;VERSION_SIZE] = [0;VERSION_SIZE];
let mut i = 0;
while i < VERSION_SIZE - 1{
result[i] = magenboy_common::VERSION.as_bytes()[i];
i +=1 ;
}
result[VERSION_SIZE - 1] = b'\0';
result
};
const VALID_EXTENSIONS:*const c_char = b"gb|gbc\0".as_ptr() as _;

#[no_mangle]
pub unsafe extern "C" fn retro_get_system_info(system_info: *mut SystemInfo){
const NAME:*const c_char = b"MagenBoy\0".as_ptr() as _;
const VERSION_SIZE:usize = magenboy_common::VERSION.len() + 1;
const VERSION:[u8;VERSION_SIZE] = {
let mut result:[u8;VERSION_SIZE] = [0;VERSION_SIZE];
let mut i = 0;
while i < VERSION_SIZE - 1{
result[i] = magenboy_common::VERSION.as_bytes()[i];
i +=1 ;
}
result[VERSION_SIZE - 1] = b'\0';
result
};
const VALID_EXTENSIONS:*const c_char = b"gb|gbc\0".as_ptr() as _;

std::ptr::write_bytes(system_info, 0, 1);
(*system_info).library_name = NAME;
(*system_info).library_version = VERSION.as_ptr() as *const c_char;
Expand Down Expand Up @@ -209,16 +125,7 @@ pub unsafe extern "C" fn retro_get_memory_size(id:c_uint)->isize{
#[no_mangle]
pub unsafe extern "C" fn retro_run(){
GAMEBOY_CORE.gameboy.as_mut().unwrap().cycle_frame();

// push audio buffer
let mut remaining_frames = GAMEBOY_CORE.audio_buffer.len();
let mut buffer_pos_ptr = GAMEBOY_CORE.audio_buffer.as_ptr() as *const Sample;
while remaining_frames > 0 {
let uploaded_frames = (GAMEBOY_CORE.audio_cb.unwrap())(buffer_pos_ptr, remaining_frames);
remaining_frames -= uploaded_frames;
buffer_pos_ptr = buffer_pos_ptr.add(uploaded_frames);
}
GAMEBOY_CORE.audio_buffer.clear();
RetroAudioDevice::push_audio_buffer_to_libretro();
}

#[no_mangle] pub extern "C" fn retro_load_game_special(_:c_uint, _:*const GameInfo, _:isize)->bool{false}
Expand Down
41 changes: 41 additions & 0 deletions libretro/src/logging.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use std::{ffi::CString ,sync::{Mutex, OnceLock}};

use libretro_sys::*;
use log::Log;

pub struct RetroLogger{
retro_logger: Option<Mutex<LogCallback>>
}

impl RetroLogger{
pub fn init(max_log_level:log::LevelFilter, log_cb: Option<LogCallback>){
static LOGGER: OnceLock<RetroLogger> = OnceLock::new();
let logger = match log_cb{
Some(cb) => LOGGER.get_or_init(||Self{ retro_logger: Some(Mutex::new(cb))}),
None => LOGGER.get_or_init(||Self { retro_logger: None }),
};
log::set_logger(logger).unwrap();
log::set_max_level(max_log_level);
}
}

impl Log for RetroLogger{
fn enabled(&self, metadata: &log::Metadata) -> bool {
return self.retro_logger.is_some() && metadata.level() >= log::max_level();
}

fn log(&self, record: &log::Record) {
let logger = self.retro_logger.as_ref().unwrap().lock().unwrap();
let level = match record.level(){
log::Level::Error => LogLevel::Error,
log::Level::Warn => LogLevel::Warn,
log::Level::Info => LogLevel::Info,
log::Level::Debug |
log::Level::Trace => LogLevel::Debug,
};
let message = CString::new(format!("{}\n", record.args())).unwrap();
unsafe{(logger.log)(level, message.as_ptr())};
}

fn flush(&self) {}
}

0 comments on commit eb6f11e

Please sign in to comment.