broken PPU imp
This commit is contained in:
+207
-9
@@ -7,8 +7,22 @@ pub struct Ppu {
|
||||
status: u8, // $2002
|
||||
oamaddr: u8, // $2003
|
||||
oam: [u8; 256], // $2004
|
||||
scroll_x: u8, // $2005
|
||||
scroll_y: u8,
|
||||
scanline: u32,
|
||||
cycle: u32,
|
||||
framebuffer: [u8; 256 * 240],
|
||||
nmi_pending: bool,
|
||||
frame_complete: bool,
|
||||
odd_frame: bool,
|
||||
t: u16,
|
||||
x: u8,
|
||||
pattern_lo_latch: u8,
|
||||
pattern_hi_latch: u8,
|
||||
bg_pattern_lo: u16,
|
||||
bg_pattern_hi: u16,
|
||||
bg_attr_lo: u16,
|
||||
bg_attr_hi: u16,
|
||||
tile_latch: u8,
|
||||
attr_latch: u8,
|
||||
write_latch: bool, // shared W toggle for $2005/$2006, reset by $2002 read
|
||||
vram_addr: u16, // 14-bit
|
||||
data_buffer: u8, // $2007 read-buffer
|
||||
@@ -17,10 +31,68 @@ pub struct Ppu {
|
||||
}
|
||||
|
||||
impl Ppu {
|
||||
pub fn tick(&mut self, cart: &mut Cartridge, cycles: u64) {
|
||||
for _ in 0..(cycles * 3) {
|
||||
self.dot(cart);
|
||||
}
|
||||
}
|
||||
|
||||
fn dot(&mut self, cart: &mut Cartridge) {
|
||||
// vblank flag + NMI
|
||||
if self.cycle == 0 && self.scanline == 241 {
|
||||
self.status |= 0x80;
|
||||
self.nmi_pending = true;
|
||||
}
|
||||
// clear vblank
|
||||
if self.cycle == 0 && self.scanline == 261 {
|
||||
self.status &= !0x80;
|
||||
}
|
||||
|
||||
let render_bg = 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) {
|
||||
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;
|
||||
self.scanline += 1;
|
||||
if self.scanline >= 262 {
|
||||
self.scanline = 0;
|
||||
self.frame_complete = true;
|
||||
self.odd_frame = !self.odd_frame;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
ctrl: 0, mask: 0, status: 0, oamaddr: 0,
|
||||
oam: [0; 256], scroll_x: 0, scroll_y: 0,
|
||||
oam: [0; 256],
|
||||
scanline: 0, cycle: 0,
|
||||
framebuffer: [0; 256 * 240],
|
||||
nmi_pending: false, frame_complete: false, odd_frame: false,
|
||||
t: 0, x: 0,
|
||||
pattern_lo_latch: 0, pattern_hi_latch: 0,
|
||||
bg_pattern_lo: 0, bg_pattern_hi: 0,
|
||||
bg_attr_lo: 0, bg_attr_hi: 0,
|
||||
tile_latch: 0, attr_latch: 0,
|
||||
write_latch: false, vram_addr: 0, data_buffer: 0,
|
||||
vram: [0; 0x1000], palette: [0; 32],
|
||||
}
|
||||
@@ -62,16 +134,24 @@ impl Ppu {
|
||||
self.oamaddr = self.oamaddr.wrapping_add(1);
|
||||
}
|
||||
0x2005 => {
|
||||
if !self.write_latch { self.scroll_x = val; } else { self.scroll_y = val; }
|
||||
if !self.write_latch {
|
||||
self.t = (self.t & 0x7FE0) | ((val >> 3) as u16); // coarse X (bits 0-4)
|
||||
self.x = val & 0x07; // fine X
|
||||
} else {
|
||||
self.t = (self.t & 0x0C1F) // keep fine Y + nametable Y + coarse X
|
||||
| (((val & 0xF8) as u16) << 2) // coarse Y -> bits 5-9
|
||||
| ((val & 0x07) as u16) << 12; // fine Y -> bits 12-14
|
||||
}
|
||||
self.write_latch = !self.write_latch;
|
||||
}
|
||||
0x2006 => {
|
||||
if !self.write_latch {
|
||||
self.vram_addr = (self.vram_addr & 0x00FF) | (((val & 0x3F) as u16) << 8);
|
||||
self.t = (self.t & 0x00FF) | (((val & 0x3F) as u16) << 8); // high 6 bits -> bits 8-13
|
||||
} else {
|
||||
self.vram_addr = (self.vram_addr & 0x7F00) | (val as u16);
|
||||
self.t = (self.t & 0x7F00) | (val as u16); // low 8 bits
|
||||
self.vram_addr = self.t; // v = t
|
||||
}
|
||||
self.write_latch = !self.write_latch;
|
||||
self.write_latch = !self.write_latch;
|
||||
}
|
||||
0x2007 => {
|
||||
self.vram_write(cart, self.vram_addr, val);
|
||||
@@ -113,8 +193,8 @@ impl Ppu {
|
||||
fn nametable_offset(&self, cart: &Cartridge, addr: u16) -> usize {
|
||||
let a = addr & 0x2FFF; // fold the $3000-$3EFF mirror into $2000-$2FFF
|
||||
let table = match cart.mirroring() {
|
||||
Mirroring::Horizontal => (a >> 10) & 1,
|
||||
Mirroring::Vertical => (a >> 11) & 1,
|
||||
Mirroring::Horizontal => (a >> 11) & 1,
|
||||
Mirroring::Vertical => (a >> 10) & 1,
|
||||
Mirroring::OneScreenLow => 0,
|
||||
Mirroring::OneScreenHigh => 1,
|
||||
Mirroring::FourScreen => (a >> 10) & 3,
|
||||
@@ -129,6 +209,124 @@ impl Ppu {
|
||||
fn palette_read(&self, addr: u16) -> u8 {
|
||||
self.palette[palette_index(addr)]
|
||||
}
|
||||
|
||||
fn bg_fetch(&mut self, cart: &mut Cartridge) {
|
||||
// shift registers move left every dot
|
||||
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
|
||||
}
|
||||
|
||||
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)),
|
||||
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 & 0x07);
|
||||
let byte = self.nametable_read(cart, addr);
|
||||
// select the 2 bits for this tile's 2x2 block
|
||||
let shift = ((self.vram_addr & 0x20) >> 3) | ((self.vram_addr & 0x01) << 1);
|
||||
self.attr_latch = (byte >> shift) & 0x03;
|
||||
}
|
||||
4 => {
|
||||
let base = ((self.ctrl & 0x10) as u16) << 8; // BG pattern table select
|
||||
let fine_y = (self.vram_addr >> 12) & 0x07;
|
||||
self.pattern_lo_latch = cart.read_chr(base | ((self.tile_latch as u16) << 4) | fine_y);
|
||||
}
|
||||
6 => {
|
||||
let base = ((self.ctrl & 0x10) as u16) << 8;
|
||||
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();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn increment_coarse_x(&mut self) {
|
||||
if self.vram_addr & 0x001F == 0x001F { // coarse X wraps 31 -> 0
|
||||
self.vram_addr &= !0x001F;
|
||||
self.vram_addr ^= 0x0400; // flip nametable X bit
|
||||
} else {
|
||||
self.vram_addr += 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn increment_vertical(&mut self) {
|
||||
if self.vram_addr & 0x7000 != 0x7000 {
|
||||
self.vram_addr += 0x1000; // fine Y++
|
||||
} else {
|
||||
self.vram_addr &= !0x7000; // fine Y = 0
|
||||
let mut y = (self.vram_addr >> 5) & 0x1F; // coarse Y
|
||||
if y == 29 {
|
||||
y = 0;
|
||||
self.vram_addr ^= 0x0800; // flip nametable Y
|
||||
} else if y == 31 {
|
||||
y = 0;
|
||||
} else {
|
||||
y += 1;
|
||||
}
|
||||
self.vram_addr = (self.vram_addr & !0x03E0) | (y << 5);
|
||||
}
|
||||
}
|
||||
|
||||
fn copy_horizontal(&mut self) { // t's coarse X + nametable X -> v
|
||||
self.vram_addr = (self.vram_addr & !0x041F) | (self.t & 0x041F);
|
||||
}
|
||||
|
||||
fn copy_vertical(&mut self) { // t's fine Y + coarse Y + nametable Y -> v
|
||||
self.vram_addr = (self.vram_addr & !0x7BE0) | (self.t & 0x7BE0);
|
||||
}
|
||||
|
||||
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 pixel = (pattern_hi_bit << 1) | pattern_lo_bit;
|
||||
let palette_bits = (attr_hi_bit << 1) | attr_lo_bit;
|
||||
|
||||
let color = if pixel == 0 {
|
||||
self.palette[0] // "universal background" transparent color
|
||||
} else {
|
||||
self.palette[(palette_bits * 4 + pixel) as usize] // background palette
|
||||
};
|
||||
|
||||
let idx = (self.scanline as usize) * 256 + (self.cycle as usize);
|
||||
self.framebuffer[idx] = color;
|
||||
}
|
||||
|
||||
pub fn frame_done(&self) -> bool { self.frame_complete }
|
||||
pub fn take_nmi(&mut self) -> bool { let n = self.nmi_pending; self.nmi_pending = false; n }
|
||||
pub fn framebuffer(&self) -> &[u8] { &self.framebuffer }
|
||||
pub fn begin_frame(&mut self) { self.frame_complete = false; self.scanline = 0; self.cycle = 0; }
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
fn palette_index(addr: u16) -> usize {
|
||||
|
||||
Reference in New Issue
Block a user