api.rsannotatedapi.rssource496 lines · 16.5 KB · raw

SNES public interface and main loop

3use crate::apu::{Apu, ApuTickEffect};
4use crate::audio::AudioResampler;
5use crate::bus::Bus;
6use crate::input::SnesInputs;
7use crate::memory::dma::{DmaStatus, DmaUnit};
8use crate::memory::{CpuInternalRegisters, Memory};
9use crate::ppu::{Ppu, PpuTickEffect};
10use crate::{bus, ppu};
11use bincode::error::EncodeError;
12use bincode::{Decode, Encode};
13use crc::Crc;
14use jgenesis_common::frontend::{
15    AudioOutput, Color, EmulatorConfigTrait, EmulatorTrait, InputPoller, NtscPerFrameParams,
16    PartialClone, RenderFrameOptions, Renderer, SaveWriter, TickEffect, TimingMode,
17};
18use jgenesis_proc_macros::{ConfigDisplay, FakeDecode, FakeEncode};
19use snes_config::{AudioInterpolationMode, SnesAspectRatio, SnesButton};
20use snes_coprocessors::st018::St018LoadError;
21use std::fmt::{Debug, Display};
22use std::num::NonZeroU64;
23use std::{io, mem};
24use thiserror::Error;
25use wdc65816_emu::core::Wdc65816;
26use wdc65816_emu::traits::BusInterface;

Length of work RAM: $7E0000-$7FFFFF.

