1//! Sega Master System / Game Gear public interface and main loop 2 3use crate::audio::{AudioResampler, TimingModeExt}; 4use crate::bus::Bus; 5use crate::input::InputState; 6use crate::memory::Memory; 7use crate::vdp::{Vdp, VdpBuffer, VdpTickEffect, ViewportSize}; 8use crate::{VdpVersion, vdp}; 9use bincode::{Decode, Encode}; 10use jgenesis_common::boxedarray::BoxedColorArray; 11use jgenesis_common::cheats::ByteCheatCodeU16Address; 12use jgenesis_common::frontend::{ 13 AudioOutput, Color, CompositeParams, EmulatorConfigTrait, EmulatorTrait, FrameSize, 14 InputPoller, PartialClone, RenderFrameOptions, Renderer, SamplesPerColorCycle, SaveWriter, 15 TickEffect, TimingMode, 16}; 17use jgenesis_proc_macros::ConfigDisplay; 18use smsgg_config::{ 19 GgAspectRatio, SmsAspectRatio, SmsGgButton, SmsGgInputs, SmsGgRegion, SmsModel, 20}; 21use std::fmt::{Debug, Display}; 22use std::num::NonZeroU32; 23use thiserror::Error; 24use ti_sn76489::{Sn76489, Sn76489TickEffect, Sn76489Version}; 25use ym_opll::Ym2413; 26use z80_emu::{InterruptMode, Z80}; 27 28#[derive(Debug, Error)] 29pub enum SmsGgError<RErr, AErr, SErr> { 30 #[error("Rendering error: {0}")] 31 Render(RErr), 32 #[error("Audio output error: {0}")] 33 Audio(AErr), 34 #[error("Save write error: {0}")] 35 SaveWrite(SErr), 36} 37 38pub type SmsGgResult<RErr, AErr, SErr> = Result<TickEffect, SmsGgError<RErr, AErr, SErr>>; 39 40#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 41pub enum SmsGgHardware { 42 MasterSystem, 43 GameGear, 44 Sg1000, 45} 46 47#[derive(Debug, Clone, Encode, Decode, ConfigDisplay)] 48pub struct SmsGgEmulatorConfig { 49 pub sms_timing_mode: TimingMode, 50 pub sms_model: SmsModel, 51 pub forced_psg_version: Option<Sn76489Version>, 52 pub sms_aspect_ratio: SmsAspectRatio, 53 pub gg_aspect_ratio: GgAspectRatio, 54 pub remove_sprite_limit: bool, 55 pub forced_region: Option<SmsGgRegion>, 56 pub sms_crop_vertical_border: bool, 57 pub sms_crop_left_border: bool, 58 pub gg_frame_blending: bool, 59 pub gg_use_sms_resolution: bool, 60 pub fm_sound_unit_enabled: bool, 61 pub z80_divider: NonZeroU32, 62 pub allow_opposing_joypad_directions: bool, 63 #[cfg_display(skip)] 64 pub cheat_codes: Vec<ByteCheatCodeU16Address>, 65} 66 67impl EmulatorConfigTrait for SmsGgEmulatorConfig { 68 fn with_overclocking_disabled(&self) -> Self { 69 Self { z80_divider: NonZeroU32::new(crate::NATIVE_Z80_DIVIDER).unwrap(), ..self.clone() } 70 } 71} 72 73impl SmsGgEmulatorConfig { 74 pub(crate) fn region(&self, memory: &Memory) -> SmsGgRegion { 75 self.forced_region.unwrap_or_else(|| memory.guess_cartridge_region()) 76 } 77 78 pub(crate) fn render_options(&self, hardware: SmsGgHardware) -> RenderFrameOptions { 79 match hardware { 80 SmsGgHardware::MasterSystem | SmsGgHardware::Sg1000 => self.sms_render_options(), 81 SmsGgHardware::GameGear => self.gg_render_options(), 82 } 83 } 84 85 pub(crate) fn sms_render_options(&self) -> RenderFrameOptions { 86 RenderFrameOptions { 87 pixel_aspect_ratio: self.sms_aspect_ratio.to_pixel_aspect_ratio(), 88 composite_params: Some(CompositeParams { 89 upscale_factor: 10, 90 samples_per_color_cycle: SamplesPerColorCycle::Fifteen, 91 }), 92 ..RenderFrameOptions::default() 93 } 94 } 95 96 pub(crate) fn gg_render_options(&self) -> RenderFrameOptions { 97 RenderFrameOptions { 98 pixel_aspect_ratio: self.gg_aspect_ratio.to_pixel_aspect_ratio(), 99 frame_blending: self.gg_frame_blending, 100 ..RenderFrameOptions::default() 101 } 102 } 103} 104 105#[derive(Debug, Clone, Encode, Decode, PartialClone)] 106pub struct SmsGgEmulator { 107 #[partial_clone(partial)] 108 memory: Memory, 109 z80: Z80, 110 vdp: Vdp, 111 vdp_version: VdpVersion, 112 psg: Sn76489, 113 ym2413: Option<Ym2413>, 114 input: InputState, 115 audio_resampler: AudioResampler, 116 frame_buffer: BoxedColorArray<{ vdp::FRAME_BUFFER_LEN }>, 117 config: SmsGgEmulatorConfig, 118 vdp_mclk_counter: u32, 119 psg_mclk_counter: u32, 120 frame_count: u64, 121 reset_frames_remaining: u32, 122} 123 124const VDP_DIVIDER: u32 = 10; 125const PSG_DIVIDER: u32 = 15; 126 127const YM2413_CLOCK_INTERVAL: u8 = 72; 128 129impl SmsGgEmulator { 130 #[must_use] 131 pub fn create<S: SaveWriter>( 132 rom: Option<Vec<u8>>, 133 bios_rom: Option<Vec<u8>>, 134 hardware: SmsGgHardware, 135 config: SmsGgEmulatorConfig, 136 save_writer: &mut S, 137 ) -> Self { 138 let cartridge_ram = save_writer.load_bytes("sav").ok(); 139 140 let vdp_version = determine_vdp_version(hardware, &config); 141 let psg_version = determine_psg_version(hardware, &config); 142 143 log::info!("VDP version: {vdp_version:?}"); 144 log::info!("PSG version: {psg_version:?}"); 145 146 let rom = match rom { 147 Some(rom) if !rom.is_empty() => rom, 148 _ => vec![0xFF; 0x8000], 149 }; 150 let memory = Memory::new(rom, bios_rom, cartridge_ram, hardware, &config); 151 let vdp = Vdp::new(vdp_version, &config); 152 let psg = Sn76489::new(psg_version); 153 let input = InputState::new(config.region(&memory), &config); 154 155 log::info!("Region in cartridge header: {:?}", memory.guess_cartridge_region()); 156 157 let mut z80 = Z80::new(); 158 init_z80(&mut z80); 159 160 let ym2413 = 161 config.fm_sound_unit_enabled.then(|| ym_opll::new_ym2413(YM2413_CLOCK_INTERVAL)); 162 163 let timing_mode = vdp.timing_mode(); 164 Self { 165 memory, 166 z80, 167 vdp, 168 vdp_version, 169 psg, 170 ym2413, 171 input, 172 audio_resampler: AudioResampler::new(timing_mode), 173 frame_buffer: BoxedColorArray::new(), 174 config, 175 vdp_mclk_counter: 0, 176 psg_mclk_counter: 0, 177 frame_count: 0, 178 reset_frames_remaining: 0, 179 } 180 } 181 182 #[must_use] 183 pub fn hardware(&self) -> SmsGgHardware { 184 self.vdp_version.hardware() 185 } 186 187 #[must_use] 188 pub fn vdp_version(&self) -> VdpVersion { 189 self.vdp_version 190 } 191 192 #[inline] 193 #[must_use] 194 pub fn has_sram(&self) -> bool { 195 self.memory.cartridge_has_battery() 196 } 197 198 fn render_frame<R: Renderer>(&mut self, renderer: &mut R) -> Result<(), R::Err> { 199 populate_frame_buffer( 200 self.vdp.frame_buffer(), 201 self.vdp.viewport(), 202 self.vdp_version, 203 self.config.sms_crop_vertical_border, 204 self.config.sms_crop_left_border, 205 self.frame_buffer.as_mut_slice(), 206 ); 207 208 let viewport = self.vdp.viewport(); 209 let frame_width = if self.config.sms_crop_left_border { 210 viewport.width_without_border().into() 211 } else { 212 viewport.width.into() 213 }; 214 let frame_height = if self.config.sms_crop_vertical_border { 215 viewport.height_without_border().into() 216 } else { 217 viewport.height.into() 218 }; 219 220 let frame_size = FrameSize { width: frame_width, height: frame_height }; 221 renderer.render_frame( 222 self.frame_buffer.as_slice(), 223 frame_size, 224 self.target_fps(), 225 self.config.render_options(self.vdp_version.hardware()), 226 ) 227 } 228 229 pub fn copy_cram(&self, out: &mut [Color]) { 230 self.vdp.copy_cram(out); 231 } 232 233 pub fn copy_vram(&self, out: &mut [Color], palette: u8, row_len: usize) { 234 self.vdp.copy_vram(out, palette, row_len); 235 } 236 237 pub fn dump_vdp_registers(&self, callback: impl FnMut(u32, &[(&str, &str)])) { 238 self.vdp.dump_registers(callback); 239 } 240} 241 242fn init_z80(z80: &mut Z80) { 243 z80.set_pc(0x0000); 244 z80.set_sp(0xDFFF); 245 z80.set_interrupt_mode(InterruptMode::Mode1); 246} 247 248fn determine_vdp_version(hardware: SmsGgHardware, config: &SmsGgEmulatorConfig) -> VdpVersion { 249 match (hardware, config.sms_timing_mode, config.sms_model) { 250 (SmsGgHardware::MasterSystem, TimingMode::Ntsc, SmsModel::Sms1) => { 251 VdpVersion::NtscMasterSystem1 252 } 253 (SmsGgHardware::MasterSystem, TimingMode::Ntsc, SmsModel::Sms2) => { 254 VdpVersion::NtscMasterSystem2 255 } 256 (SmsGgHardware::MasterSystem, TimingMode::Pal, SmsModel::Sms1) => { 257 VdpVersion::PalMasterSystem1 258 } 259 (SmsGgHardware::MasterSystem, TimingMode::Pal, SmsModel::Sms2) => { 260 VdpVersion::PalMasterSystem2 261 } 262 (SmsGgHardware::GameGear, _, _) => VdpVersion::GameGear, 263 (SmsGgHardware::Sg1000, TimingMode::Ntsc, _) => VdpVersion::NtscSg1000, 264 (SmsGgHardware::Sg1000, TimingMode::Pal, _) => VdpVersion::PalSg1000, 265 } 266} 267 268fn determine_psg_version(hardware: SmsGgHardware, config: &SmsGgEmulatorConfig) -> Sn76489Version { 269 config.forced_psg_version.unwrap_or(match hardware { 270 SmsGgHardware::MasterSystem => Sn76489Version::MasterSystem2, 271 SmsGgHardware::GameGear => Sn76489Version::Standard, 272 SmsGgHardware::Sg1000 => Sn76489Version::Discrete, 273 }) 274} 275 276impl EmulatorTrait for SmsGgEmulator { 277 type Button = SmsGgButton; 278 type Inputs = SmsGgInputs; 279 type Config = SmsGgEmulatorConfig; 280 type SaveState = Self; 281 282 type Err< 283 RErr: Debug + Display + Send + Sync + 'static, 284 AErr: Debug + Display + Send + Sync + 'static, 285 SErr: Debug + Display + Send + Sync + 'static, 286 > = SmsGgError<RErr, AErr, SErr>; 287 288 /// Execute a single CPU instruction and run the rest of the components for the corresponding 289 /// number of cycles. 290 /// 291 /// # Errors 292 /// 293 /// This method will propagate any errors encountered while rendering frames, pushing audio 294 /// samples, or persisting cartridge SRAM. 295 #[inline] 296 fn tick<R, A, I, S>( 297 &mut self, 298 renderer: &mut R, 299 audio_output: &mut A, 300 input_poller: &mut I, 301 save_writer: &mut S, 302 ) -> SmsGgResult<R::Err, A::Err, S::Err> 303 where 304 R: Renderer, 305 A: AudioOutput, 306 I: InputPoller<Self::Inputs>, 307 S: SaveWriter, 308 { 309 self.input.set_inputs(*input_poller.poll()); 310 311 let z80_t_cycles = self.z80.execute_instruction(&mut Bus::new( 312 self.vdp_version, 313 &mut self.memory, 314 &mut self.vdp, 315 &mut self.psg, 316 self.ym2413.as_mut(), 317 &mut self.input, 318 )); 319 320 let mclk_cycles = z80_t_cycles * self.config.z80_divider.get(); 321 self.vdp_mclk_counter += mclk_cycles; 322 self.psg_mclk_counter += mclk_cycles; 323 324 while self.psg_mclk_counter >= PSG_DIVIDER { 325 self.psg_mclk_counter -= PSG_DIVIDER; 326 327 if let Some(ym2413) = &mut self.ym2413 { 328 ym2413.tick(); 329 } 330 if self.psg.tick() == Sn76489TickEffect::Clocked { 331 let (psg_sample_l, psg_sample_r) = 332 if self.memory.psg_enabled() { self.psg.sample() } else { (0.0, 0.0) }; 333 let ym_sample = if self.memory.fm_enabled() { 334 self.ym2413.as_ref().map_or(0.0, Ym2413::sample) 335 } else { 336 0.0 337 }; 338 339 let sample_l = psg_sample_l + ym_sample; 340 let sample_r = psg_sample_r + ym_sample; 341 self.audio_resampler.collect_sample(sample_l, sample_r); 342 } 343 } 344 345 self.audio_resampler.output_samples(audio_output).map_err(SmsGgError::Audio)?; 346 347 let mut frame_rendered = false; 348 while self.vdp_mclk_counter >= VDP_DIVIDER { 349 self.vdp_mclk_counter -= VDP_DIVIDER; 350 351 if self.vdp.tick() == VdpTickEffect::FrameComplete { 352 self.render_frame(renderer).map_err(SmsGgError::Render)?; 353 frame_rendered = true; 354 355 self.input.set_reset(self.reset_frames_remaining != 0); 356 self.reset_frames_remaining = self.reset_frames_remaining.saturating_sub(1); 357 358 self.frame_count += 1; 359 if self.frame_count.is_multiple_of(60) 360 && self.memory.cartridge_has_battery() 361 && self.memory.cartridge_ram_dirty() 362 { 363 self.memory.clear_cartridge_ram_dirty(); 364 save_writer 365 .persist_bytes("sav", self.memory.cartridge_ram()) 366 .map_err(SmsGgError::SaveWrite)?; 367 } 368 } 369 } 370 371 Ok(if frame_rendered { TickEffect::FrameRendered } else { TickEffect::None }) 372 } 373 374 fn force_render<R>(&mut self, renderer: &mut R) -> Result<(), R::Err> 375 where 376 R: Renderer, 377 { 378 self.render_frame(renderer) 379 } 380 381 fn reload_config(&mut self, config: &Self::Config) { 382 self.config = config.clone(); 383 384 let hardware = self.hardware(); 385 self.vdp_version = determine_vdp_version(hardware, config); 386 self.vdp.update_config(self.vdp_version, config); 387 388 self.psg.set_version(determine_psg_version(hardware, config)); 389 390 self.memory.update_config(config); 391 392 self.input.reload_config(config.region(&self.memory), config); 393 self.audio_resampler.update_timing_mode(self.vdp.timing_mode()); 394 } 395 396 fn soft_reset(&mut self) { 397 log::info!("Soft resetting console"); 398 399 // The SMS RESET button only sets a bit in a register; emulate "soft reset" by keeping the 400 // button virtually held down for 5 frames 401 self.reset_frames_remaining = 5; 402 } 403 404 fn hard_reset<S: SaveWriter>(&mut self, _save_writer: &mut S) { 405 log::info!("Hard resetting console"); 406 407 self.memory.reset(&self.config); 408 409 self.z80 = Z80::new(); 410 init_z80(&mut self.z80); 411 412 self.vdp = Vdp::new(self.vdp_version, &self.config); 413 self.psg = Sn76489::new(self.psg.version()); 414 self.input = InputState::new(self.input.region(), &self.config); 415 416 self.ym2413 = 417 self.config.fm_sound_unit_enabled.then(|| ym_opll::new_ym2413(YM2413_CLOCK_INTERVAL)); 418 419 self.frame_buffer = BoxedColorArray::new(); 420 421 self.vdp_mclk_counter = 0; 422 self.psg_mclk_counter = 0; 423 self.frame_count = 0; 424 self.reset_frames_remaining = 0; 425 } 426 427 fn load_state(&mut self, mut state: Self::SaveState) { 428 state.memory.take_rom_from(&mut self.memory); 429 *self = state; 430 } 431 432 fn to_save_state(&self) -> Self::SaveState { 433 self.partial_clone() 434 } 435 436 fn target_fps(&self) -> f64 { 437 let timing_mode = self.vdp.timing_mode(); 438 let mclk_frequency = timing_mode.mclk_frequency(); 439 let scanlines_per_frame = match timing_mode { 440 TimingMode::Ntsc => vdp::NTSC_SCANLINES_PER_FRAME, 441 TimingMode::Pal => vdp::PAL_SCANLINES_PER_FRAME, 442 }; 443 444 mclk_frequency / f64::from(vdp::MCLK_CYCLES_PER_SCANLINE) / f64::from(scanlines_per_frame) 445 } 446 447 fn update_audio_output_frequency(&mut self, output_frequency: u64) { 448 self.audio_resampler.update_output_frequency(output_frequency); 449 } 450} 451 452fn populate_frame_buffer( 453 vdp_buffer: &VdpBuffer, 454 viewport: ViewportSize, 455 vdp_version: VdpVersion, 456 crop_vertical_border: bool, 457 crop_left_border: bool, 458 frame_buffer: &mut [Color], 459) { 460 let (row_skip, row_take) = if crop_vertical_border { 461 (viewport.top_border_height as usize, viewport.height_without_border() as usize) 462 } else { 463 (0, viewport.height as usize) 464 }; 465 let (col_skip, screen_width) = if crop_left_border { 466 (viewport.left_border_width as usize, viewport.width_without_border() as usize) 467 } else { 468 (0, viewport.width as usize) 469 }; 470 471 for (i, row) in vdp_buffer.iter().skip(row_skip).take(row_take).enumerate() { 472 for (j, color) in row.iter().copied().skip(col_skip).enumerate() { 473 let color = match vdp_version.hardware() { 474 SmsGgHardware::MasterSystem => Color::rgb( 475 vdp::convert_sms_color(color & 0x03), 476 vdp::convert_sms_color((color >> 2) & 0x03), 477 vdp::convert_sms_color((color >> 4) & 0x03), 478 ), 479 SmsGgHardware::GameGear => Color::rgb( 480 vdp::convert_gg_color(color & 0x0F), 481 vdp::convert_gg_color((color >> 4) & 0x0F), 482 vdp::convert_gg_color((color >> 8) & 0x0F), 483 ), 484 SmsGgHardware::Sg1000 => vdp::convert_sg_color(color), 485 }; 486 487 frame_buffer[i * screen_width + j] = color; 488 } 489 } 490}