From 5d48780ad55c23f3f2811782d37a611b32228264 Mon Sep 17 00:00:00 2001 From: JakeBreath Date: Sun, 9 Aug 2026 22:41:07 -0500 Subject: [PATCH] fixed PPU implementation, seems to render correctly --- src/ppu.rs | 104 +++++++++++++++++++++++++++-------------------------- 1 file changed, 54 insertions(+), 50 deletions(-) diff --git a/src/ppu.rs b/src/ppu.rs index ae51098..ab326cf 100644 --- a/src/ppu.rs +++ b/src/ppu.rs @@ -50,27 +50,42 @@ impl Ppu { self.status &= !0x80; } - let render_bg = self.mask & 0x08 != 0; + let rendering = self.mask & 0x08 != 0; - if render_bg && self.scanline < 240 && self.cycle == 0 { - // fine-X scroll: consume the first `x` pixels of this scanline - self.bg_pattern_lo <<= self.x; - self.bg_pattern_hi <<= self.x; - self.bg_attr_lo <<= self.x; - self.bg_attr_hi <<= self.x; + // Background pipeline: visible + pre-render scanlines only + if rendering && (self.scanline < 240 || self.scanline == 261) { + // Shifters shift ONLY during the fetch windows (reference timing) + if (self.cycle >= 2 && self.cycle < 258) || (self.cycle >= 321 && self.cycle < 338) { + self.update_shifters(); + self.bg_fetch(cart); + } + + // End of visible scanline: advance vertical position + if self.cycle == 256 && (self.scanline < 240 || self.scanline == 261) { + self.increment_vertical(); + } + // ...and preload the first tile of the NEXT scanline, resetting X + if self.cycle == 257 && (self.scanline < 240 || self.scanline == 261) { + self.load_shifters(); + self.copy_horizontal(); + } + // Idle nametable fetches at the end of the line + if (self.cycle == 338 || self.cycle == 340) && (self.scanline < 240 || self.scanline == 261) { + self.tile_latch = self.nametable_read(cart, 0x2000 | (self.vram_addr & 0x0FFF)); + } + // Pre-render line: reload vertical scroll from t + if self.scanline == 261 && self.cycle >= 280 && self.cycle < 305 { + self.copy_vertical(); + } } - if render_bg && self.scanline < 240 && self.cycle < 256 { + // Compose + store pixel (visible only, x = cycle - 1) + if rendering && self.scanline < 240 && self.cycle >= 1 && self.cycle <= 256 { self.render_bg_pixel(cart); } - if render_bg && (self.scanline < 240 || self.scanline == 261) { - self.bg_fetch(cart); - } - // advance timing self.cycle += 1; - let rendering = self.mask & 0x18 != 0; // bg or sprite render enabled let line_len = if self.scanline == 261 && self.odd_frame && rendering { 340 } else { 341 }; if self.cycle >= line_len { self.cycle = 0; @@ -212,40 +227,37 @@ impl Ppu { self.palette[palette_index(addr)] } - fn bg_fetch(&mut self, cart: &mut Cartridge) { - // shift registers move left every dot + fn update_shifters(&mut self) { self.bg_pattern_lo <<= 1; self.bg_pattern_hi <<= 1; self.bg_attr_lo <<= 1; self.bg_attr_hi <<= 1; + } - if self.cycle == 255 && self.scanline < 240 { - self.copy_horizontal(); // dot 255: reload coarse X from t - } + fn load_shifters(&mut self) { + self.bg_pattern_lo = (self.bg_pattern_lo & 0xFF00) | self.pattern_lo_latch as u16; + self.bg_pattern_hi = (self.bg_pattern_hi & 0xFF00) | self.pattern_hi_latch as u16; + self.bg_attr_lo = (self.bg_attr_lo & 0xFF00) | if self.attr_latch & 0x01 != 0 { 0xFF } else { 0x00 }; + self.bg_attr_hi = (self.bg_attr_hi & 0xFF00) | if self.attr_latch & 0x02 != 0 { 0xFF } else { 0x00 }; + } - if self.cycle == 256 && (self.scanline < 240 || self.scanline == 261) { - self.increment_vertical(); // dot 256: advance to next scanline's tiles - } - - if self.cycle == 280 && self.scanline == 261 { - self.copy_vertical(); // dot 280 (pre-render): reload vertical bits from t - } - - match self.cycle % 8 { - 0 => self.tile_latch = self.nametable_read(cart, 0x2000 | (self.vram_addr & 0x0FFF)), + fn bg_fetch(&mut self, cart: &mut Cartridge) { + match (self.cycle - 1) % 8 { + 0 => { + self.load_shifters(); + self.tile_latch = self.nametable_read(cart, 0x2000 | (self.vram_addr & 0x0FFF)); + } 2 => { - // attribute byte address (coarseY>>2, coarseX>>2, nametable select) let addr = 0x23C0 | (self.vram_addr & 0x0C00) | ((self.vram_addr >> 4) & 0x38) | ((self.vram_addr >> 2) & 0x07); let byte = self.nametable_read(cart, addr); - // select the 2 bits for this tile's 2x2 block let shift = ((self.vram_addr & 0x40) >> 4) | (self.vram_addr & 0x02); self.attr_latch = (byte >> shift) & 0x03; } 4 => { - let base = ((self.ctrl & 0x10) as u16) << 8; // BG pattern table select + let base = ((self.ctrl & 0x10) as u16) << 8; let fine_y = (self.vram_addr >> 12) & 0x07; self.pattern_lo_latch = cart.read_chr(base | ((self.tile_latch as u16) << 4) | fine_y); } @@ -254,15 +266,7 @@ impl Ppu { let fine_y = (self.vram_addr >> 12) & 0x07; self.pattern_hi_latch = cart.read_chr(base | ((self.tile_latch as u16) << 4) | fine_y + 8); } - 7 => { - // reload shift registers with the fetched tile - self.bg_pattern_lo = (self.bg_pattern_lo & 0xFF00) | self.pattern_lo_latch as u16; - self.bg_pattern_hi = (self.bg_pattern_hi & 0xFF00) | self.pattern_hi_latch as u16; - // 0xFF/0x00 per attribute bit -> constant top bit during the tile's 8 pixels - self.bg_attr_lo = (self.bg_attr_lo & 0xFF00) | if self.attr_latch & 0x01 != 0 { 0xFF } else { 0x00 }; - self.bg_attr_hi = (self.bg_attr_hi & 0xFF00) | if self.attr_latch & 0x02 != 0 { 0xFF } else { 0x00 }; - self.increment_coarse_x(); - } + 7 => self.increment_coarse_x(), _ => {} } } @@ -303,22 +307,22 @@ impl Ppu { } fn render_bg_pixel(&mut self, _cart: &mut Cartridge) { - // fine-X offset: the first `x` dots render from the pre-fetched tile - let pattern_lo_bit = (self.bg_pattern_lo >> 15) & 1; - let pattern_hi_bit = (self.bg_pattern_hi >> 15) & 1; - let attr_lo_bit = (self.bg_attr_lo >> 15) & 1; - let attr_hi_bit = (self.bg_attr_hi >> 15) & 1; + let bit_mux = 0x8000u16 >> self.x; + let pattern_lo_bit = (self.bg_pattern_lo & bit_mux) != 0; + let pattern_hi_bit = (self.bg_pattern_hi & bit_mux) != 0; + let attr_lo_bit = (self.bg_attr_lo & bit_mux) != 0; + let attr_hi_bit = (self.bg_attr_hi & bit_mux) != 0; - let pixel = (pattern_hi_bit << 1) | pattern_lo_bit; - let palette_bits = (attr_hi_bit << 1) | attr_lo_bit; + let pixel = (pattern_hi_bit as u8) << 1 | pattern_lo_bit as u8; + let palette_bits = (attr_hi_bit as u8) << 1 | attr_lo_bit as u8; let color = if pixel == 0 { - self.palette[0] // "universal background" transparent color + self.palette[0] } else { - self.palette[(palette_bits * 4 + pixel) as usize] // background palette + self.palette[(palette_bits * 4 + pixel) as usize] }; - let idx = (self.scanline as usize) * 256 + (self.cycle as usize); + let idx = (self.scanline as usize) * 256 + (self.cycle as usize - 1); self.framebuffer[idx] = color; }