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) { self.cpu_cycles_since_poll = self.cpu_cycles_since_poll.saturating_add(1); 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 { ::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); } }