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;
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,
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
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.
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}