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}; 25 26// The number of master clock ticks to run in one `Emulator::tick` call 27const PAL_MASTER_CLOCK_TICKS: u32 = 80; 28 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 { 36 /// Force timing mode to NTSC/PAL if set 37 /// If None, timing mode will default based on iNES ROM header 38 pub forced_timing_mode: Option<TimingMode>, 39 /// Aspect ratio 40 pub aspect_ratio: NesAspectRatio, 41 /// Palette 42 #[cfg_display(skip)] 43 pub palette: NesPalette, 44 /// Crop frame vertically from 240px to 224px in NTSC mode 45 pub ntsc_crop_vertical_overscan: bool, 46 /// Overscan in pixels 47 pub overscan: Overscan, 48 /// Whether to send the renderer 6-bit NES colors instead of RGB888 colors 49 pub emulate_ntsc_output: bool, 50 /// If true, do not emulate the 8 sprite per scanline limit; this eliminates sprite flickering 51 /// but can cause bugs in some games 52 pub remove_sprite_limit: bool, 53 /// 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, 55 /// 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, 58 /// If true, adjust audio frequency so that audio sync times to 60Hz NTSC / 50Hz PAL 59 pub audio_refresh_rate_adjustment: bool, 60 /// Whether to allow simultaneous left+right and up+down joypad inputs. 61 /// Some games exhibit severe glitches when opposing joypad directions are pressed 62 /// simultaneously, e.g. Zelda 2 and Battletoads 63 pub allow_opposing_joypad_directions: bool, 64 /// Whether DMA is allowed to trigger dummy reads to the controller port registers. 65 /// Dummy reads are more accurate to hardware but cause input glitches in some games. 66 pub dma_dummy_joy_reads: bool, 67} 68 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 { 105 /// Create a new emulator instance. 106 /// 107 /// # Errors 108 /// 109 /// This function will return an error if it cannot successfully parse NES ROM data out of the 110 /// 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 } 143 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>; 300 301 /// Run the emulator for 1 CPU cycle / 3 PPU cycles (NTSC) or 5 CPU cycles / 16 PPU cycles (PAL). 302 /// 303 /// # Errors 304 /// 305 /// This method will propagate any errors encountered while rendering a frame, pushing 306 /// 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 } 352 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}