api.rsannotatedapi.rssource421 lines · 14.5 KB · raw
1use crate::apu::ApuState;
2use crate::audio::AudioResampler;
3use crate::bus::cartridge::CartridgeFileError;
4use crate::bus::{Bus, cartridge};
5use crate::cpu::CpuState;
6use crate::graphics::TimingModeGraphicsExt;
7use crate::input::NesInputs;
8use crate::ppu::PpuState;
9use crate::{apu, audio, cpu, graphics, ppu};
10use bincode::{Decode, Encode};
11use jgenesis_common::frontend::{
12    AudioOutput, Color, CompositeParams, EmulatorConfigTrait, EmulatorTrait, FrameSize,
13    InputPoller, NtscPerFrameParams, PartialClone, RenderFrameOptions, Renderer,
14    SamplesPerColorCycle, SaveWriter, TickEffect, TickResult, TimingMode,
15};
16use jgenesis_proc_macros::ConfigDisplay;
17use std::fmt::{Debug, Display};
18use std::mem;
19use thiserror::Error;
20
21pub use graphics::PatternTable;
22use jgenesis_common::boxedarray::BoxedColorArray;
23use mos6502_emu::bus::BusInterface;
24use nes_config::{NesAspectRatio, NesAudioResampler, NesButton, NesPalette, Overscan};

The number of master clock ticks to run in one Emulator::tick call

