Initial commit: NES emulator with GTK4 desktop frontend
Some checks failed
CI / rust (push) Has been cancelled
Some checks failed
CI / rust (push) Has been cancelled
Full NES emulation: CPU, PPU, APU, 47 mappers, iNES/NES 2.0 parsing. GTK4 desktop client with HeaderBar, pixel-perfect Cairo rendering, drag-and-drop ROM loading, and keyboard shortcuts. 187 tests covering core emulation, mappers, and runtime.
This commit is contained in:
116
src/native_core/bus/timing.rs
Normal file
116
src/native_core/bus/timing.rs
Normal file
@@ -0,0 +1,116 @@
|
||||
use super::{
|
||||
NativeBus, PPU_DOTS_PER_FRAME, PPU_DOTS_PER_SCANLINE, PPU_PRERENDER_SCANLINE,
|
||||
PPU_VBLANK_START_SCANLINE,
|
||||
};
|
||||
use crate::native_core::cpu::CpuBus;
|
||||
use crate::native_core::mapper::Mapper;
|
||||
|
||||
impl NativeBus {
|
||||
fn clock_one_cpu_cycle(&mut self) {
|
||||
for _ in 0..3 {
|
||||
self.clock_ppu_dot();
|
||||
}
|
||||
self.mapper.clock_cpu(1);
|
||||
self.apu.clock_cpu_cycle();
|
||||
}
|
||||
|
||||
fn service_dmc_dma(&mut self, addr: u16) {
|
||||
let byte = self.dma_read(addr);
|
||||
self.apu.provide_dmc_dma_byte(byte);
|
||||
// DMC DMA steals CPU bus cycles while APU/PPU/mapper keep ticking.
|
||||
for _ in 0..4 {
|
||||
self.clock_one_cpu_cycle();
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn dma_read(&mut self, addr: u16) -> u8 {
|
||||
<Self as CpuBus>::read(self, addr)
|
||||
}
|
||||
|
||||
pub(super) fn clock_ppu_dot(&mut self) {
|
||||
self.ppu_dot += 1;
|
||||
if self.ppu_dot >= PPU_DOTS_PER_FRAME {
|
||||
self.ppu_dot = 0;
|
||||
self.frame_complete = true;
|
||||
self.odd_frame = !self.odd_frame;
|
||||
}
|
||||
|
||||
let scanline = self.ppu_dot / PPU_DOTS_PER_SCANLINE;
|
||||
let dot = self.ppu_dot % PPU_DOTS_PER_SCANLINE;
|
||||
let rendering_enabled = self.ppu.rendering_enabled();
|
||||
|
||||
{
|
||||
let mapper: &(dyn Mapper + Send) = &*self.mapper;
|
||||
self.ppu.render_dot(mapper, scanline, dot);
|
||||
}
|
||||
|
||||
if rendering_enabled && (scanline < 240 || scanline == PPU_PRERENDER_SCANLINE) {
|
||||
if self.mapper.needs_ppu_a12_clock() {
|
||||
let a12_high = self.ppu.mmc3_a12_high_at(scanline, dot);
|
||||
if a12_high {
|
||||
if !self.mmc3_a12_prev_high
|
||||
&& self.mmc3_a12_low_dots >= 8
|
||||
&& self.mmc3_last_irq_scanline != scanline
|
||||
{
|
||||
self.mapper.clock_scanline();
|
||||
self.mmc3_last_irq_scanline = scanline;
|
||||
}
|
||||
self.mmc3_a12_prev_high = true;
|
||||
self.mmc3_a12_low_dots = 0;
|
||||
} else {
|
||||
self.mmc3_a12_prev_high = false;
|
||||
self.mmc3_a12_low_dots = self.mmc3_a12_low_dots.saturating_add(1);
|
||||
}
|
||||
} else if dot == 260 {
|
||||
self.mapper.clock_scanline();
|
||||
}
|
||||
} else {
|
||||
self.mmc3_a12_prev_high = false;
|
||||
self.mmc3_a12_low_dots = self.mmc3_a12_low_dots.saturating_add(1);
|
||||
}
|
||||
if rendering_enabled && scanline == PPU_PRERENDER_SCANLINE && dot == 339 && self.odd_frame {
|
||||
// NTSC odd frame timing: skip pre-render dot 340 when rendering is enabled.
|
||||
self.ppu_dot = self.ppu_dot.saturating_add(1);
|
||||
}
|
||||
|
||||
if !self.in_vblank && scanline == PPU_VBLANK_START_SCANLINE && dot == 1 {
|
||||
if self.suppress_vblank_this_frame {
|
||||
self.suppress_vblank_this_frame = false;
|
||||
self.in_vblank = false;
|
||||
self.ppu.set_vblank(false);
|
||||
} else {
|
||||
self.in_vblank = true;
|
||||
self.ppu.set_vblank(true);
|
||||
if self.ppu.nmi_enabled() {
|
||||
self.nmi_pending = true;
|
||||
}
|
||||
}
|
||||
} else if scanline == PPU_PRERENDER_SCANLINE && dot == 1 {
|
||||
self.in_vblank = false;
|
||||
self.ppu.set_vblank(false);
|
||||
self.ppu.set_sprite0_hit(false);
|
||||
self.ppu.set_sprite_overflow(false);
|
||||
self.suppress_vblank_this_frame = false;
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn clock_cpu_cycles(&mut self, cycles: u32) {
|
||||
if cycles == 0 {
|
||||
return;
|
||||
}
|
||||
let mut remaining = cycles;
|
||||
while remaining != 0 {
|
||||
self.clock_one_cpu_cycle();
|
||||
while let Some(addr) = self.apu.take_dmc_dma_request() {
|
||||
self.service_dmc_dma(addr);
|
||||
}
|
||||
remaining -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn note_scroll_write_now(&mut self) {
|
||||
let scanline = (self.ppu_dot / PPU_DOTS_PER_SCANLINE) as usize;
|
||||
let dot = self.ppu_dot % PPU_DOTS_PER_SCANLINE;
|
||||
self.ppu.note_scroll_register_write(scanline, dot);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user