From e70eef50727cf3b7f0e0e5efaf4f943df8a31550 Mon Sep 17 00:00:00 2001 From: Nathan Leiby Date: Thu, 19 Dec 2024 16:43:28 -0500 Subject: [PATCH] feat: hacky scrolling (vertical) --- TODO.md | 14 +++++- src/ppu.rs | 134 +++++++++++++++++++++++++------------------------- src/render.rs | 42 +++++++++------- src/rom.rs | 6 +-- 4 files changed, 108 insertions(+), 88 deletions(-) diff --git a/TODO.md b/TODO.md index dc7391b..dc7e8ad 100644 --- a/TODO.md +++ b/TODO.md @@ -1,6 +1,14 @@ -- [ ] Add scrolling support -- [..] Add tests cases for PPU registers +- [ ] Bug: gamepad input not detected in Mario - [ ] Add support for 2 gamepads +- [..] Add scrolling support + - [x] horizontal scrolling (vertical mirroring) + - [ ] vertical scrolling (horizontal mirroring) -- working on it via Galaga +- [ ] Implement mappers + - [ ] iNES Mapper 1 = MMC1 + - SNROM - e.g. [Metroid](https://nescartdb.com/profile/view/224/metroid), [Final Fantasy](https://nescartdb.com/profile/view/154/final-fantasy) + - [ ] iNES Mapper 2 + - UNROM - [Mega Man](https://nescartdb.com/profile/view/608/mega-man) +- [..] Add tests cases for PPU registers - [ ] Separate the core from the specific hardware (screen, input) - [ ] Try replacing screen and input with macroquad - [ ] Integrate code coverage into CI. (maybe add a repo tag in README) @@ -25,6 +33,8 @@ - log the details - highlight which pattern is being used - show state of CPU (same idea as "trace") + - inspiration: + - https://www.reddit.com/r/EmuDev/comments/1hh4gr6/my_nes_emulator_debugger/ - [ ] More NES Test roms - Try running more NES Test roms, maybe they can help now that i have some graphics? - Lots of the PPU test rom links here are broken.. https://www.nesdev.org/wiki/Emulator_tests diff --git a/src/ppu.rs b/src/ppu.rs index 8e773c0..0b3940c 100644 --- a/src/ppu.rs +++ b/src/ppu.rs @@ -156,38 +156,8 @@ impl Ppu { PaletteIdx(palette_idx) } - pub fn draw_background(&self, frame: &mut Frame) { - let bank = self.get_background_pattern_bank(); - let pattern_table = - &self.chr_rom[bank * PATTERN_TABLE_SIZE..(bank + 1) * PATTERN_TABLE_SIZE]; - - // TODO: Investigate... - // Selecting a nametable here causes a temporarily black screen - // at at the start of pacman (vs nametable = 0). - // However, with nametable = 0 there's another bug of wrong colors at start. - // Both resolve to correct screen in 1-2 seconds. - let which_nametable = self - .registers - .control - .intersection(ControlRegister::NAMETABLE) - .bits() as usize; - assert!(which_nametable <= 3); - let nt_start = which_nametable * 0x400; - - let rows = 30; - let cols = 32; - for y in 0..rows { - for x in 0..cols { - let offset = y * cols + x; - let tile_n = self.vram[self.mirror_vram_addr((nt_start + offset) as u16) as usize]; - let bgp_idx = self.palette_for_bg_tile((x, y), nt_start); - let palette = self.lookup_palette(bgp_idx); - frame.draw_bg_tile(pattern_table, tile_n as usize, (x, y), 0, palette); - } - } - } - - /// Draws the entire possible background of 2 frames, but it gets filtered within, which you can then filter by applying a windowed viewport + /// Draws the entire possible background of 2 frames, but it gets filtered within + /// draw_bg_tile to only pixels on the actual screen pub fn draw_scrollable_background(&self, frame: &mut Frame) { let bank = self.get_background_pattern_bank(); let pattern_table = @@ -202,42 +172,74 @@ impl Ppu { assert!(which_nametable <= 3); let nt_start = which_nametable * 0x400; - // self.registers.control. - - // Determine which nametable is on the left - let nts = if nt_start == 0 { - [0, 0x400] - } else { - [0x400, 0x800] - }; - - let x_scroll: usize = self.registers.scroll.x_scroll as usize; - let y_scroll: usize = self.registers.scroll.y_scroll as usize; - // TODO - // if self.registers.control.contains(ControlRegister::X_SCROLL) { - // x_scroll += 256; - // } - - let rows = 30; - let cols = 32; - for tile_y in 0..rows { - for (nt_idx, &nt_start) in nts.iter().enumerate() { - for tile_x in 0..cols { - let offset = tile_y * cols + tile_x; - let tile_n = - self.vram[self.mirror_vram_addr((nt_start + offset) as u16) as usize]; - let bgp_idx = self.palette_for_bg_tile((tile_x, tile_y), nt_start); - let palette = self.lookup_palette(bgp_idx); - frame.draw_bg_tile( - pattern_table, - tile_n as usize, - (tile_x + cols * (nt_idx), tile_y), - x_scroll, - palette, - ); + let TILE_ROWS = 30; + let TILE_COLS = 32; + match self.mirroring { + Mirroring::Vertical => { + let x_scroll: usize = self.registers.scroll.x_scroll as usize; + // TODO: explore 8th bit of scroll + // if self.registers.control.contains(ControlRegister::X_SCROLL) { + // x_scroll += 256; + // } + + // Determine which nametable is on the left + let nts = if nt_start == 0 { + [0, 0x400] + } else { + [0x400, 0x800] + }; + + for tile_y in 0..TILE_ROWS { + for (nt_idx, &nt_start) in nts.iter().enumerate() { + for tile_x in 0..TILE_COLS { + let offset = tile_y * TILE_COLS + tile_x; + let tile_n = self.vram + [self.mirror_vram_addr((nt_start + offset) as u16) as usize]; + let bgp_idx = self.palette_for_bg_tile((tile_x, tile_y), nt_start); + let palette = self.lookup_palette(bgp_idx); + frame.draw_bg_tile( + pattern_table, + tile_n as usize, + (tile_x + TILE_COLS * nt_idx, tile_y), + x_scroll, + 0, + palette, + ); + } + } } } - } + Mirroring::Horizontal => { + let y_scroll: usize = self.registers.scroll.y_scroll as usize; + // Determine which nametable is above + let nts = if nt_start == 0 { + [0, 0x800] + } else { + [0x800, 0] + }; + + for tile_y in 0..TILE_ROWS { + for (nt_idx, &nt_start) in nts.iter().enumerate() { + for tile_x in 0..TILE_COLS { + let offset = tile_y * TILE_COLS + tile_x; + let tile_n = self.vram + [self.mirror_vram_addr((nt_start + offset) as u16) as usize]; + let bgp_idx = self.palette_for_bg_tile((tile_x, tile_y), nt_start); + let palette = self.lookup_palette(bgp_idx); + frame.draw_bg_tile( + pattern_table, + tile_n as usize, + (tile_x, tile_y + TILE_ROWS * nt_idx), + 0, + y_scroll, + palette, + ); + } + } + } + } + _ => todo!("other mirroring"), + }; } /// Determine which CHR ROM bank (Pattern Table) is used for background tiles diff --git a/src/render.rs b/src/render.rs index 52e0f13..4833ec6 100644 --- a/src/render.rs +++ b/src/render.rs @@ -5,6 +5,7 @@ use crate::{ /// Debug flags const DEBUG_DRAW_SPRITE_BOUNDING_BOX: bool = false; +// const DEBUG_DRAW_SPRITE_BOUNDING_BOX: bool = true; /// Size of a single 8x8 Tile (in bytes). /// 2 bytes for each row of 8 pixels. @@ -70,7 +71,7 @@ impl Frame { tile_n: usize, pos: (usize, usize), x_scroll: usize, - // y_scroll: usize, + y_scroll: usize, palette: [u8; 4], ) { let tile = get_tile(pattern_table, tile_n); @@ -79,13 +80,14 @@ impl Frame { for (col, &palette_idx) in row_data.iter().enumerate() { let color = SYSTEM_PALETTE[palette[palette_idx as usize] as usize]; let left_x = (tile_x * TILE_SIZE_PIXELS) + col; - // only draw pixels within the screen width - if x_scroll <= left_x && left_x - x_scroll <= 256 { - self.set_pixel( - (tile_x * TILE_SIZE_PIXELS) + col - x_scroll, - (tile_y * TILE_SIZE_PIXELS) + row, - color, - ); + let top_y = (tile_y * TILE_SIZE_PIXELS) + row; + // only draw pixels within the screen's viewport + if x_scroll <= left_x + && left_x - x_scroll <= Frame::WIDTH + && y_scroll <= top_y + && top_y - y_scroll <= Frame::HEIGHT + { + self.set_pixel(left_x - x_scroll, top_y - y_scroll, color); } } } @@ -127,19 +129,25 @@ impl Frame { if DEBUG_DRAW_SPRITE_BOUNDING_BOX { // Debug sprite by drawing a bounding box - if col == 0 - || row == 0 - || col == TILE_SIZE_PIXELS - 1 - || row == TILE_SIZE_PIXELS - 1 - { + let horiz_edge = row == 0 || row == TILE_SIZE_PIXELS - 1; + let vert_edge = col == 0 || col == TILE_SIZE_PIXELS - 1; + if horiz_edge || vert_edge { // default = red // flip v = yellow // flip h = purple // flip v+h = white let color = ( 255, - if sprite.flip_vertical { 255 } else { 0 }, - if sprite.flip_horizontal { 255 } else { 0 }, + if sprite.flip_vertical && vert_edge { + 255 + } else { + 0 + }, + if sprite.flip_horizontal && horiz_edge { + 255 + } else { + 0 + }, ); self.set_pixel(x + col, y + row, color); } @@ -163,7 +171,7 @@ mod test { let mut f = Frame::new(); assert_eq!(f.data[0], 0); - f.draw_bg_tile(&pattern_table, tile_n, pos, 0, palette); + f.draw_bg_tile(&pattern_table, tile_n, pos, 0, 0, palette); assert_eq!(f.data[0], 128); assert_eq!(f.data[1], 128); assert_eq!(f.data[2], 128); @@ -182,7 +190,7 @@ mod test { let palette = [65, 65, 65, 3]; // 65 should crash if read, since it's OOB the palette with 64 colors let mut f = Frame::new(); - f.draw_bg_tile(&pattern_table, tile_n, pos, 0, palette); + f.draw_bg_tile(&pattern_table, tile_n, pos, 0, 0, palette); // verify we drew the entire tile in the one selected color for row in 0..8 { diff --git a/src/rom.rs b/src/rom.rs index 566e29b..934305f 100644 --- a/src/rom.rs +++ b/src/rom.rs @@ -55,9 +55,9 @@ impl Rom { let rom_mapper_type = hi + lo; let mapper = match rom_mapper_type { - 0 => Mapper::Zero, - _ => todo!(), - // _ => Mapper::Zero, // TODO: Hack to get tiles viewing.. but should revert to TODO + // 0 => Mapper::Zero, + // _ => todo!(), + _ => Mapper::Zero, // TODO: Hack to get tiles viewing.. but should revert to TODO. Why is mapper saying `0x40` (64) for some games even though it should be 0? }; let mirroring = match (is_four_screen, is_vertical_screen) {