1mod finitefloat; 2 3pub use finitefloat::{FiniteF32, FiniteF64}; 4 5use crate::input::Player; 6use bincode::{Decode, Encode}; 7use jgenesis_proc_macros::{EnumAll, EnumDisplay, EnumFromStr}; 8use std::borrow::Cow; 9use std::error::Error; 10use std::fmt::{Debug, Display, Formatter}; 11use std::hash::Hash; 12 13#[repr(C)] 14#[derive(Debug, Clone, Copy, PartialEq, Eq, bytemuck::Pod, bytemuck::Zeroable, Encode, Decode)] 15pub struct Color { 16 pub r: u8, 17 pub g: u8, 18 pub b: u8, 19 pub a: u8, 20} 21 22impl Color { 23 pub const BLACK: Self = Self::rgb(0, 0, 0); 24 25 pub const TRANSPARENT: Self = Self::rgba(0, 0, 0, 0); 26 27 #[must_use] 28 #[inline] 29 pub const fn rgb(r: u8, g: u8, b: u8) -> Self { 30 Self { r, g, b, a: 255 } 31 } 32 33 #[must_use] 34 #[inline] 35 pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self { 36 Self { r, g, b, a } 37 } 38} 39 40impl Default for Color { 41 #[inline] 42 fn default() -> Self { 43 Self::BLACK 44 } 45} 46 47#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 48pub struct FrameSize { 49 pub width: u32, 50 pub height: u32, 51} 52 53impl FrameSize { 54 #[allow(clippy::len_without_is_empty)] 55 #[must_use] 56 pub fn len(self) -> u32 { 57 self.width * self.height 58 } 59} 60 61#[derive(Debug, Clone, Copy)] 62pub struct DisplayArea { 63 pub width: u32, 64 pub height: u32, 65 pub x: u32, 66 pub y: u32, 67 pub pixel_density: f32, 68} 69 70#[derive(Debug, Clone, Copy)] 71pub enum Rotation { 72 None, 73 Clockwise, 74 OneEighty, 75 Counterclockwise, 76} 77 78#[derive(Debug, Clone, Copy)] 79pub struct DisplayInfo { 80 pub frame_size: FrameSize, 81 pub display_area: DisplayArea, 82 pub rotation: Rotation, 83} 84 85#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Encode, Decode)] 86pub enum ColorCorrection { 87 #[default] 88 None, 89 GbcLcd { 90 screen_gamma: FiniteF32, 91 }, 92 GbaLcd { 93 screen_gamma: FiniteF32, 94 }, 95} 96 97impl Display for ColorCorrection { 98 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 99 match self { 100 Self::None => write!(f, "None"), 101 &Self::GbcLcd { screen_gamma } => { 102 write!(f, "Game Boy Color LCD (gamma {:.1})", f32::from(screen_gamma)) 103 } 104 &Self::GbaLcd { screen_gamma } => { 105 write!(f, "Game Boy Advance LCD (gamma {:.1})", f32::from(screen_gamma)) 106 } 107 } 108 } 109} 110 111#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 112pub enum SamplesPerColorCycle { 113 Twelve, // ~42.95 MHz sample rate (NES, SNES) 114 Fifteen, // ~53.69 MHz sample rate (SMS, Genesis) 115} 116 117impl From<SamplesPerColorCycle> for u32 { 118 fn from(value: SamplesPerColorCycle) -> Self { 119 match value { 120 SamplesPerColorCycle::Twelve => 12, 121 SamplesPerColorCycle::Fifteen => 15, 122 } 123 } 124} 125 126impl From<SamplesPerColorCycle> for u64 { 127 fn from(value: SamplesPerColorCycle) -> Self { 128 u32::from(value).into() 129 } 130} 131 132#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 133pub struct CompositeParams { 134 // How many times to repeat each frame buffer pixel 135 pub upscale_factor: u32, 136 pub samples_per_color_cycle: SamplesPerColorCycle, 137} 138 139#[derive(Debug, Clone, Copy, PartialEq, Eq)] 140pub struct NtscPerFrameParams { 141 pub frame_phase_offset: u64, 142 pub per_line_phase_offset: u64, 143} 144 145/// Rendering options that are not required to be explicitly specified, unlike frame size 146#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] 147pub struct RenderFrameOptions { 148 pub pixel_aspect_ratio: Option<FiniteF64>, 149 pub color_correction: ColorCorrection, 150 pub frame_blending: bool, 151 pub composite_params: Option<CompositeParams>, 152 pub emulate_nes_ntsc_output: bool, 153 pub ntsc_per_frame_params: Option<NtscPerFrameParams>, 154} 155 156/// [`RenderFrameOptions`] excluding per-frame parameters 157#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] 158pub struct RenderFrameOptionsHashable { 159 pub pixel_aspect_ratio: Option<FiniteF64>, 160 pub color_correction: ColorCorrection, 161 pub frame_blending: bool, 162 pub composite_params: Option<CompositeParams>, 163 pub emulate_nes_ntsc_output: bool, 164} 165 166impl RenderFrameOptions { 167 #[must_use] 168 pub fn pixel_aspect_ratio(pixel_aspect_ratio: Option<FiniteF64>) -> Self { 169 Self { pixel_aspect_ratio, ..Self::default() } 170 } 171 172 #[must_use] 173 pub fn to_hashable(self) -> RenderFrameOptionsHashable { 174 RenderFrameOptionsHashable { 175 pixel_aspect_ratio: self.pixel_aspect_ratio, 176 color_correction: self.color_correction, 177 frame_blending: self.frame_blending, 178 composite_params: self.composite_params, 179 emulate_nes_ntsc_output: self.emulate_nes_ntsc_output, 180 } 181 } 182} 183 184pub trait Renderer { 185 type Err: Debug + Display + Send + Sync + 'static; 186 187 /// Render a frame. 188 /// 189 /// The frame buffer may be larger than the specified frame size, but the len must be at least 190 /// (`frame_width` * `frame_height`). Colors past the first (`frame_width` * `frame_height`) 191 /// will be ignored. 192 /// 193 /// `target_fps` must be a finite positive value. 194 /// 195 /// If pixel aspect ratio is None, the frame will be stretched to fill the window. If it is 196 /// Some, the frame will be rendered in the largest possible area that maintains the specified 197 /// pixel aspect ratio. 198 /// 199 /// # Errors 200 /// 201 /// This method will return an error if it is unable to render the frame. 202 fn render_frame( 203 &mut self, 204 frame_buffer: &[Color], 205 frame_size: FrameSize, 206 target_fps: f64, 207 options: RenderFrameOptions, 208 ) -> Result<(), Self::Err>; 209} 210 211pub trait AudioOutput { 212 type Err: Debug + Display + Send + Sync + 'static; 213 214 /// Push a stereo audio sample. 215 /// 216 /// # Errors 217 /// 218 /// This method will return an error if it is unable to push the sample to the audio device. 219 fn push_sample(&mut self, sample_l: f64, sample_r: f64) -> Result<(), Self::Err>; 220} 221 222pub trait SaveWriter { 223 type Err: Debug + Display + Send + Sync + 'static; 224 225 /// Read an array of bytes using the given extension. 226 /// 227 /// # Errors 228 /// 229 /// Will propagate any errors encountered while reading the file. 230 fn load_bytes(&mut self, extension: &str) -> Result<Vec<u8>, Self::Err>; 231 232 /// Write a slice of bytes using the given extension. 233 /// 234 /// # Errors 235 /// 236 /// Will propagate any errors encountered while writing the file. 237 fn persist_bytes(&mut self, extension: &str, bytes: &[u8]) -> Result<(), Self::Err>; 238 239 /// Load a serialized value using the given extension. 240 /// 241 /// For loading raw bytes, use `load_bytes` instead which does not assume that the length is serialized. 242 /// 243 /// # Errors 244 /// 245 /// Will propagate any errors encountered while reading the file or deserializing the data. 246 fn load_serialized<D: Decode<()>>(&mut self, extension: &str) -> Result<D, Self::Err>; 247 248 /// Write a serialized value using the given extension. 249 /// 250 /// For writing raw bytes, use `persist_bytes` instead which does not serialize the slice length. 251 /// 252 /// # Errors 253 /// 254 /// Will propagate any errors encountered while writing the file or serializing the data. 255 fn persist_serialized<E: Encode>(&mut self, extension: &str, data: E) -> Result<(), Self::Err>; 256} 257 258pub trait PartialClone { 259 /// Create a partial clone of `self`, which clones all emulation state but may not clone 260 /// read-only fields such as ROMs and frame buffers. 261 #[must_use] 262 fn partial_clone(&self) -> Self; 263} 264 265impl<T: PartialClone> PartialClone for Option<T> { 266 fn partial_clone(&self) -> Self { 267 self.as_ref().map(T::partial_clone) 268 } 269} 270 271pub use jgenesis_proc_macros::PartialClone; 272 273#[derive( 274 Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode, EnumDisplay, EnumFromStr, EnumAll, 275)] 276#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 277#[cfg_attr(feature = "clap", derive(jgenesis_proc_macros::CustomValueEnum))] 278pub enum TimingMode { 279 #[default] 280 Ntsc, 281 Pal, 282} 283 284#[derive(Debug, Clone, Copy, PartialEq, Eq)] 285pub enum TickEffect { 286 None, 287 FrameRendered, 288} 289 290pub type TickResult<Err> = Result<TickEffect, Err>; 291 292#[derive(Debug, Clone)] 293pub struct Modal { 294 pub id: Option<Cow<'static, str>>, 295 pub text: String, 296} 297 298pub trait MappableInputs<Button> { 299 fn set_field(&mut self, button: Button, player: Player, pressed: bool); 300 301 // Value is always non-negative (0-32767) 302 #[allow(unused_variables)] 303 fn set_analog(&mut self, button: Button, player: Player, value: i16) {} 304 305 #[allow(unused_variables)] 306 fn handle_mouse_motion( 307 &mut self, 308 position: (f32, f32), 309 delta: (f32, f32), 310 display_info: DisplayInfo, 311 ) { 312 } 313 314 fn handle_mouse_leave(&mut self) {} 315 316 // Should return true when emulating a peripheral that needs relative mouse mode, e.g. an 317 // emulated mouse 318 fn needs_relative_mouse_mode(&self) -> bool { 319 false 320 } 321 322 #[allow(unused_variables)] 323 fn modal_for_input(&self, button: Button, player: Player, pressed: bool) -> Option<Modal> { 324 None 325 } 326} 327 328pub trait InputPoller<Inputs> { 329 fn poll(&mut self) -> &Inputs; 330} 331 332pub struct ConstantInputPoller<'a, Inputs>(pub &'a Inputs); 333 334impl<Inputs> InputPoller<Inputs> for ConstantInputPoller<'_, Inputs> { 335 fn poll(&mut self) -> &Inputs { 336 self.0 337 } 338} 339 340pub trait EmulatorConfigTrait: Clone + Send + Sync + 'static { 341 #[must_use] 342 fn with_overclocking_disabled(&self) -> Self { 343 self.clone() 344 } 345} 346 347pub trait EmulatorTrait: 'static { 348 type Button: Debug + Copy + Eq + Hash; 349 type Inputs: Clone + Eq + Default + MappableInputs<Self::Button> + Send + Sync + 'static; 350 type Config: EmulatorConfigTrait; 351 type SaveState: Encode + Decode<()> + Send + Sync + 'static; 352 353 type Err<RErr: Debug + Display + Send + Sync + 'static, AErr: Debug + Display + Send + Sync + 'static, SErr: Debug + Display + Send + Sync + 'static>: Error + Send + Sync + 'static; 354 355 /// Tick the emulator for a small amount of time, e.g. a single CPU instruction. 356 /// 357 /// # Errors 358 /// 359 /// This method should propagate any errors encountered while rendering frames, pushing audio 360 /// samples, or persisting save files. 361 #[allow(clippy::type_complexity)] 362 fn tick<R, A, I, S>( 363 &mut self, 364 renderer: &mut R, 365 audio_output: &mut A, 366 input_poller: &mut I, 367 save_writer: &mut S, 368 ) -> TickResult<Self::Err<R::Err, A::Err, S::Err>> 369 where 370 R: Renderer, 371 A: AudioOutput, 372 I: InputPoller<Self::Inputs>, 373 S: SaveWriter; 374 375 /// Forcibly render the current frame buffer. 376 /// 377 /// # Errors 378 /// 379 /// This method can propagate any error returned by the renderer. 380 fn force_render<R>(&mut self, renderer: &mut R) -> Result<(), R::Err> 381 where 382 R: Renderer; 383 384 fn reload_config(&mut self, config: &Self::Config); 385 386 fn soft_reset(&mut self); 387 388 fn hard_reset<S: SaveWriter>(&mut self, save_writer: &mut S); 389 390 fn load_state(&mut self, state: Self::SaveState); 391 392 fn to_save_state(&self) -> Self::SaveState; 393 394 #[must_use] 395 fn save_state_version() -> &'static str { 396 "0.14.0-1" 397 } 398 399 fn target_fps(&self) -> f64; 400 401 fn update_audio_output_frequency(&mut self, output_frequency: u64); 402 403 fn startup_modals(&self) -> Vec<Modal> { 404 vec![] 405 } 406}