fixed PPU implementation, seems to render correctly

This commit is contained in:
2026-08-09 22:41:07 -05:00
parent 1235717c54
commit 5d48780ad5
+54 -50
View File
@@ -50,27 +50,42 @@ impl Ppu {
self.status &= !0x80; 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 { // Background pipeline: visible + pre-render scanlines only
// fine-X scroll: consume the first `x` pixels of this scanline if rendering && (self.scanline < 240 || self.scanline == 261) {
self.bg_pattern_lo <<= self.x; // Shifters shift ONLY during the fetch windows (reference timing)
self.bg_pattern_hi <<= self.x; if (self.cycle >= 2 && self.cycle < 258) || (self.cycle >= 321 && self.cycle < 338) {
self.bg_attr_lo <<= self.x; self.update_shifters();
self.bg_attr_hi <<= self.x; 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); self.render_bg_pixel(cart);
} }
if render_bg && (self.scanline < 240 || self.scanline == 261) {
self.bg_fetch(cart);
}
// advance timing // advance timing
self.cycle += 1; 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 }; let line_len = if self.scanline == 261 && self.odd_frame && rendering { 340 } else { 341 };
if self.cycle >= line_len { if self.cycle >= line_len {
self.cycle = 0; self.cycle = 0;
@@ -212,40 +227,37 @@ impl Ppu {
self.palette[palette_index(addr)] self.palette[palette_index(addr)]
} }
fn bg_fetch(&mut self, cart: &mut Cartridge) { fn update_shifters(&mut self) {
// shift registers move left every dot
self.bg_pattern_lo <<= 1; self.bg_pattern_lo <<= 1;
self.bg_pattern_hi <<= 1; self.bg_pattern_hi <<= 1;
self.bg_attr_lo <<= 1; self.bg_attr_lo <<= 1;
self.bg_attr_hi <<= 1; self.bg_attr_hi <<= 1;
}
if self.cycle == 255 && self.scanline < 240 { fn load_shifters(&mut self) {
self.copy_horizontal(); // dot 255: reload coarse X from t 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) { fn bg_fetch(&mut self, cart: &mut Cartridge) {
self.increment_vertical(); // dot 256: advance to next scanline's tiles match (self.cycle - 1) % 8 {
} 0 => {
self.load_shifters();
if self.cycle == 280 && self.scanline == 261 { self.tile_latch = self.nametable_read(cart, 0x2000 | (self.vram_addr & 0x0FFF));
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)),
2 => { 2 => {
// attribute byte address (coarseY>>2, coarseX>>2, nametable select)
let addr = 0x23C0 let addr = 0x23C0
| (self.vram_addr & 0x0C00) | (self.vram_addr & 0x0C00)
| ((self.vram_addr >> 4) & 0x38) | ((self.vram_addr >> 4) & 0x38)
| ((self.vram_addr >> 2) & 0x07); | ((self.vram_addr >> 2) & 0x07);
let byte = self.nametable_read(cart, addr); 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); let shift = ((self.vram_addr & 0x40) >> 4) | (self.vram_addr & 0x02);
self.attr_latch = (byte >> shift) & 0x03; self.attr_latch = (byte >> shift) & 0x03;
} }
4 => { 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; let fine_y = (self.vram_addr >> 12) & 0x07;
self.pattern_lo_latch = cart.read_chr(base | ((self.tile_latch as u16) << 4) | fine_y); 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; 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); self.pattern_hi_latch = cart.read_chr(base | ((self.tile_latch as u16) << 4) | fine_y + 8);
} }
7 => { 7 => self.increment_coarse_x(),
// 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();
}
_ => {} _ => {}
} }
} }
@@ -303,22 +307,22 @@ impl Ppu {
} }
fn render_bg_pixel(&mut self, _cart: &mut Cartridge) { fn render_bg_pixel(&mut self, _cart: &mut Cartridge) {
// fine-X offset: the first `x` dots render from the pre-fetched tile let bit_mux = 0x8000u16 >> self.x;
let pattern_lo_bit = (self.bg_pattern_lo >> 15) & 1; let pattern_lo_bit = (self.bg_pattern_lo & bit_mux) != 0;
let pattern_hi_bit = (self.bg_pattern_hi >> 15) & 1; let pattern_hi_bit = (self.bg_pattern_hi & bit_mux) != 0;
let attr_lo_bit = (self.bg_attr_lo >> 15) & 1; let attr_lo_bit = (self.bg_attr_lo & bit_mux) != 0;
let attr_hi_bit = (self.bg_attr_hi >> 15) & 1; let attr_hi_bit = (self.bg_attr_hi & bit_mux) != 0;
let pixel = (pattern_hi_bit << 1) | pattern_lo_bit; let pixel = (pattern_hi_bit as u8) << 1 | pattern_lo_bit as u8;
let palette_bits = (attr_hi_bit << 1) | attr_lo_bit; let palette_bits = (attr_hi_bit as u8) << 1 | attr_lo_bit as u8;
let color = if pixel == 0 { let color = if pixel == 0 {
self.palette[0] // "universal background" transparent color self.palette[0]
} else { } 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; self.framebuffer[idx] = color;
} }