1use crate::bus::Bus; 2use crate::input::InputState; 3use crate::memory::{HuCard, Memory}; 4use crate::psg::Huc6280Psg; 5use crate::video; 6use crate::video::VideoSubsystem; 7use bincode::{Decode, Encode}; 8use huc6280_emu::Huc6280; 9use jgenesis_common::frontend::{ 10 AudioOutput, Color, EmulatorConfigTrait, EmulatorTrait, FiniteF64, InputPoller, PartialClone, 11 RenderFrameOptions, Renderer, SaveWriter, TickEffect, TickResult, 12}; 13use jgenesis_proc_macros::ConfigDisplay; 14use pce_config::{ 15 PceAspectRatio, PceAudioResampler, PceButton, PceInputDevice, PceInputs, PcePaletteType, 16 PceRegion, 17}; 18use std::cmp; 19use std::fmt::{Debug, Display}; 20use std::num::NonZeroU64; 21use thiserror::Error;
Roughly 21.47 MHz
24pub const MASTER_CLOCK_FREQUENCY: f64 = 236.25e6 / 11.0;
26#[derive(Debug, Clone, Copy, Encode, Decode, ConfigDisplay)] 27pub struct PceEmulatorConfig { 28 pub region: PceRegion, 29 pub cpu_fast_clock_divider: NonZeroU64, 30 pub aspect_ratio: PceAspectRatio, 31 pub palette: PcePaletteType, 32 pub crop_overscan: bool, 33 pub remove_sprite_limits: bool, 34 pub audio_resampler: PceAudioResampler, 35 pub input_device: PceInputDevice, 36 #[cfg_display(debug_fmt)] 37 pub turbo_tap_connected: [bool; pce_config::TURBO_TAP_GAMEPADS as usize], 38 pub allow_opposing_joypad_directions: bool, 39 pub allow_simultaneous_run_select: bool, 40} 41 42impl EmulatorConfigTrait for PceEmulatorConfig { 43 fn with_overclocking_disabled(&self) -> Self { 44 Self { 45 cpu_fast_clock_divider: NonZeroU64::new(pce_config::NATIVE_FAST_CPU_DIVIDER).unwrap(), 46 ..*self 47 } 48 } 49} 50 51impl PceEmulatorConfig { 52 #[must_use] 53 pub fn clamped_cpu_fast_divider(&self) -> u64 { 54 cmp::min(pce_config::NATIVE_FAST_CPU_DIVIDER, self.cpu_fast_clock_divider.get()) 55 } 56} 57 58#[derive(Debug, Error)] 59pub enum PceError<RErr, AErr, SErr> { 60 #[error("Error rendering frame: {0}")] 61 Render(RErr), 62 #[error("Error outputting audio: {0}")] 63 Audio(AErr), 64 #[error("Error writing save file: {0}")] 65 SaveWrite(SErr), 66} 67 68#[derive(Debug, Clone, PartialClone, Encode, Decode)] 69pub struct PcEngineEmulator { 70 cpu: Huc6280, 71 video: VideoSubsystem, 72 psg: Huc6280Psg, 73 memory: Memory, 74 cartridge: HuCard, 75 input_state: InputState, 76 config: PceEmulatorConfig, 77 cycle_counter: u64, 78 last_psg_sync_cycles: u64, 79} 80 81impl PcEngineEmulator { 82 #[must_use] 83 pub fn create<S: SaveWriter>( 84 rom: Vec<u8>, 85 config: PceEmulatorConfig, 86 save_writer: &mut S, 87 ) -> Self { 88 let initial_sram = save_writer.load_bytes("sav").ok(); 89 90 let mut emulator = Self { 91 cpu: Huc6280::new(), 92 video: VideoSubsystem::new(config), 93 psg: Huc6280Psg::new(config), 94 memory: Memory::new(&config), 95 cartridge: HuCard::new(rom, initial_sram), 96 input_state: InputState::new(config), 97 config, 98 cycle_counter: 0, 99 last_psg_sync_cycles: 0, 100 }; 101 102 emulator.cpu.reset(&mut Bus { 103 memory: &mut emulator.memory, 104 video: &mut emulator.video, 105 psg: &mut emulator.psg, 106 cartridge: &mut emulator.cartridge, 107 input: &mut emulator.input_state, 108 cycle_counter: &mut emulator.cycle_counter, 109 }); 110 111 emulator 112 } 113 114 fn render_frame<R: Renderer>(&mut self, renderer: &mut R) -> Result<(), R::Err> { 115 self.video.render_rgba8_frame_buffer(); 116 117 let aspect_ratio = match self.config.aspect_ratio { 118 PceAspectRatio::Ntsc => Some(self.video.ntsc_aspect_ratio()), 119 PceAspectRatio::SquarePixels => Some(FiniteF64::ONE), 120 PceAspectRatio::Stretched => None, 121 }; 122 123 renderer.render_frame( 124 self.video.frame_buffer(), 125 self.video.frame_size(), 126 self.video.target_fps(), 127 RenderFrameOptions { 128 pixel_aspect_ratio: aspect_ratio, 129 composite_params: Some(self.video.composite_params()), 130 ntsc_per_frame_params: Some(self.video.ntsc_per_frame_params()), 131 ..RenderFrameOptions::default() 132 }, 133 ) 134 } 135 136 pub fn dump_vram(&self, palette: u16, out: &mut [[Color; 64]]) { 137 self.video.dump_vram(palette, out); 138 } 139 140 pub fn dump_palettes(&self, out: &mut [Color]) { 141 self.video.dump_palettes(out); 142 } 143} 144 145impl EmulatorTrait for PcEngineEmulator { 146 type Button = PceButton; 147 type Inputs = PceInputs; 148 type Config = PceEmulatorConfig; 149 type SaveState = Self; 150 151 type Err< 152 RErr: Debug + Display + Send + Sync + 'static, 153 AErr: Debug + Display + Send + Sync + 'static, 154 SErr: Debug + Display + Send + Sync + 'static, 155 > = PceError<RErr, AErr, SErr>; 156 157 fn tick<R, A, I, S>( 158 &mut self, 159 renderer: &mut R, 160 audio_output: &mut A, 161 input_poller: &mut I, 162 save_writer: &mut S, 163 ) -> TickResult<Self::Err<R::Err, A::Err, S::Err>> 164 where 165 R: Renderer, 166 A: AudioOutput, 167 I: InputPoller<Self::Inputs>, 168 S: SaveWriter, 169 { 170 self.input_state.update_inputs(*input_poller.poll()); 171 172 self.cpu.execute_instruction(&mut Bus { 173 memory: &mut self.memory, 174 video: &mut self.video, 175 psg: &mut self.psg, 176 cartridge: &mut self.cartridge, 177 input: &mut self.input_state, 178 cycle_counter: &mut self.cycle_counter, 179 }); 180 181 // Sync PSG here in case the VDC blocked a CPU VRAM access for a long amount of time across 182 // a frame boundary 183 if self.cycle_counter - self.last_psg_sync_cycles >= video::MCLK_CYCLES_PER_SCANLINE { 184 self.psg.step_to(self.cycle_counter); 185 self.psg.drain_output_buffer(audio_output).map_err(PceError::Audio)?; 186 187 self.last_psg_sync_cycles = self.cycle_counter; 188 } 189 190 if self.video.frame_complete() { 191 self.video.clear_frame_complete(); 192 193 self.render_frame(renderer).map_err(PceError::Render)?; 194 195 if self.cartridge.is_sram_dirty() { 196 self.cartridge.clear_sram_dirty(); 197 198 if let Some(sram) = self.cartridge.sram() { 199 save_writer.persist_bytes("sav", sram).map_err(PceError::SaveWrite)?; 200 } 201 } 202 203 Ok(TickEffect::FrameRendered) 204 } else { 205 Ok(TickEffect::None) 206 } 207 } 208 209 fn force_render<R>(&mut self, renderer: &mut R) -> Result<(), R::Err> 210 where 211 R: Renderer, 212 { 213 self.render_frame(renderer) 214 } 215 216 fn reload_config(&mut self, config: &Self::Config) { 217 self.config = *config; 218 219 self.memory.reload_config(config); 220 self.video.reload_config(*config); 221 self.psg.reload_config(*config); 222 self.input_state.reload_config(*config); 223 } 224 225 fn soft_reset(&mut self) { 226 log::warn!("PC Engine does not support soft reset except in software"); 227 } 228 229 fn hard_reset<S: SaveWriter>(&mut self, save_writer: &mut S) { 230 let rom = self.cartridge.clone_rom(); 231 *self = Self::create(rom, self.config, save_writer); 232 } 233 234 fn load_state(&mut self, mut state: Self::SaveState) { 235 state.cartridge.take_rom_from(&mut self.cartridge); 236 *self = state; 237 } 238 239 fn to_save_state(&self) -> Self::SaveState { 240 self.partial_clone() 241 } 242 243 fn target_fps(&self) -> f64 { 244 self.video.target_fps() 245 } 246 247 fn update_audio_output_frequency(&mut self, output_frequency: u64) { 248 self.psg.update_output_frequency(output_frequency); 249 } 250}