27const PAL_MASTER_CLOCK_TICKS: u32 = 80;
29const PAL_CPU_DIVIDER: u32 = 16;
30const PAL_PPU_DIVIDER: u32 = 5;
31
32const FRAME_BUFFER_LEN: usize = (ppu::SCREEN_WIDTH as usize) * (ppu::MAX_SCREEN_HEIGHT as usize);
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode, ConfigDisplay)]
35pub struct NesEmulatorConfig {

Force timing mode to NTSC/PAL if set If None, timing mode will default based on iNES ROM header

38    pub forced_timing_mode: Option<TimingMode>,

Aspect ratio

40    pub aspect_ratio: NesAspectRatio,

Palette

42    #[cfg_display(skip)]
43    pub palette: NesPalette,

Crop frame vertically from 240px to 224px in NTSC mode

45    pub ntsc_crop_vertical_overscan: bool,

Overscan in pixels

47    pub overscan: Overscan,

Whether to send the renderer 6-bit NES colors instead of RGB888 colors

49    pub emulate_ntsc_output: bool,

If true, do not emulate the 8 sprite per scanline limit; this eliminates sprite flickering but can cause bugs in some games

52    pub remove_sprite_limit: bool,

If true, add a black border over the top scanline, the leftmost 2 columns, and the rightmost 2 columns

54    pub pal_black_border: bool,

If true, silence the triangle wave channel when it is outputting a wave at ultrasonic frequency

56    pub silence_ultrasonic_triangle_output: bool,
57    pub audio_resampler: NesAudioResampler,

If true, adjust audio frequency so that audio sync times to 60Hz NTSC / 50Hz PAL

59    pub audio_refresh_rate_adjustment: bool,

Whether to allow simultaneous left+right and up+down joypad inputs. Some games exhibit severe glitches when opposing joypad directions are pressed simultaneously, e.g. Zelda 2 and Battletoads

63    pub allow_opposing_joypad_directions: bool,

Whether DMA is allowed to trigger dummy reads to the controller port registers. Dummy reads are more accurate to hardware but cause input glitches in some games.

66    pub dma_dummy_joy_reads: bool,
67}
69impl EmulatorConfigTrait for NesEmulatorConfig {}
70
71#[derive(Debug, Error)]
72pub enum NesError<RErr, AErr, SErr> {
73    #[error("Error rendering frame: {0}")]
74    Render(RErr),
75    #[error("Error outputting audio samples: {0}")]
76    Audio(AErr),
77    #[error("Error persisting save file: {0}")]
78    SaveWrite(SErr),
79}
80
81#[derive(Debug, Error)]
82pub enum NesInitializationError {
83    #[error("Error loading cartridge ROM: {0}")]
84    CartridgeLoad(#[from] CartridgeFileError),
85    #[error("ROM image was empty")]
86    EmptyRom,
87}
88
89#[derive(Debug, Clone, Encode, Decode, PartialClone)]
90pub struct NesEmulator {
91    #[partial_clone(partial)]
92    bus: Bus,
93    cpu_state: CpuState,
94    ppu_state: PpuState,
95    apu_state: ApuState,
96    config: NesEmulatorConfig,
97    rgba_frame_buffer: BoxedColorArray<FRAME_BUFFER_LEN>,
98    audio_resampler: AudioResampler,
99    // Kept around to enable hard reset
100    #[partial_clone(default)]
101    raw_rom_bytes: Vec<u8>,
102}
103
104impl NesEmulator {

Create a new emulator instance.

Errors

This function will return an error if it cannot successfully parse NES ROM data out of the given ROM bytes.

111    pub fn create<S: SaveWriter>(
112        rom_bytes: Vec<u8>,
113        config: NesEmulatorConfig,
114        save_writer: &mut S,
115    ) -> Result<Self, NesInitializationError> {
116        if rom_bytes.is_empty() {
117            return Err(NesInitializationError::EmptyRom);
118        }
119
120        let sav_bytes = save_writer.load_bytes("sav").ok();
121        let mapper = cartridge::from_ines_file(&rom_bytes, sav_bytes, config.forced_timing_mode)?;
122        let timing_mode = mapper.timing_mode();
123
124        let mut bus = Bus::from_cartridge(mapper, config.overscan);
125
126        let cpu_state = CpuState::new(&mut bus.cpu());
127        let ppu_state = PpuState::new(timing_mode, config.ntsc_crop_vertical_overscan);
128        let mut apu_state = ApuState::new(timing_mode);
129
130        init_apu(&mut apu_state, &mut bus, &config);
131
132        Ok(Self {
133            bus,
134            cpu_state,
135            ppu_state,
136            apu_state,
137            config,
138            rgba_frame_buffer: BoxedColorArray::new(),
139            audio_resampler: AudioResampler::new(timing_mode, &config),
140            raw_rom_bytes: rom_bytes,
141        })
142    }
144    #[inline]
145    #[must_use]
146    pub fn timing_mode(&self) -> TimingMode {
147        self.bus.mapper().timing_mode()
148    }
149
150    fn ntsc_tick(&mut self) {
151        cpu::tick(&mut self.cpu_state, &mut self.bus.cpu(), &mut self.apu_state, &self.config);
152        apu::tick(&mut self.apu_state, &mut self.bus.cpu(), &self.config);
153        ppu::tick(&mut self.ppu_state, &mut self.bus.ppu(), &self.config);
154        self.bus.tick_cpu(&self.apu_state);
155        self.bus.tick();
156
157        self.bus.poll_interrupt_lines();
158
159        ppu::tick(&mut self.ppu_state, &mut self.bus.ppu(), &self.config);
160        self.bus.tick();
161
162        ppu::tick(&mut self.ppu_state, &mut self.bus.ppu(), &self.config);
163        self.bus.tick();
164
165        self.push_audio_sample();
166    }
167
168    fn pal_tick(&mut self) {
169        // Both CPU and PPU tick on the first master clock cycle
170        cpu::tick(&mut self.cpu_state, &mut self.bus.cpu(), &mut self.apu_state, &self.config);
171        apu::tick(&mut self.apu_state, &mut self.bus.cpu(), &self.config);
172        ppu::tick(&mut self.ppu_state, &mut self.bus.ppu(), &self.config);
173        self.bus.tick_cpu(&self.apu_state);
174        self.bus.tick();
175
176        self.bus.poll_interrupt_lines();
177
178        self.push_audio_sample();
179
180        for i in 1..PAL_MASTER_CLOCK_TICKS {
181            if i % PAL_CPU_DIVIDER == 0 {
182                cpu::tick(
183                    &mut self.cpu_state,
184                    &mut self.bus.cpu(),
185                    &mut self.apu_state,
186                    &self.config,
187                );
188                apu::tick(&mut self.apu_state, &mut self.bus.cpu(), &self.config);
189                self.bus.tick_cpu(&self.apu_state);
190                self.bus.tick();
191
192                self.bus.poll_interrupt_lines();
193
194                self.push_audio_sample();
195            } else if i % PAL_PPU_DIVIDER == 0 {
196                ppu::tick(&mut self.ppu_state, &mut self.bus.ppu(), &self.config);
197                self.bus.tick();
198            }
199        }
200    }
201
202    fn render_frame<R: Renderer>(&mut self, renderer: &mut R) -> Result<(), R::Err> {
203        let overscan = self.config.overscan;
204        let display_mode = if !self.config.ntsc_crop_vertical_overscan {
205            TimingMode::Pal
206        } else {
207            self.bus.mapper().timing_mode()
208        };
209        graphics::ppu_frame_buffer_to_rgba(
210            self.ppu_state.frame_buffer(),
211            self.rgba_frame_buffer.as_mut_slice(),
212            overscan,
213            display_mode,
214            &self.config.palette,
215            self.config.emulate_ntsc_output,
216        );
217
218        let visible_screen_height = display_mode.visible_screen_height();
219        let frame_size = FrameSize {
220            width: ppu::SCREEN_WIDTH
221                .saturating_sub(overscan.left)
222                .saturating_sub(overscan.right)
223                .into(),
224            height: visible_screen_height
225                .saturating_sub(overscan.top)
226                .saturating_sub(overscan.bottom)
227                .into(),
228        };
229
230        if frame_size.width == 0 || frame_size.height == 0 {
231            log::error!("Overscan values are too large, entire frame was cropped: {overscan}");
232            return renderer.render_frame(
233                &[Color::BLACK],
234                FrameSize { width: 1, height: 1 },
235                self.target_fps(),
236                RenderFrameOptions::default(),
237            );
238        }
239
240        let pixel_aspect_ratio = self.config.aspect_ratio.to_pixel_aspect_ratio();
241
242        renderer.render_frame(
243            self.rgba_frame_buffer.as_slice(),
244            frame_size,
245            self.target_fps(),
246            RenderFrameOptions {
247                pixel_aspect_ratio,
248                composite_params: Some(CompositeParams {
249                    upscale_factor: 8,
250                    samples_per_color_cycle: SamplesPerColorCycle::Twelve,
251                }),
252                emulate_nes_ntsc_output: true,
253                ntsc_per_frame_params: Some(NtscPerFrameParams {
254                    frame_phase_offset: 8 * self.ppu_state.frame_start_cycles(),
255                    per_line_phase_offset: (8 * ppu::DOTS_PER_SCANLINE).into(),
256                }),
257                ..RenderFrameOptions::default()
258            },
259        )
260    }
261
262    fn push_audio_sample(&mut self) {
263        let audio_sample = {
264            let sample = self.apu_state.sample();
265            self.bus.mapper().sample_audio(sample)
266        };
267
268        self.audio_resampler.collect_sample(audio_sample);
269    }
270
271    pub fn copy_nametables(&mut self, pattern_table: PatternTable, out: &mut [Color]) {
272        graphics::copy_nametables(pattern_table, &mut self.bus.ppu(), out, &self.config.palette);
273    }
274
275    pub fn copy_oam(&mut self, pattern_table: PatternTable, out: &mut [Color]) {
276        graphics::copy_oam(pattern_table, &mut self.bus.ppu(), out, &self.config.palette);
277    }
278
279    pub fn copy_palette_ram(&mut self, out: &mut [Color]) {
280        graphics::copy_palette_ram(&self.bus.ppu(), out, &self.config.palette);
281    }
282
283    #[inline]
284    pub fn using_double_height_sprites(&mut self) -> bool {
285        self.bus.ppu().get_ppu_registers().double_height_sprites()
286    }
287}
288
289impl EmulatorTrait for NesEmulator {
290    type Button = NesButton;
291    type Inputs = NesInputs;
292    type Config = NesEmulatorConfig;
293    type SaveState = Self;
294
295    type Err<
296        RErr: Debug + Display + Send + Sync + 'static,
297        AErr: Debug + Display + Send + Sync + 'static,
298        SErr: Debug + Display + Send + Sync + 'static,
299    > = NesError<RErr, AErr, SErr>;

Run the emulator for 1 CPU cycle / 3 PPU cycles (NTSC) or 5 CPU cycles / 16 PPU cycles (PAL).

Errors

This method will propagate any errors encountered while rendering a frame, pushing audio samples, or persisting SRAM.

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    ) -> TickResult<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 prev_in_vblank = self.ppu_state.in_vblank();
321
322        let inputs = input_poller.poll();
323        self.bus.update_p1_joypad_state(inputs.p1, self.config.allow_opposing_joypad_directions);
324        self.bus.update_p2_joypad_state(inputs.p2, self.config.allow_opposing_joypad_directions);
325
326        let timing_mode = self.bus.mapper().timing_mode();
327
328        match timing_mode {
329            TimingMode::Ntsc => self.ntsc_tick(),
330            TimingMode::Pal => self.pal_tick(),
331        }
332
333        self.audio_resampler.output_samples(audio_output).map_err(NesError::Audio)?;
334
335        if !prev_in_vblank && self.ppu_state.in_vblank() {
336            if self.config.pal_black_border {
337                ppu::render_pal_black_border(&mut self.ppu_state);
338            }
339
340            self.render_frame(renderer).map_err(NesError::Render)?;
341
342            if self.bus.mapper_mut().get_and_clear_ram_dirty_bit() {
343                let sram = self.bus.mapper().get_prg_ram();
344                save_writer.persist_bytes("sav", sram).map_err(NesError::SaveWrite)?;
345            }
346
347            return Ok(TickEffect::FrameRendered);
348        }
349
350        Ok(TickEffect::None)
351    }
353    fn force_render<R>(&mut self, renderer: &mut R) -> Result<(), R::Err>
354    where
355        R: Renderer,
356    {
357        self.render_frame(renderer)
358    }
359
360    fn reload_config(&mut self, config: &Self::Config) {
361        self.config = *config;
362
363        self.bus.reload_config(config);
364        self.audio_resampler.reload_config(config);
365        self.ppu_state.ntsc_crop_vertical_overscan = config.ntsc_crop_vertical_overscan;
366    }
367
368    fn soft_reset(&mut self) {
369        cpu::reset(&mut self.cpu_state, &mut self.bus.cpu());
370        apu::reset(&mut self.apu_state);
371        ppu::reset(&mut self.ppu_state, &mut self.bus.ppu());
372
373        for _ in 0..10 {
374            apu::tick(&mut self.apu_state, &mut self.bus.cpu(), &self.config);
375            self.bus.tick();
376        }
377    }
378
379    fn hard_reset<S: SaveWriter>(&mut self, save_writer: &mut S) {
380        let rom_bytes = mem::take(&mut self.raw_rom_bytes);
381
382        *self = Self::create(rom_bytes, self.config, save_writer)
383            .expect("Creation during hard reset should never fail");
384    }
385
386    fn load_state(&mut self, mut state: Self::SaveState) {
387        state.bus.move_rom_from(&mut self.bus);
388        state.raw_rom_bytes = mem::take(&mut self.raw_rom_bytes);
389        *self = state;
390    }
391
392    fn to_save_state(&self) -> Self::SaveState {
393        self.partial_clone()
394    }
395
396    fn target_fps(&self) -> f64 {
397        let timing_mode = self.bus.mapper().timing_mode();
398        match (timing_mode, self.config.audio_refresh_rate_adjustment) {
399            (TimingMode::Ntsc, true) => 60.0,
400            (TimingMode::Ntsc, false) => audio::NTSC_NES_NATIVE_DISPLAY_RATE,
401            (TimingMode::Pal, true) => 50.0,
402            (TimingMode::Pal, false) => audio::PAL_NES_NATIVE_DISPLAY_RATE,
403        }
404    }
405
406    fn update_audio_output_frequency(&mut self, output_frequency: u64) {
407        self.audio_resampler.update_output_frequency(output_frequency);
408    }
409}
410
411fn init_apu(apu_state: &mut ApuState, bus: &mut Bus, config: &NesEmulatorConfig) {
412    // Write 0x00 to JOY2 to reset the frame counter
413    bus.cpu().write(0x4017, 0x00);
414    bus.tick();
415
416    // Run the APU for 10 cycles
417    for _ in 0..10 {
418        apu::tick(apu_state, &mut bus.cpu(), config);
419        bus.tick();
420    }
421}