api.rsannotatedapi.rssource490 lines · 16.1 KB · raw
1//! Sega Master System / Game Gear public interface and main loop
2
3use crate::audio::{AudioResampler, TimingModeExt};
4use crate::bus::Bus;
5use crate::input::InputState;
6use crate::memory::Memory;
7use crate::vdp::{Vdp, VdpBuffer, VdpTickEffect, ViewportSize};
8use crate::{VdpVersion, vdp};
9use bincode::{Decode, Encode};
10use jgenesis_common::boxedarray::BoxedColorArray;
11use jgenesis_common::cheats::ByteCheatCodeU16Address;
12use jgenesis_common::frontend::{
13    AudioOutput, Color, CompositeParams, EmulatorConfigTrait, EmulatorTrait, FrameSize,
14    InputPoller, PartialClone, RenderFrameOptions, Renderer, SamplesPerColorCycle, SaveWriter,
15    TickEffect, TimingMode,
16};
17use jgenesis_proc_macros::ConfigDisplay;
18use smsgg_config::{
19    GgAspectRatio, SmsAspectRatio, SmsGgButton, SmsGgInputs, SmsGgRegion, SmsModel,
20};
21use std::fmt::{Debug, Display};
22use std::num::NonZeroU32;
23use thiserror::Error;
24use ti_sn76489::{Sn76489, Sn76489TickEffect, Sn76489Version};
25use ym_opll::Ym2413;
26use z80_emu::{InterruptMode, Z80};
27
28#[derive(Debug, Error)]
29pub enum SmsGgError<RErr, AErr, SErr> {
30    #[error("Rendering error: {0}")]
31    Render(RErr),
32    #[error("Audio output error: {0}")]
33    Audio(AErr),
34    #[error("Save write error: {0}")]
35    SaveWrite(SErr),
36}
37
38pub type SmsGgResult<RErr, AErr, SErr> = Result<TickEffect, SmsGgError<RErr, AErr, SErr>>;
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
41pub enum SmsGgHardware {
42    MasterSystem,
43    GameGear,
44    Sg1000,
45}
46
47#[derive(Debug, Clone, Encode, Decode, ConfigDisplay)]
48pub struct SmsGgEmulatorConfig {
49    pub sms_timing_mode: TimingMode,
50    pub sms_model: SmsModel,
51    pub forced_psg_version: Option<Sn76489Version>,
52    pub sms_aspect_ratio: SmsAspectRatio,
53    pub gg_aspect_ratio: GgAspectRatio,
54    pub remove_sprite_limit: bool,
55    pub forced_region: Option<SmsGgRegion>,
56    pub sms_crop_vertical_border: bool,
57    pub sms_crop_left_border: bool,
58    pub gg_frame_blending: bool,
59    pub gg_use_sms_resolution: bool,
60    pub fm_sound_unit_enabled: bool,
61    pub z80_divider: NonZeroU32,
62    pub allow_opposing_joypad_directions: bool,
63    #[cfg_display(skip)]
64    pub cheat_codes: Vec<ByteCheatCodeU16Address>,
65}
66
67impl EmulatorConfigTrait for SmsGgEmulatorConfig {
68    fn with_overclocking_disabled(&self) -> Self {
69        Self { z80_divider: NonZeroU32::new(crate::NATIVE_Z80_DIVIDER).unwrap(), ..self.clone() }
70    }
71}
72
73impl SmsGgEmulatorConfig {
74    pub(crate) fn region(&self, memory: &Memory) -> SmsGgRegion {
75        self.forced_region.unwrap_or_else(|| memory.guess_cartridge_region())
76    }
77
78    pub(crate) fn render_options(&self, hardware: SmsGgHardware) -> RenderFrameOptions {
79        match hardware {
80            SmsGgHardware::MasterSystem | SmsGgHardware::Sg1000 => self.sms_render_options(),
81            SmsGgHardware::GameGear => self.gg_render_options(),
82        }
83    }
84
85    pub(crate) fn sms_render_options(&self) -> RenderFrameOptions {
86        RenderFrameOptions {
87            pixel_aspect_ratio: self.sms_aspect_ratio.to_pixel_aspect_ratio(),
88            composite_params: Some(CompositeParams {
89                upscale_factor: 10,
90                samples_per_color_cycle: SamplesPerColorCycle::Fifteen,
91            }),
92            ..RenderFrameOptions::default()
93        }
94    }
95
96    pub(crate) fn gg_render_options(&self) -> RenderFrameOptions {
97        RenderFrameOptions {
98            pixel_aspect_ratio: self.gg_aspect_ratio.to_pixel_aspect_ratio(),
99            frame_blending: self.gg_frame_blending,
100            ..RenderFrameOptions::default()
101        }
102    }
103}
104
105#[derive(Debug, Clone, Encode, Decode, PartialClone)]
106pub struct SmsGgEmulator {
107    #[partial_clone(partial)]
108    memory: Memory,
109    z80: Z80,
110    vdp: Vdp,
111    vdp_version: VdpVersion,
112    psg: Sn76489,
113    ym2413: Option<Ym2413>,
114    input: InputState,
115    audio_resampler: AudioResampler,
116    frame_buffer: BoxedColorArray<{ vdp::FRAME_BUFFER_LEN }>,
117    config: SmsGgEmulatorConfig,
118    vdp_mclk_counter: u32,
119    psg_mclk_counter: u32,
120    frame_count: u64,
121    reset_frames_remaining: u32,
122}
123
124const VDP_DIVIDER: u32 = 10;
125const PSG_DIVIDER: u32 = 15;
126
127const YM2413_CLOCK_INTERVAL: u8 = 72;
128
129impl SmsGgEmulator {
130    #[must_use]
131    pub fn create<S: SaveWriter>(
132        rom: Option<Vec<u8>>,
133        bios_rom: Option<Vec<u8>>,
134        hardware: SmsGgHardware,
135        config: SmsGgEmulatorConfig,
136        save_writer: &mut S,
137    ) -> Self {
138        let cartridge_ram = save_writer.load_bytes("sav").ok();
139
140        let vdp_version = determine_vdp_version(hardware, &config);
141        let psg_version = determine_psg_version(hardware, &config);
142
143        log::info!("VDP version: {vdp_version:?}");
144        log::info!("PSG version: {psg_version:?}");
145
146        let rom = match rom {
147            Some(rom) if !rom.is_empty() => rom,
148            _ => vec![0xFF; 0x8000],
149        };
150        let memory = Memory::new(rom, bios_rom, cartridge_ram, hardware, &config);
151        let vdp = Vdp::new(vdp_version, &config);
152        let psg = Sn76489::new(psg_version);
153        let input = InputState::new(config.region(&memory), &config);
154
155        log::info!("Region in cartridge header: {:?}", memory.guess_cartridge_region());
156
157        let mut z80 = Z80::new();
158        init_z80(&mut z80);
159
160        let ym2413 =
161            config.fm_sound_unit_enabled.then(|| ym_opll::new_ym2413(YM2413_CLOCK_INTERVAL));
162
163        let timing_mode = vdp.timing_mode();
164        Self {
165            memory,
166            z80,
167            vdp,
168            vdp_version,
169            psg,
170            ym2413,
171            input,
172            audio_resampler: AudioResampler::new(timing_mode),
173            frame_buffer: BoxedColorArray::new(),
174            config,
175            vdp_mclk_counter: 0,
176            psg_mclk_counter: 0,
177            frame_count: 0,
178            reset_frames_remaining: 0,
179        }
180    }
181
182    #[must_use]
183    pub fn hardware(&self) -> SmsGgHardware {
184        self.vdp_version.hardware()
185    }
186
187    #[must_use]
188    pub fn vdp_version(&self) -> VdpVersion {
189        self.vdp_version
190    }
191
192    #[inline]
193    #[must_use]
194    pub fn has_sram(&self) -> bool {
195        self.memory.cartridge_has_battery()
196    }
197
198    fn render_frame<R: Renderer>(&mut self, renderer: &mut R) -> Result<(), R::Err> {
199        populate_frame_buffer(
200            self.vdp.frame_buffer(),
201            self.vdp.viewport(),
202            self.vdp_version,
203            self.config.sms_crop_vertical_border,
204            self.config.sms_crop_left_border,
205            self.frame_buffer.as_mut_slice(),
206        );
207
208        let viewport = self.vdp.viewport();
209        let frame_width = if self.config.sms_crop_left_border {
210            viewport.width_without_border().into()
211        } else {
212            viewport.width.into()
213        };
214        let frame_height = if self.config.sms_crop_vertical_border {
215            viewport.height_without_border().into()
216        } else {
217            viewport.height.into()
218        };
219
220        let frame_size = FrameSize { width: frame_width, height: frame_height };
221        renderer.render_frame(
222            self.frame_buffer.as_slice(),
223            frame_size,
224            self.target_fps(),
225            self.config.render_options(self.vdp_version.hardware()),
226        )
227    }
228
229    pub fn copy_cram(&self, out: &mut [Color]) {
230        self.vdp.copy_cram(out);
231    }
232
233    pub fn copy_vram(&self, out: &mut [Color], palette: u8, row_len: usize) {
234        self.vdp.copy_vram(out, palette, row_len);
235    }
236
237    pub fn dump_vdp_registers(&self, callback: impl FnMut(u32, &[(&str, &str)])) {
238        self.vdp.dump_registers(callback);
239    }
240}
241
242fn init_z80(z80: &mut Z80) {
243    z80.set_pc(0x0000);
244    z80.set_sp(0xDFFF);
245    z80.set_interrupt_mode(InterruptMode::Mode1);
246}
247
248fn determine_vdp_version(hardware: SmsGgHardware, config: &SmsGgEmulatorConfig) -> VdpVersion {
249    match (hardware, config.sms_timing_mode, config.sms_model) {
250        (SmsGgHardware::MasterSystem, TimingMode::Ntsc, SmsModel::Sms1) => {
251            VdpVersion::NtscMasterSystem1
252        }
253        (SmsGgHardware::MasterSystem, TimingMode::Ntsc, SmsModel::Sms2) => {
254            VdpVersion::NtscMasterSystem2
255        }
256        (SmsGgHardware::MasterSystem, TimingMode::Pal, SmsModel::Sms1) => {
257            VdpVersion::PalMasterSystem1
258        }
259        (SmsGgHardware::MasterSystem, TimingMode::Pal, SmsModel::Sms2) => {
260            VdpVersion::PalMasterSystem2
261        }
262        (SmsGgHardware::GameGear, _, _) => VdpVersion::GameGear,
263        (SmsGgHardware::Sg1000, TimingMode::Ntsc, _) => VdpVersion::NtscSg1000,
264        (SmsGgHardware::Sg1000, TimingMode::Pal, _) => VdpVersion::PalSg1000,
265    }
266}
267
268fn determine_psg_version(hardware: SmsGgHardware, config: &SmsGgEmulatorConfig) -> Sn76489Version {
269    config.forced_psg_version.unwrap_or(match hardware {
270        SmsGgHardware::MasterSystem => Sn76489Version::MasterSystem2,
271        SmsGgHardware::GameGear => Sn76489Version::Standard,
272        SmsGgHardware::Sg1000 => Sn76489Version::Discrete,
273    })
274}
275
276impl EmulatorTrait for SmsGgEmulator {
277    type Button = SmsGgButton;
278    type Inputs = SmsGgInputs;
279    type Config = SmsGgEmulatorConfig;
280    type SaveState = Self;
281
282    type Err<
283        RErr: Debug + Display + Send + Sync + 'static,
284        AErr: Debug + Display + Send + Sync + 'static,
285        SErr: Debug + Display + Send + Sync + 'static,
286    > = SmsGgError<RErr, AErr, SErr>;
287
288    /// Execute a single CPU instruction and run the rest of the components for the corresponding
289    /// number of cycles.
290    ///
291    /// # Errors
292    ///
293    /// This method will propagate any errors encountered while rendering frames, pushing audio
294    /// samples, or persisting cartridge SRAM.
295    #[inline]
296    fn tick<R, A, I, S>(
297        &mut self,
298        renderer: &mut R,
299        audio_output: &mut A,
300        input_poller: &mut I,
301        save_writer: &mut S,
302    ) -> SmsGgResult<R::Err, A::Err, S::Err>
303    where
304        R: Renderer,
305        A: AudioOutput,
306        I: InputPoller<Self::Inputs>,
307        S: SaveWriter,
308    {
309        self.input.set_inputs(*input_poller.poll());
310
311        let z80_t_cycles = self.z80.execute_instruction(&mut Bus::new(
312            self.vdp_version,
313            &mut self.memory,
314            &mut self.vdp,
315            &mut self.psg,
316            self.ym2413.as_mut(),
317            &mut self.input,
318        ));
319
320        let mclk_cycles = z80_t_cycles * self.config.z80_divider.get();
321        self.vdp_mclk_counter += mclk_cycles;
322        self.psg_mclk_counter += mclk_cycles;
323
324        while self.psg_mclk_counter >= PSG_DIVIDER {
325            self.psg_mclk_counter -= PSG_DIVIDER;
326
327            if let Some(ym2413) = &mut self.ym2413 {
328                ym2413.tick();
329            }
330            if self.psg.tick() == Sn76489TickEffect::Clocked {
331                let (psg_sample_l, psg_sample_r) =
332                    if self.memory.psg_enabled() { self.psg.sample() } else { (0.0, 0.0) };
333                let ym_sample = if self.memory.fm_enabled() {
334                    self.ym2413.as_ref().map_or(0.0, Ym2413::sample)
335                } else {
336                    0.0
337                };
338
339                let sample_l = psg_sample_l + ym_sample;
340                let sample_r = psg_sample_r + ym_sample;
341                self.audio_resampler.collect_sample(sample_l, sample_r);
342            }
343        }
344
345        self.audio_resampler.output_samples(audio_output).map_err(SmsGgError::Audio)?;
346
347        let mut frame_rendered = false;
348        while self.vdp_mclk_counter >= VDP_DIVIDER {
349            self.vdp_mclk_counter -= VDP_DIVIDER;
350
351            if self.vdp.tick() == VdpTickEffect::FrameComplete {
352                self.render_frame(renderer).map_err(SmsGgError::Render)?;
353                frame_rendered = true;
354
355                self.input.set_reset(self.reset_frames_remaining != 0);
356                self.reset_frames_remaining = self.reset_frames_remaining.saturating_sub(1);
357
358                self.frame_count += 1;
359                if self.frame_count.is_multiple_of(60)
360                    && self.memory.cartridge_has_battery()
361                    && self.memory.cartridge_ram_dirty()
362                {
363                    self.memory.clear_cartridge_ram_dirty();
364                    save_writer
365                        .persist_bytes("sav", self.memory.cartridge_ram())
366                        .map_err(SmsGgError::SaveWrite)?;
367                }
368            }
369        }
370
371        Ok(if frame_rendered { TickEffect::FrameRendered } else { TickEffect::None })
372    }
373
374    fn force_render<R>(&mut self, renderer: &mut R) -> Result<(), R::Err>
375    where
376        R: Renderer,
377    {
378        self.render_frame(renderer)
379    }
380
381    fn reload_config(&mut self, config: &Self::Config) {
382        self.config = config.clone();
383
384        let hardware = self.hardware();
385        self.vdp_version = determine_vdp_version(hardware, config);
386        self.vdp.update_config(self.vdp_version, config);
387
388        self.psg.set_version(determine_psg_version(hardware, config));
389
390        self.memory.update_config(config);
391
392        self.input.reload_config(config.region(&self.memory), config);
393        self.audio_resampler.update_timing_mode(self.vdp.timing_mode());
394    }
395
396    fn soft_reset(&mut self) {
397        log::info!("Soft resetting console");
398
399        // The SMS RESET button only sets a bit in a register; emulate "soft reset" by keeping the
400        // button virtually held down for 5 frames
401        self.reset_frames_remaining = 5;
402    }
403
404    fn hard_reset<S: SaveWriter>(&mut self, _save_writer: &mut S) {
405        log::info!("Hard resetting console");
406
407        self.memory.reset(&self.config);
408
409        self.z80 = Z80::new();
410        init_z80(&mut self.z80);
411
412        self.vdp = Vdp::new(self.vdp_version, &self.config);
413        self.psg = Sn76489::new(self.psg.version());
414        self.input = InputState::new(self.input.region(), &self.config);
415
416        self.ym2413 =
417            self.config.fm_sound_unit_enabled.then(|| ym_opll::new_ym2413(YM2413_CLOCK_INTERVAL));
418
419        self.frame_buffer = BoxedColorArray::new();
420
421        self.vdp_mclk_counter = 0;
422        self.psg_mclk_counter = 0;
423        self.frame_count = 0;
424        self.reset_frames_remaining = 0;
425    }
426
427    fn load_state(&mut self, mut state: Self::SaveState) {
428        state.memory.take_rom_from(&mut self.memory);
429        *self = state;
430    }
431
432    fn to_save_state(&self) -> Self::SaveState {
433        self.partial_clone()
434    }
435
436    fn target_fps(&self) -> f64 {
437        let timing_mode = self.vdp.timing_mode();
438        let mclk_frequency = timing_mode.mclk_frequency();
439        let scanlines_per_frame = match timing_mode {
440            TimingMode::Ntsc => vdp::NTSC_SCANLINES_PER_FRAME,
441            TimingMode::Pal => vdp::PAL_SCANLINES_PER_FRAME,
442        };
443
444        mclk_frequency / f64::from(vdp::MCLK_CYCLES_PER_SCANLINE) / f64::from(scanlines_per_frame)
445    }
446
447    fn update_audio_output_frequency(&mut self, output_frequency: u64) {
448        self.audio_resampler.update_output_frequency(output_frequency);
449    }
450}
451
452fn populate_frame_buffer(
453    vdp_buffer: &VdpBuffer,
454    viewport: ViewportSize,
455    vdp_version: VdpVersion,
456    crop_vertical_border: bool,
457    crop_left_border: bool,
458    frame_buffer: &mut [Color],
459) {
460    let (row_skip, row_take) = if crop_vertical_border {
461        (viewport.top_border_height as usize, viewport.height_without_border() as usize)
462    } else {
463        (0, viewport.height as usize)
464    };
465    let (col_skip, screen_width) = if crop_left_border {
466        (viewport.left_border_width as usize, viewport.width_without_border() as usize)
467    } else {
468        (0, viewport.width as usize)
469    };
470
471    for (i, row) in vdp_buffer.iter().skip(row_skip).take(row_take).enumerate() {
472        for (j, color) in row.iter().copied().skip(col_skip).enumerate() {
473            let color = match vdp_version.hardware() {
474                SmsGgHardware::MasterSystem => Color::rgb(
475                    vdp::convert_sms_color(color & 0x03),
476                    vdp::convert_sms_color((color >> 2) & 0x03),
477                    vdp::convert_sms_color((color >> 4) & 0x03),
478                ),
479                SmsGgHardware::GameGear => Color::rgb(
480                    vdp::convert_gg_color(color & 0x0F),
481                    vdp::convert_gg_color((color >> 4) & 0x0F),
482                    vdp::convert_gg_color((color >> 8) & 0x0F),
483                ),
484                SmsGgHardware::Sg1000 => vdp::convert_sg_color(color),
485            };
486
487            frame_buffer[i * screen_width + j] = color;
488        }
489    }
490}