Skip to content

Commit

Permalink
feat: hacky scrolling (vertical)
Browse files Browse the repository at this point in the history
  • Loading branch information
nathanleiby committed Dec 19, 2024
1 parent 48a1978 commit e70eef5
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 88 deletions.
14 changes: 12 additions & 2 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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
Expand Down
134 changes: 68 additions & 66 deletions src/ppu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand All @@ -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
Expand Down
42 changes: 25 additions & 17 deletions src/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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);
Expand All @@ -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);
}
}
}
Expand Down Expand Up @@ -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);
}
Expand All @@ -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);
Expand All @@ -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 {
Expand Down
6 changes: 3 additions & 3 deletions src/rom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down

0 comments on commit e70eef5

Please sign in to comment.