29pub const WRAM_LEN: usize = crate::memory::MAIN_RAM_LEN;
31const MEMORY_REFRESH_MCLK: u64 = 536;
32const MEMORY_REFRESH_CYCLES: u64 = 40;
33
34const CRC: Crc<u32> = Crc::<u32>::new(&crc::CRC_32_ISO_HDLC);
35
36#[derive(Debug, Clone, Copy, Encode, Decode, ConfigDisplay)]
37pub struct SnesEmulatorConfig {
38    pub forced_timing_mode: Option<TimingMode>,
39    pub aspect_ratio: SnesAspectRatio,
40    pub deinterlace: bool,
41    pub audio_interpolation: AudioInterpolationMode,
42    pub audio_60hz_hack: bool,
43    pub gsu_overclock_factor: NonZeroU64,
44    pub allow_opposing_joypad_directions: bool,
45    pub mouse_sensitivity: f64,
46}
47
48impl EmulatorConfigTrait for SnesEmulatorConfig {
49    fn with_overclocking_disabled(&self) -> Self {
50        Self { gsu_overclock_factor: NonZeroU64::new(1).unwrap(), ..*self }
51    }
52}
53
54impl Default for SnesEmulatorConfig {
55    fn default() -> Self {
56        Self {
57            forced_timing_mode: None,
58            aspect_ratio: SnesAspectRatio::default(),
59            deinterlace: true,
60            audio_interpolation: AudioInterpolationMode::default(),
61            audio_60hz_hack: false,
62            gsu_overclock_factor: NonZeroU64::new(1).unwrap(),
63            allow_opposing_joypad_directions: false,
64            mouse_sensitivity: 1.0,
65        }
66    }
67}
68
69pub type CoprocessorRomFn =
70    dyn Fn() -> Result<Vec<u8>, (io::Error, String)> + Send + Sync + 'static;
71
72#[derive(Default, FakeEncode, FakeDecode)]
73pub struct CoprocessorRoms {
74    pub dsp1: Option<Box<CoprocessorRomFn>>,
75    pub dsp2: Option<Box<CoprocessorRomFn>>,
76    pub dsp3: Option<Box<CoprocessorRomFn>>,
77    pub dsp4: Option<Box<CoprocessorRomFn>>,
78    pub st010: Option<Box<CoprocessorRomFn>>,
79    pub st011: Option<Box<CoprocessorRomFn>>,
80    pub st018: Option<Box<CoprocessorRomFn>>,
81}
82
83impl CoprocessorRoms {
84    #[must_use]
85    pub fn none() -> Self {
86        Self::default()
87    }
88}
89
90#[derive(Debug, Error)]
91pub enum SnesError<RErr, AErr, SErr> {
92    #[error("Error rendering frame: {0}")]
93    Render(RErr),
94    #[error("Error outputting audio samples: {0}")]
95    AudioOutput(AErr),
96    #[error("Error persisting save file: {0}")]
97    SaveWrite(SErr),
98    #[error("Error encoding save file bytes: {0}")]
99    SaveEncode(#[from] EncodeError),
100}
101
102#[derive(Debug, Error)]
103pub enum SnesLoadError {
104    #[error("ROM is only {size} bytes; all SNES ROM chips should be at least 32768 bytes")]
105    RomTooSmall { size: usize },
106    #[error("Cannot load DSP-1 cartridge because DSP-1 ROM is not configured")]
107    MissingDsp1Rom,
108    #[error("Cannot load DSP-2 cartridge because DSP-2 ROM is not configured")]
109    MissingDsp2Rom,
110    #[error("Cannot load DSP-3 cartridge because DSP-3 ROM is not configured")]
111    MissingDsp3Rom,
112    #[error("Cannot load DSP-4 cartridge because DSP-4 ROM is not configured")]
113    MissingDsp4Rom,
114    #[error("Cannot load ST010 cartridge because ST010 ROM is not configured")]
115    MissingSt010Rom,
116    #[error("Cannot load ST011 cartridge because ST011 ROM is not configured")]
117    MissingSt011Rom,
118    #[error("Cannot load ST018 cartridge because ST018 ROM is not configured")]
119    MissingSt018Rom,
120    #[error("Failed to load required coprocessor ROM from '{path}': {source}")]
121    CoprocessorRomLoad {
122        #[source]
123        source: io::Error,
124        path: String,
125    },
126    #[error("Invalid ST018 coprocessor ROM: {0}")]
127    St018RomLoad(St018LoadError),
128}
129
130pub type SnesLoadResult<T> = Result<T, SnesLoadError>;
131
132macro_rules! new_bus {
133    ($self:expr) => {
134        Bus {
135            memory: &mut $self.memory,
136            cpu_registers: &mut $self.cpu_registers,
137            ppu: &mut $self.ppu,
138            apu: &mut $self.apu,
139            latched_interrupts: $self.latched_interrupts,
140            access_master_cycles: 0,
141            pending_write: None,
142        }
143    };
144}
145
146#[derive(Debug, Clone, Copy, Encode, Decode)]
147pub(crate) struct LatchedInterrupts {
148    pub nmi: bool,
149    pub irq: bool,
150}
151
152#[derive(Encode, Decode, PartialClone)]
153pub struct SnesEmulator {
154    main_cpu: Wdc65816,
155    cpu_registers: CpuInternalRegisters,
156    dma_unit: DmaUnit,
157    #[partial_clone(partial)]
158    memory: Memory,
159    ppu: Ppu,
160    apu: Apu,
161    audio_resampler: AudioResampler,
162    total_master_cycles: u64,
163    latched_interrupts: Option<LatchedInterrupts>,
164    memory_refresh_pending: bool,
165    timing_mode: TimingMode,
166    aspect_ratio: SnesAspectRatio,
167    frame_count: u64,
168    last_sram_checksum: u32,
169    // Following fields only stored here to enable hard reset
170    #[partial_clone(default)]
171    coprocessor_roms: CoprocessorRoms,
172    emulator_config: SnesEmulatorConfig,
173}
174
175impl SnesEmulator {

Errors

This function will return an error if it is unable to load the cartridge ROM for any reason.

179    pub fn create<S: SaveWriter>(
180        rom: Vec<u8>,
181        config: SnesEmulatorConfig,
182        coprocessor_roms: CoprocessorRoms,
183        save_writer: &mut S,
184    ) -> SnesLoadResult<Self> {
185        let main_cpu = Wdc65816::new();
186        let cpu_registers = CpuInternalRegisters::new(&config);
187        let dma_unit = DmaUnit::new();
188
189        let initial_sram = save_writer.load_bytes("sav").ok();
190        let sram_checksum = initial_sram.as_ref().map_or(0, |sram| CRC.checksum(sram));
191        let memory = Memory::create(
192            rom,
193            initial_sram,
194            &coprocessor_roms,
195            config.forced_timing_mode,
196            config.gsu_overclock_factor,
197            save_writer,
198        )?;
199
200        let timing_mode =
201            config.forced_timing_mode.unwrap_or_else(|| memory.cartridge_timing_mode());
202        let ppu = Ppu::new(timing_mode, config);
203        let apu = Apu::new(timing_mode, config);
204
205        log::info!("Running with timing/display mode {timing_mode}");
206
207        let mut emulator = Self {
208            main_cpu,
209            cpu_registers,
210            dma_unit,
211            memory,
212            ppu,
213            apu,
214            audio_resampler: AudioResampler::new(),
215            total_master_cycles: 0,
216            latched_interrupts: None,
217            memory_refresh_pending: false,
218            timing_mode,
219            aspect_ratio: config.aspect_ratio,
220            frame_count: 0,
221            last_sram_checksum: sram_checksum,
222            coprocessor_roms,
223            emulator_config: config,
224        };
225
226        // Reset CPU so that execution starts from the right place
227        emulator.main_cpu.reset(&mut new_bus!(emulator));
228
229        Ok(emulator)
230    }
232    #[must_use]
233    pub fn cartridge_title(&mut self) -> String {
234        self.memory.cartridge_title()
235    }
236
237    #[inline]
238    #[must_use]
239    pub fn has_sram(&self) -> bool {
240        self.memory.has_battery_backed_sram()
241    }

The whole of work RAM, indexed by offset from $7E0000. A host reads game state from here between frames.

245    #[inline]
246    #[must_use]
247    pub fn wram(&self) -> &[u8; WRAM_LEN] {
248        self.memory.wram()
249    }

Work RAM, writable, indexed like [Self::wram]. A host that plays the game from RAM (and cheats in it, as a bot linked into an emulator does) writes here between frames.

253    #[inline]
254    #[must_use]
255    pub fn wram_mut(&mut self) -> &mut [u8; WRAM_LEN] {
256        self.memory.wram_mut()
257    }
259    pub fn copy_cgram(&self, out: &mut [Color]) {
260        self.ppu.copy_cgram(out);
261    }
262
263    pub fn copy_vram_2bpp(&self, out: &mut [Color], palette: u8, row_len: usize) {
264        self.ppu.copy_vram_2bpp(out, palette, row_len);
265    }
266
267    pub fn copy_vram_4bpp(&self, out: &mut [Color], palette: u8, row_len: usize) {
268        self.ppu.copy_vram_4bpp(out, palette, row_len);
269    }
270
271    pub fn copy_vram_8bpp(&self, out: &mut [Color], row_len: usize) {
272        self.ppu.copy_vram_8bpp(out, row_len);
273    }
274
275    pub fn copy_vram_mode7(&self, out: &mut [Color], row_len: usize) {
276        self.ppu.copy_vram_mode7(out, row_len);
277    }
278
279    fn render_options(&self) -> RenderFrameOptions {
280        let frame_size = self.ppu.frame_size();
281        let aspect_ratio = self.aspect_ratio.to_pixel_aspect_ratio(frame_size);
282
283        RenderFrameOptions {
284            pixel_aspect_ratio: aspect_ratio,
285            composite_params: Some(self.ppu.composite_params()),
286            ntsc_per_frame_params: Some(NtscPerFrameParams {
287                frame_phase_offset: 2 * self.ppu.frame_start_cycles(),
288                per_line_phase_offset: 2 * ppu::MCLKS_PER_NORMAL_SCANLINE,
289            }),
290            ..RenderFrameOptions::default()
291        }
292    }
293}
294
295impl EmulatorTrait for SnesEmulator {
296    type Button = SnesButton;
297    type Inputs = SnesInputs;
298    type Config = SnesEmulatorConfig;
299    type SaveState = Self;
300
301    type Err<
302        RErr: Debug + Display + Send + Sync + 'static,
303        AErr: Debug + Display + Send + Sync + 'static,
304        SErr: Debug + Display + Send + Sync + 'static,
305    > = SnesError<RErr, AErr, SErr>;
306
307    fn tick<R, A, I, S>(
308        &mut self,
309        renderer: &mut R,
310        audio_output: &mut A,
311        input_poller: &mut I,
312        save_writer: &mut S,
313    ) -> Result<TickEffect, Self::Err<R::Err, A::Err, S::Err>>
314    where
315        R: Renderer,
316        A: AudioOutput,
317        I: InputPoller<Self::Inputs>,
318        S: SaveWriter,
319    {
320        let (master_cycles_elapsed, pending_write) = if self.memory_refresh_pending {
321            // The CPU (including DMA) halts for 40 cycles partway through every scanline so that
322            // the system can refresh DRAM (used for work RAM)
323            self.memory_refresh_pending = false;
324            (MEMORY_REFRESH_CYCLES, None)
325        } else {
326            let mut bus = new_bus!(self);
327
328            match self.dma_unit.tick(
329                &mut bus,
330                self.total_master_cycles,
331                bus::next_cpu_cycle_mclk(&self.main_cpu),
332            ) {
333                DmaStatus::None => {
334                    // DMA not in progress, tick CPU
335                    self.main_cpu.tick(&mut bus);
336                    self.latched_interrupts = None;
337
338                    bus.cpu_registers.tick_cpu_cycle();
339
340                    (bus.access_master_cycles, bus.pending_write)
341                }
342                DmaStatus::InProgress { master_cycles_elapsed } => {
343                    // Latch interrupt lines at the start of DMA to emulate interrupt tests being
344                    // delayed by one cycle after the DMA ends.
345                    // Wild Guns depends on this
346                    if self.latched_interrupts.is_none() {
347                        self.latched_interrupts =
348                            Some(LatchedInterrupts { nmi: bus.nmi(), irq: bus.irq() });
349                    }
350
351                    (master_cycles_elapsed, None)
352                }
353            }
354        };
355        debug_assert!(master_cycles_elapsed > 0);
356
357        // Copy WRIO from CPU to PPU for possible H/V counter latching
358        self.ppu.update_wrio(self.cpu_registers.wrio_register());
359
360        // Possibly latch H/V counter from the controller (e.g. Super Scope)
361        if let Some((h, v)) = self.cpu_registers.controller_hv_latch() {
362            self.ppu.update_controller_hv_latch(h, v, master_cycles_elapsed);
363        }
364
365        if let ApuTickEffect::OutputSample(sample_l, sample_r) =
366            self.apu.tick(master_cycles_elapsed)
367        {
368            self.audio_resampler.collect_sample(sample_l, sample_r);
369        }
370
371        self.audio_resampler.output_samples(audio_output).map_err(SnesError::AudioOutput)?;
372
373        self.memory.tick(master_cycles_elapsed);
374
375        let prev_scanline_mclk = self.ppu.scanline_master_cycles();
376        let mut tick_effect = TickEffect::None;
377        if self.ppu.tick(master_cycles_elapsed) == PpuTickEffect::FrameComplete {
378            renderer
379                .render_frame(
380                    self.ppu.frame_buffer(),
381                    self.ppu.frame_size(),
382                    self.target_fps(),
383                    self.render_options(),
384                )
385                .map_err(SnesError::Render)?;
386
387            // Only persist SRAM if it's changed since the last write, and only check ~twice per
388            // second because of the checksum calculation
389            if self.memory.has_battery_backed_sram()
390                && let Some(sram) = self.memory.sram()
391                && self.frame_count.is_multiple_of(30)
392            {
393                let checksum = CRC.checksum(sram);
394                if checksum != self.last_sram_checksum {
395                    save_writer.persist_bytes("sav", sram).map_err(SnesError::SaveWrite)?;
396                    self.memory
397                        .write_auxiliary_save_files(save_writer)
398                        .map_err(SnesError::SaveWrite)?;
399
400                    self.last_sram_checksum = checksum;
401                }
402            }
403
404            self.frame_count += 1;
405            tick_effect = TickEffect::FrameRendered;
406        }
407
408        let inputs = input_poller.poll();
409        self.cpu_registers.tick(master_cycles_elapsed, &self.ppu, inputs);
410
411        // CPU reads are applied before advancing other components but CPU writes are applied after.
412        // This fixes freezing in Rendering Ranger R2
413        // TODO this is slightly inaccurate - reads should happen 4 mclks before writes, not a full CPU cycle
414        if let Some((address, value)) = pending_write {
415            new_bus!(self).apply_write(address, value);
416        }
417
418        self.total_master_cycles += master_cycles_elapsed;
419        if prev_scanline_mclk < MEMORY_REFRESH_MCLK
420            && self.ppu.scanline_master_cycles() >= MEMORY_REFRESH_MCLK
421        {
422            self.memory_refresh_pending = true;
423        }
424
425        Ok(tick_effect)
426    }
427
428    fn force_render<R>(&mut self, renderer: &mut R) -> Result<(), R::Err>
429    where
430        R: Renderer,
431    {
432        let frame_size = self.ppu.frame_size();
433        renderer.render_frame(
434            self.ppu.frame_buffer(),
435            frame_size,
436            self.target_fps(),
437            self.render_options(),
438        )
439    }
440
441    fn reload_config(&mut self, config: &Self::Config) {
442        self.aspect_ratio = config.aspect_ratio;
443        self.ppu.update_config(*config);
444        self.apu.update_config(*config);
445        self.memory.update_gsu_overclock_factor(config.gsu_overclock_factor);
446        self.cpu_registers.reload_config(config);
447
448        self.emulator_config = *config;
449    }
450
451    fn soft_reset(&mut self) {
452        log::info!("Soft resetting");
453
454        // Reset memory before CPU because some coprocessors (Super FX) block access to the
455        // RESET interrupt vector while the coprocessor is running
456        self.memory.reset();
457
458        self.main_cpu.reset(&mut new_bus!(self));
459        self.cpu_registers.reset();
460        self.ppu.reset();
461        self.apu.reset();
462    }
463
464    fn hard_reset<S: SaveWriter>(&mut self, save_writer: &mut S) {
465        log::info!("Hard resetting");
466
467        let rom = self.memory.take_rom();
468
469        let coprocessor_roms = mem::take(&mut self.coprocessor_roms);
470        *self = Self::create(rom, self.emulator_config, coprocessor_roms, save_writer)
471            .expect("Hard resetting should never fail to load");
472    }
473
474    fn load_state(&mut self, mut state: Self::SaveState) {
475        state.memory.take_rom_from(&mut self.memory);
476        state.coprocessor_roms = mem::take(&mut self.coprocessor_roms);
477        *self = state;
478    }
479
480    fn to_save_state(&self) -> Self::SaveState {
481        self.partial_clone()
482    }
483
484    fn target_fps(&self) -> f64 {
485        match (self.timing_mode, self.emulator_config.audio_60hz_hack) {
486            (TimingMode::Ntsc, true) => 60.0,
487            (TimingMode::Ntsc, false) => 60.0988,
488            (TimingMode::Pal, true) => 50.0,
489            (TimingMode::Pal, false) => 50.007,
490        }
491    }
492
493    fn update_audio_output_frequency(&mut self, output_frequency: u64) {
494        self.audio_resampler.update_output_frequency(output_frequency);
495    }
496}