fixed PPU implementation, seems to render correctly
This commit is contained in:
+53
-49
@@ -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;
|
||||
}
|
||||
|
||||
if render_bg && self.scanline < 240 && self.cycle < 256 {
|
||||
self.render_bg_pixel(cart);
|
||||
}
|
||||
|
||||
if render_bg && (self.scanline < 240 || self.scanline == 261) {
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
||||
// Compose + store pixel (visible only, x = cycle - 1)
|
||||
if rendering && self.scanline < 240 && self.cycle >= 1 && self.cycle <= 256 {
|
||||
self.render_bg_pixel(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
|
||||
}
|
||||
|
||||
if self.cycle == 256 && (self.scanline < 240 || self.scanline == 261) {
|
||||
self.increment_vertical(); // dot 256: advance to next scanline's tiles
|
||||
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 == 280 && self.scanline == 261 {
|
||||
self.copy_vertical(); // dot 280 (pre-render): reload vertical bits from t
|
||||
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));
|
||||
}
|
||||
|
||||
match self.cycle % 8 {
|
||||
0 => 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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user