From d1f4b65eafe9c41ecca7bbe8026ec0a693c1ecda Mon Sep 17 00:00:00 2001 From: Hannes Schulze Date: Sun, 26 Jan 2025 22:01:11 +0100 Subject: refactor ppu --- lib/nes/ppu.cc | 296 +++++++++++++++++++++++++++------------------------------ 1 file changed, 142 insertions(+), 154 deletions(-) (limited to 'lib/nes/ppu.cc') diff --git a/lib/nes/ppu.cc b/lib/nes/ppu.cc index e37a303..0772d80 100644 --- a/lib/nes/ppu.cc +++ b/lib/nes/ppu.cc @@ -25,7 +25,7 @@ namespace nes auto ppu::step() -> void { // See https://www.nesdev.org/wiki/PPU_rendering - // Based on: https://github.com/fogleman/nes/blob/master/nes/ppu.go + // Inspired by https://github.com/fogleman/nes/blob/master/nes/ppu.go auto const enable_rendering = mask_.get_enable_background() || mask_.get_enable_sprites(); @@ -65,26 +65,52 @@ namespace nes if (render_line && fetch_cycle) { - tile_data_ <<= 4; + // Fetch cycle for the background. switch (scanline_cycle_ % 8) { case 1: - fetch_name_table_byte(); + { + // Load the tile pattern for the background from the name table. + fetch_cycle_.tile = read8(address{ 0x2000 } + internal_.v.get_tile_address()); break; + } case 3: - fetch_attribute_table_byte(); + { + // Load the tile palette for the background from the attribute table. + auto const addr = + address{ 0x23C0 } + + (internal_.v.get_name_table() * 0x400u) + + ((internal_.v.get_coarse_y() & 0b11100u) << 1) + + ((internal_.v.get_coarse_x() & 0b11100u) >> 2); + auto const shift = + ((internal_.v.get_coarse_y() & 0b00010u) << 1) | + ((internal_.v.get_coarse_x() & 0b00010u) << 0); + fetch_cycle_.palette = static_cast((read8(addr) >> shift) & 0b11); break; + } case 5: - fetch_low_tile_byte(); + { + // Load bitplane 0 for the background tile's pattern. + fetch_cycle_.bitplane_0 = get_tile_bitplane( + control_.get_background_pattern_table(), fetch_cycle_.tile, internal_.v.get_fine_y(), 0); break; + } case 7: - fetch_high_tile_byte(); + { + // Load bitplane 1 for the background tile's pattern. + fetch_cycle_.bitplane_1 = get_tile_bitplane( + control_.get_background_pattern_table(), fetch_cycle_.tile, internal_.v.get_fine_y(), 1); break; + } case 0: - store_tile_data(); - break; - default: + { + // Fetch cycle done -> build the tile row for the background. + current_background_ = next_background_; + next_background_ = get_tile_row( + fetch_cycle_.palette, fetch_cycle_.bitplane_0, fetch_cycle_.bitplane_1); break; + } + default: break; } } @@ -149,9 +175,7 @@ namespace nes auto background_color = color_index{ 0 }; if (mask_.get_enable_background()) { - // TODO - auto const tile_data = static_cast(tile_data_ >> 32); - background_color = color_index{ static_cast((tile_data >> ((7 - internal_.x) * 4)) & 0xF) }; + background_color = current_background_.colors[internal_.x + x % tile_size]; background_color.set_foreground(false); } @@ -163,9 +187,8 @@ namespace nes { auto const s = sprites_[i]; auto const offset = static_cast(x) - static_cast(s.x); - if (offset < 0 || offset > 7) { continue; } - // TODO - auto const color = color_index{ static_cast((s.pattern >> ((7 - offset) * 4)) & 0xF) }; + if (offset < 0 || static_cast(offset) >= tile_size) { continue; } + auto const color = s.pattern.colors[offset]; if (color.get_color() == 0) { continue; } foreground = s; @@ -178,8 +201,8 @@ namespace nes auto has_background = background_color.get_color() != 0; auto has_foreground = foreground_color.get_color() != 0; - if (x < 8 && !mask_.get_show_background_start()) { has_background = false; } - if (x < 8 && !mask_.get_show_sprites_start()) { has_foreground = false; } + if (x < tile_size && !mask_.get_show_background_start()) { has_background = false; } + if (x < tile_size && !mask_.get_show_sprites_start()) { has_foreground = false; } auto color = color_index{ 0 }; if (!has_background && has_foreground) @@ -203,104 +226,9 @@ namespace nes display_.set(x, y, resolve_color(ref_color(color))); } - auto ppu::fetch_name_table_byte() -> void - { - // TODO - auto const addr = static_cast(0x2000 | (internal_.v.value & 0x0FFF)); - name_table_byte_ = read8(address{ addr }); - } - - auto ppu::fetch_attribute_table_byte() -> void - { - // TODO - auto const v = internal_.v.value; - auto const addr = static_cast(0x23C0 | (v & 0x0C00) | ((v >> 4) & 0x38) | ((v >> 2) & 0x07)); - auto const shift = ((v >> 4) & 4) | (v & 2); - attribute_table_byte_ = static_cast(((read8(address{ addr }) >> shift) & 3) << 2); - } - - auto ppu::fetch_low_tile_byte() -> void - { - auto const addr = static_cast(0x1000 * control_.get_background_pattern_table() + 16 * name_table_byte_ + internal_.v.get_fine_y()); - low_tile_byte_ = read8(address{ addr }); - } - - auto ppu::fetch_high_tile_byte() -> void - { - auto const addr = static_cast(0x1000 * control_.get_background_pattern_table() + 16 * name_table_byte_ + internal_.v.get_fine_y()); - high_tile_byte_ = read8(address{ addr } + 8); - } - - auto ppu::store_tile_data() -> void - { - // TODO - auto data = std::uint32_t{ 0 }; - for (auto i = unsigned{ 0 }; i < 8; ++i) - { - auto const p1 = (low_tile_byte_ & 0x80u) >> 7; - auto const p2 = (high_tile_byte_ & 0x80u) >> 6; - low_tile_byte_ <<= 1; - high_tile_byte_ <<= 1; - data <<= 4; - data |= attribute_table_byte_ | p1 | p2; - } - tile_data_ |= data; - } - - auto ppu::increment_x() -> void - { - if (internal_.v.get_coarse_x() == 0x1F) - { - internal_.v.set_coarse_x(0); - internal_.v.set_horizontal_name_table(1 ^ internal_.v.get_horizontal_name_table()); - } - else - { - internal_.v.set_coarse_x(internal_.v.get_coarse_x() + 1); - } - } - - auto ppu::increment_y() -> void - { - if (internal_.v.get_fine_y() == 7) - { - internal_.v.set_fine_y(0); - if (internal_.v.get_coarse_y() == 29) - { - internal_.v.set_coarse_y(0); - internal_.v.set_vertical_name_table(1 ^ internal_.v.get_vertical_name_table()); - } - else if (internal_.v.get_coarse_y() == 31) - { - internal_.v.set_coarse_y(0); - // Nametable not switched - } - else - { - internal_.v.set_coarse_y(internal_.v.get_coarse_y() + 1); - } - } - else - { - internal_.v.set_fine_y(internal_.v.get_fine_y() + 1); - } - } - - auto ppu::copy_x() -> void - { - // TODO - internal_.v.value = (internal_.v.value & 0xFBE0) | (internal_.t.value & 0x041F); - } - - auto ppu::copy_y() -> void - { - // TODO - internal_.v.value = (internal_.v.value & 0x841F) | (internal_.t.value & 0x7BE0); - } - auto ppu::evaluate_sprites() -> void { - auto const height = sprite_height(); + auto const height = get_sprite_height(); sprite_count_ = 0; for (auto i = unsigned{ 0 }; i < 64; ++i) { @@ -324,52 +252,39 @@ namespace nes } } - auto ppu::fetch_sprite_pattern(sprite const s, unsigned row) -> std::uint32_t + auto ppu::fetch_sprite_pattern(sprite const s, unsigned row) -> tile_row { - if (s.get_flip_vertical()) { row = sprite_height() - 1 - row; } - address addr; + if (s.get_flip_vertical()) { row = get_sprite_height() - 1 - row; } + unsigned tile, pattern_table; if (control_.get_large_sprites()) { - auto tile = s.get_top_tile(); - if (row >= 8) + tile = s.get_top_tile(); + if (row >= tile_size) { tile += 1; - row -= 8; + row -= tile_size; } - addr = address{ static_cast(0x1000u * s.get_pattern_table() + 16u * tile + row) }; + pattern_table = s.get_pattern_table(); } else { - addr = address{ static_cast(0x1000u * control_.get_sprite_pattern_table() + 16u * s.tile_index + row) }; + tile = s.tile_index; + pattern_table = control_.get_sprite_pattern_table(); } - auto const a = s.get_palette() << 2; - auto low_tile_byte = read8(addr); - auto high_tile_byte = read8(addr + 8); - auto data = std::uint32_t{ 0 }; - for (auto j = unsigned{ 0 }; j < 8; ++j) + auto const bitplane_0 = get_tile_bitplane(pattern_table, tile, row, 0); + auto const bitplane_1 = get_tile_bitplane(pattern_table, tile, row, 1); + auto res = get_tile_row(s.get_palette(), bitplane_0, bitplane_1); + + if (s.get_flip_horizontal()) { - // TODO - std::uint8_t p1, p2; - if (s.get_flip_horizontal()) + for (auto i = unsigned{ 0 }; i < tile_size / 2; ++i) { - p1 = static_cast((low_tile_byte & 1) << 0); - p2 = static_cast((high_tile_byte & 1) << 1); - low_tile_byte >>= 1; - high_tile_byte >>= 1; + std::swap(res.colors[i], res.colors[tile_size - 1 - i]); } - else - { - p1 = static_cast((low_tile_byte & 0x80) >> 7); - p2 = static_cast((high_tile_byte & 0x80) >> 6); - low_tile_byte <<= 1; - high_tile_byte <<= 1; - } - data <<= 4; - data |= a | p1 | p2; } - return data; + return res; } // ----------------------------------------------------------------------------------------------------------------- @@ -425,15 +340,15 @@ namespace nes auto ppu::read_ppustatus() -> std::uint8_t { - auto const base = latch_ & ~status_mask; - auto const res = static_cast(base | (status_.value & status_mask)); - NES_DEBUG_LOG(ppu, "PPUSTATUS -> {:#2x}", res); + auto res = status_; + res.set_remaining(latch_); + NES_DEBUG_LOG(ppu, "PPUSTATUS -> {:#2x}", res.value); status_.set_vblank(false); internal_.w = false; - write_latch(res); - return res; + write_latch(res.value); + return res.value; } auto ppu::read_oamdata() -> std::uint8_t @@ -481,8 +396,7 @@ namespace nes { NES_DEBUG_LOG(ppu, "PPUCTRL <- {:#2x}", value); control_.value = value; - // TODO: https://www.nesdev.org/wiki/PPU_registers#PPUCTRL - internal_.t.value = static_cast((internal_.t.value & 0xF3FF) | ((value & 0x03) << 10)); + internal_.t.set_name_table(control_.get_base_name_table()); if (control_.get_vblank_nmi() && status_.get_vblank()) { cpu_.trigger_nmi(); } } } @@ -584,14 +498,66 @@ namespace nes // Helpers // ----------------------------------------------------------------------------------------------------------------- - auto ppu::sprite_height() const -> unsigned + auto ppu::increment_vram() -> void { - return control_.get_large_sprites() ? 16 : 8; + internal_.v.value += control_.get_vram_row_increment() ? 32 : 1; } - auto ppu::increment_vram() -> void + auto ppu::increment_x() -> void { - internal_.v.value += control_.get_vram_row_increment() ? 32 : 1; + if (internal_.v.get_coarse_x() == 31) + { + internal_.v.set_coarse_x(0); + internal_.v.set_horizontal_name_table(1 ^ internal_.v.get_horizontal_name_table()); + } + else + { + internal_.v.set_coarse_x(internal_.v.get_coarse_x() + 1); + } + } + + auto ppu::increment_y() -> void + { + if (internal_.v.get_fine_y() == 7) + { + internal_.v.set_fine_y(0); + if (internal_.v.get_coarse_y() == 29) + { + internal_.v.set_coarse_y(0); + internal_.v.set_vertical_name_table(1 ^ internal_.v.get_vertical_name_table()); + } + else if (internal_.v.get_coarse_y() == 31) + { + internal_.v.set_coarse_y(0); + // Nametable not switched + } + else + { + internal_.v.set_coarse_y(internal_.v.get_coarse_y() + 1); + } + } + else + { + internal_.v.set_fine_y(internal_.v.get_fine_y() + 1); + } + } + + auto ppu::copy_x() -> void + { + internal_.v.set_coarse_x(internal_.t.get_coarse_x()); + internal_.v.set_horizontal_name_table(internal_.t.get_horizontal_name_table()); + } + + auto ppu::copy_y() -> void + { + internal_.v.set_coarse_y(internal_.t.get_coarse_y()); + internal_.v.set_fine_y(internal_.t.get_fine_y()); + internal_.v.set_vertical_name_table(internal_.t.get_vertical_name_table()); + } + + auto ppu::get_sprite_height() const -> unsigned + { + return tile_size * (control_.get_large_sprites() ? 2 : 1); } auto ppu::get_sprite(unsigned const i) const -> sprite @@ -599,6 +565,28 @@ namespace nes return sprite{ oam_[i * 4 + 0], oam_[i * 4 + 1], oam_[i * 4 + 2], oam_[i * 4 + 3] }; } + auto ppu::get_tile_bitplane( + unsigned const pattern_table, unsigned const tile, unsigned const row, unsigned const bitplane) -> std::uint8_t + { + auto const addr = static_cast(0x1000 * pattern_table + 0x10 * tile + row); + return read8(address{ addr } + (bitplane * 8u)); + } + + auto ppu::get_tile_row(unsigned const palette, std::uint8_t bitplane_0, std::uint8_t bitplane_1) const -> tile_row + { + auto res = tile_row{}; + for (auto i = unsigned{ 0 }; i < tile_size; ++i) + { + auto const bit_0 = (bitplane_0 & 0b10000000u) >> 7; + auto const bit_1 = (bitplane_1 & 0b10000000u) >> 7; + bitplane_0 <<= 1; + bitplane_1 <<= 1; + res.colors[i].set_palette(palette); + res.colors[i].set_color((bit_1 << 1) | (bit_0 << 0)); + } + return res; + } + auto ppu::ref_color(color_index index) -> color& { // See https://www.nesdev.org/wiki/PPU_palettes -- cgit v1.3.1