diff --git a/CHANGELOG.md b/CHANGELOG.md index a92c0b596ec916a3de2d1d80f27cfa11680b3e0f..290c3c3bccd6b56ba8f3799684049075f842ee09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Support for Python 3 API - [#36](https://gitlab.stage.hive.pt/joamag/boytacean/-/issues/36) * `next_frame()` method for frame by frame navigation +* Support for palette switching option in Libretro - [#37](https://gitlab.stage.hive.pt/joamag/boytacean/-/issues/37) ### Changed diff --git a/frontends/libretro/src/consts.rs b/frontends/libretro/src/consts.rs index 9b35e0e2bb2d641653465026d5dc0879f5614a15..51082f85836ab04d04548a52920830b50769f4ff 100644 --- a/frontends/libretro/src/consts.rs +++ b/frontends/libretro/src/consts.rs @@ -2,6 +2,9 @@ pub const RETRO_API_VERSION: u32 = 1; pub const REGION_NTSC: u32 = 0; pub const RETRO_ENVIRONMENT_SET_PIXEL_FORMAT: u32 = 10; +pub const RETRO_ENVIRONMENT_GET_VARIABLE: u32 = 15; +pub const RETRO_ENVIRONMENT_SET_VARIABLES: u32 = 16; +pub const RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE: u32 = 17; pub const RETRO_PIXEL_FORMAT_0RGB1555: usize = 0; pub const RETRO_PIXEL_FORMAT_XRGB8888: usize = 1; diff --git a/frontends/libretro/src/lib.rs b/frontends/libretro/src/lib.rs index 3893b0d27d15c49fd12a6fab91f92160fe17d413..cb8d838fce9cf50f6eafd31e3293570cbbc4e8d8 100644 --- a/frontends/libretro/src/lib.rs +++ b/frontends/libretro/src/lib.rs @@ -1,6 +1,7 @@ #![allow(clippy::uninlined_format_args)] pub mod consts; +pub mod palettes; use boytacean::{ debugln, @@ -19,8 +20,11 @@ use consts::{ RETRO_DEVICE_ID_JOYPAD_R2, RETRO_DEVICE_ID_JOYPAD_R3, RETRO_DEVICE_ID_JOYPAD_RIGHT, RETRO_DEVICE_ID_JOYPAD_SELECT, RETRO_DEVICE_ID_JOYPAD_START, RETRO_DEVICE_ID_JOYPAD_UP, RETRO_DEVICE_ID_JOYPAD_X, RETRO_DEVICE_ID_JOYPAD_Y, RETRO_DEVICE_JOYPAD, - RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, RETRO_PIXEL_FORMAT_XRGB8888, + RETRO_ENVIRONMENT_GET_VARIABLE, RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, + RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, RETRO_ENVIRONMENT_SET_VARIABLES, + RETRO_PIXEL_FORMAT_XRGB8888, }; +use palettes::get_palette; use std::{ collections::HashMap, ffi::CStr, @@ -60,6 +64,11 @@ static mut INPUT_POLL_CALLBACK: Option<extern "C" fn()> = None; static mut INPUT_STATE_CALLBACK: Option< extern "C" fn(port: u32, device: u32, index: u32, id: u32) -> i16, > = None; +static mut UPDATED: bool = false; +static mut VARIABLE: RetroVariable = RetroVariable { + key: "palette\0".as_ptr() as *const c_char, + value: std::ptr::null(), +}; const KEYS: [RetroJoypad; 8] = [ RetroJoypad::RetroDeviceIdJoypadUp, @@ -71,6 +80,17 @@ const KEYS: [RetroJoypad; 8] = [ RetroJoypad::RetroDeviceIdJoypadA, RetroJoypad::RetroDeviceIdJoypadB, ]; +const VARIABLES: [RetroVariable; 2] = [ + RetroVariable { + key: "palette\0".as_ptr() as *const c_char, + value: "DMG color palette; basic|hogwards|christmas|goldsilver|pacman|mariobros|pokemon\0" + .as_ptr() as *const c_char, + }, + RetroVariable { + key: std::ptr::null(), + value: std::ptr::null(), + }, +]; #[derive(Clone, Copy, PartialEq, Eq, Hash)] pub enum RetroJoypad { @@ -159,6 +179,12 @@ pub struct RetroSystemTiming { sample_rate: f64, } +#[repr(C)] +pub struct RetroVariable { + key: *const c_char, + value: *const c_char, +} + #[no_mangle] pub extern "C" fn retro_api_version() -> c_uint { debugln!("retro_api_version()"); @@ -233,6 +259,11 @@ pub extern "C" fn retro_set_environment( debugln!("retro_set_environment()"); unsafe { ENVIRONMENT_CALLBACK = callback; + let environment_cb = ENVIRONMENT_CALLBACK.as_ref().unwrap(); + environment_cb( + RETRO_ENVIRONMENT_SET_VARIABLES, + &VARIABLES as *const _ as *const c_void, + ); } } @@ -244,6 +275,7 @@ pub extern "C" fn retro_set_controller_port_device() { #[no_mangle] pub extern "C" fn retro_run() { let emulator = unsafe { EMULATOR.as_mut().unwrap() }; + let environment_cb = unsafe { ENVIRONMENT_CALLBACK.as_ref().unwrap() }; let video_refresh_cb = unsafe { VIDEO_REFRESH_CALLBACK.as_ref().unwrap() }; let sample_batch_cb = unsafe { AUDIO_SAMPLE_BATCH_CALLBACK.as_ref().unwrap() }; let input_poll_cb = unsafe { INPUT_POLL_CALLBACK.as_ref().unwrap() }; @@ -257,6 +289,20 @@ pub extern "C" fn retro_run() { / GameBoy::VISUAL_FREQ) .round() as u32; + // determines if any of the variable has changed value + // if that's the case all of them must be polled for + // update, and if needed action is triggered + unsafe { + environment_cb( + RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, + &UPDATED as *const _ as *const c_void, + ); + if UPDATED { + update_vars(); + UPDATED = false; + } + } + loop { // limits the number of ticks to the typical number // of cycles expected for the current logic cycle @@ -340,6 +386,7 @@ pub unsafe extern "C" fn retro_load_game(game: *const RetroGameInfo) -> bool { instance.reset(); instance.load(true); instance.load_cartridge(rom); + update_vars(); true } @@ -488,6 +535,25 @@ pub extern "C" fn retro_set_input_state( } } +unsafe fn update_vars() { + update_palette(); +} + +unsafe fn update_palette() { + let emulator = EMULATOR.as_mut().unwrap(); + let environment_cb = ENVIRONMENT_CALLBACK.as_ref().unwrap(); + environment_cb( + RETRO_ENVIRONMENT_GET_VARIABLE, + &VARIABLE as *const _ as *const c_void, + ); + if VARIABLE.value.is_null() { + return; + } + let palette_name = String::from(CStr::from_ptr(VARIABLE.value).to_str().unwrap()); + let palette_info: boytacean::ppu::PaletteInfo = get_palette(palette_name); + emulator.ppu().set_palette_colors(palette_info.colors()); +} + fn retro_key_to_pad(retro_key: RetroJoypad) -> Option<PadKey> { match retro_key { RetroJoypad::RetroDeviceIdJoypadUp => Some(PadKey::Up), diff --git a/frontends/libretro/src/palettes.rs b/frontends/libretro/src/palettes.rs new file mode 100644 index 0000000000000000000000000000000000000000..f0340a59d7c351bf90f64dffebe9fed8dabc08ca --- /dev/null +++ b/frontends/libretro/src/palettes.rs @@ -0,0 +1,91 @@ +use boytacean::ppu::{Palette, PaletteInfo}; + +pub struct PaletteInfoStatic { + name: &'static str, + colors: Palette, +} + +impl PaletteInfoStatic { + pub fn into_palette_info(&self) -> PaletteInfo { + PaletteInfo::new(self.name, self.colors) + } +} + +static PALETTES: [PaletteInfoStatic; 7] = [ + PaletteInfoStatic { + name: "basic", + colors: [ + [0xff, 0xff, 0xff], + [0xc0, 0xc0, 0xc0], + [0x60, 0x60, 0x60], + [0x00, 0x00, 0x00], + ], + }, + PaletteInfoStatic { + name: "hogwards", + colors: [ + [0xb6, 0xa5, 0x71], + [0x8b, 0x7e, 0x56], + [0x55, 0x4d, 0x35], + [0x20, 0x1d, 0x13], + ], + }, + PaletteInfoStatic { + name: "christmas", + colors: [ + [0xe8, 0xe7, 0xdf], + [0x8b, 0xab, 0x95], + [0x9e, 0x5c, 0x5e], + [0x53, 0x4d, 0x57], + ], + }, + PaletteInfoStatic { + name: "goldsilver", + colors: [ + [0xc5, 0xc6, 0x6d], + [0x97, 0xa1, 0xb0], + [0x58, 0x5e, 0x67], + [0x23, 0x52, 0x29], + ], + }, + PaletteInfoStatic { + name: "pacman", + colors: [ + [0xff, 0xff, 0x00], + [0xff, 0xb8, 0x97], + [0x37, 0x32, 0xff], + [0x00, 0x00, 0x00], + ], + }, + PaletteInfoStatic { + name: "mariobros", + colors: [ + [0xf7, 0xce, 0xc3], + [0xcc, 0x9e, 0x22], + [0x92, 0x34, 0x04], + [0x00, 0x00, 0x00], + ], + }, + PaletteInfoStatic { + name: "pokemon", + colors: [ + [0xf8, 0x78, 0x00], + [0xb8, 0x60, 0x00], + [0x78, 0x38, 0x00], + [0x00, 0x00, 0x00], + ], + }, +]; + +pub fn get_palette_names() -> Vec<String> { + PALETTES.iter().map(|p| p.name.to_string()).collect() +} + +pub fn get_palette(name: String) -> PaletteInfo { + for palette in PALETTES.iter() { + if palette.name == name { + return palette.into_palette_info(); + } + } + PALETTES[0].into_palette_info() +} diff --git a/src/ppu.rs b/src/ppu.rs index 542d92d365bf4a6586e140308e8cb870ad71bde8..87287b576e22bc98faae3b840c743d467886a4e0 100644 --- a/src/ppu.rs +++ b/src/ppu.rs @@ -127,8 +127,8 @@ pub type Palette = [Pixel; PALETTE_SIZE]; /// with alpha within the Game Boy context. pub type PaletteAlpha = [PixelAlpha; PALETTE_SIZE]; -/// Represents a palette with the metadata that is -/// associated with it. +/// Represents a palette together with the metadata +/// that is associated with it. #[cfg_attr(feature = "wasm", wasm_bindgen)] #[derive(Clone, PartialEq, Eq)] pub struct PaletteInfo {