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}
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}
[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}
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;
Render a frame.
The frame buffer may be larger than the specified frame size, but the len must be at least
(frame_width * frame_height). Colors past the first (frame_width * frame_height)
will be ignored.
target_fps must be a finite positive value.
If pixel aspect ratio is None, the frame will be stretched to fill the window. If it is Some, the frame will be rendered in the largest possible area that maintains the specified pixel aspect ratio.
Errors
This method will return an error if it is unable to render the frame.
Push a stereo audio sample.
Errors
This method will return an error if it is unable to push the sample to the audio device.
Read an array of bytes using the given extension.
Errors
Will propagate any errors encountered while reading the file.
230 fn load_bytes(&mut self, extension: &str) -> Result<Vec<u8>, Self::Err>;
Write a slice of bytes using the given extension.
Errors
Will propagate any errors encountered while writing the file.
237 fn persist_bytes(&mut self, extension: &str, bytes: &[u8]) -> Result<(), Self::Err>;
Load a serialized value using the given extension.
For loading raw bytes, use load_bytes instead which does not assume that the length is serialized.
Errors
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>;
Write a serialized value using the given extension.
For writing raw bytes, use persist_bytes instead which does not serialize the slice length.
Errors
Will propagate any errors encountered while writing the file or serializing the data.
258pub trait PartialClone {
Create a partial clone of self, which clones all emulation state but may not clone
read-only fields such as ROMs and frame buffers.
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;
Tick the emulator for a small amount of time, e.g. a single CPU instruction.
Errors
This method should propagate any errors encountered while rendering frames, pushing audio 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;
Forcibly render the current frame buffer.
Errors
This method can propagate any error returned by the renderer.
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}