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.

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}
211pub trait AudioOutput {
212    type Err: Debug + Display + Send + Sync + 'static;

Push a stereo audio sample.

Errors

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}
222pub trait SaveWriter {
223    type Err: Debug + Display + Send + Sync + 'static;

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.

255    fn persist_serialized<E: Encode>(&mut self, extension: &str, data: E) -> Result<(), Self::Err>;
256}
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.

261    #[must_use]
262    fn partial_clone(&self) -> Self;
263}
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.

380    fn force_render<R>(&mut self, renderer: &mut R) -> Result<(), R::Err>
381    where
382        R: 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}