api.rsannotatedapi.rssource250 lines · 7.5 KB · raw
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}