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 +++++++++++++++++++++++------------------------- lib/nes/ppu.hh | 57 ++++++---- lib/nes/util/address.hh | 8 +- 3 files changed, 183 insertions(+), 178 deletions(-) (limited to 'lib/nes') 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 diff --git a/lib/nes/ppu.hh b/lib/nes/ppu.hh index 4f15d37..8e521c5 100644 --- a/lib/nes/ppu.hh +++ b/lib/nes/ppu.hh @@ -16,7 +16,7 @@ namespace nes static constexpr auto vram_size = std::size_t{ 0x800 }; static constexpr auto oam_size = std::size_t{ 0x100 }; static constexpr auto palette_buffer_size = std::size_t{ 0x20 }; - static constexpr auto status_mask = std::uint8_t{ 0b11100000 }; + static constexpr auto tile_size = unsigned{ 8 }; // Writes to some registers are ignored until this clock cycle. static constexpr auto boot_up_cycles = cycle_count::from_ppu(29658); @@ -36,6 +36,8 @@ namespace nes std::uint8_t value{}; + color_index() = default; + explicit color_index(std::uint8_t const value) : value{ value } { @@ -67,6 +69,8 @@ namespace nes std::uint8_t attributes{}; std::uint8_t x{}; + explicit sprite() = default; + explicit sprite(std::uint8_t const y, std::uint8_t const tile_index, std::uint8_t const attributes, std::uint8_t const x) : y{ y } , tile_index{ tile_index } @@ -76,9 +80,14 @@ namespace nes } }; + struct tile_row + { + color_index colors[tile_size]{}; + }; + struct evaluated_sprite { - std::uint32_t pattern{ 0 }; + tile_row pattern; std::uint8_t x{ 0 }; bool is_in_front{ false }; bool is_sprite_zero{ false }; @@ -112,6 +121,9 @@ namespace nes auto set_sprite_zero_hit(bool const v) -> void { value = (value & ~0b01000000) | (v ? 0b01000000 : 0); } auto set_vblank(bool const v) -> void { value = (value & ~0b10000000) | (v ? 0b10000000 : 0); } + // Set the remaining (unused) bits based on the given value. + auto set_remaining(std::uint8_t const v) -> void { value = (value & ~0b00011111) | (v & 0b00011111); } + std::uint8_t value{ 0b00000000 }; } status_{}; struct @@ -179,14 +191,18 @@ namespace nes // Scroll position auto get_coarse_x() const -> unsigned { return (value & 0b0000000000011111) >> 0; } auto get_coarse_y() const -> unsigned { return (value & 0b0000001111100000) >> 5; } + auto get_name_table() const -> unsigned { return (value & 0b0000110000000000) >> 10; } auto get_horizontal_name_table() const -> unsigned { return (value & 0b0000010000000000) >> 10; } auto get_vertical_name_table() const -> unsigned { return (value & 0b0000100000000000) >> 11; } + auto get_tile_address() const -> unsigned { return (value & 0b0000111111111111) >> 0; } auto get_fine_y() const -> unsigned { return (value & 0b0111000000000000) >> 12; } auto set_coarse_x(unsigned const v) -> void { value = (value & ~0b0000000000011111) | ((v << 0) & 0b0000000000011111); } auto set_coarse_y(unsigned const v) -> void { value = (value & ~0b0000001111100000) | ((v << 5) & 0b0000001111100000); } + auto set_name_table(unsigned const v) -> void { value = (value & ~0b0000110000000000) | ((v << 10) & 0b0000110000000000); } auto set_horizontal_name_table(unsigned const v) -> void { value = (value & ~0b0000010000000000) | ((v << 10) & 0b0000010000000000); } auto set_vertical_name_table(unsigned const v) -> void { value = (value & ~0b0000100000000000) | ((v << 11) & 0b0000100000000000); } + auto set_tile_address(unsigned const v) -> void { value = (value & ~0b0000111111111111) | ((v << 0) & 0b0000111111111111); } auto set_fine_y(unsigned const v) -> void { value = (value & ~0b0111000000000000) | ((v << 12) & 0b0111000000000000); } // Address @@ -198,19 +214,23 @@ namespace nes std::uint16_t value{}; } v{}, t{}; // Current and temporary VRAM address and scroll position (15 bits) - std::uint8_t x{}; // Fine X scroll (3 bits) - bool w{}; // Second write toggle (1 bit) + std::uint8_t x{}; // Fine X scroll (3 bits) + bool w{}; // Second write toggle (1 bit) } internal_{}; std::uint8_t oamaddr_{}; // Address accessed by IO registers std::uint8_t ppudata_read_buffer_{}; // Delays PPUDATA reads by one. unsigned scanline_{ 240 }; unsigned scanline_cycle_{ 340 }; bool even_frame_{ true }; - std::uint64_t tile_data_{ 0 }; // Data for the current tile (higher 32 bits) and the next tile (lower 32 bits). - std::uint8_t name_table_byte_{ 0 }; - std::uint8_t attribute_table_byte_{ 0 }; - std::uint8_t low_tile_byte_{ 0 }; - std::uint8_t high_tile_byte_{ 0 }; + tile_row current_background_{}; + tile_row next_background_{}; + struct + { + std::uint8_t tile{ 0 }; + std::uint8_t palette{ 0 }; + std::uint8_t bitplane_0{ 0 }; + std::uint8_t bitplane_1{ 0 }; + } fetch_cycle_{}; // Data populated during the fetch cycle. evaluated_sprite sprites_[8]{}; // Evaluated sprites. unsigned sprite_count_{ 0 }; // Number of evaluated sprites in sprites_. @@ -250,23 +270,20 @@ namespace nes private: auto render_pixel() -> void; - auto fetch_name_table_byte() -> void; - auto fetch_attribute_table_byte() -> void; - auto fetch_low_tile_byte() -> void; - auto fetch_high_tile_byte() -> void; - auto store_tile_data() -> void; - auto increment_x() -> void; - auto increment_y() -> void; - auto copy_x() -> void; - auto copy_y() -> void; auto evaluate_sprites() -> void; - auto fetch_sprite_pattern(sprite, unsigned row) -> std::uint32_t; + auto fetch_sprite_pattern(sprite, unsigned row) -> tile_row; // Helpers - auto sprite_height() const -> unsigned; auto increment_vram() -> void; + auto increment_x() -> void; + auto increment_y() -> void; + auto copy_x() -> void; + auto copy_y() -> void; + auto get_sprite_height() const -> unsigned; auto get_sprite(unsigned) const -> sprite; + auto get_tile_bitplane(unsigned pattern_table, unsigned tile, unsigned row, unsigned bitplane) -> std::uint8_t; + auto get_tile_row(unsigned palette, std::uint8_t bitplane_0, std::uint8_t bitplane_1) const -> tile_row; auto ref_color(color_index) -> color&; auto resolve_color(color) const -> rgb; }; diff --git a/lib/nes/util/address.hh b/lib/nes/util/address.hh index 7987d07..2cce2f0 100644 --- a/lib/nes/util/address.hh +++ b/lib/nes/util/address.hh @@ -51,22 +51,22 @@ namespace nes constexpr auto operator<=(address const a, address const b) -> bool { return a.get_absolute() <= b.get_absolute(); } constexpr auto operator>=(address const a, address const b) -> bool { return a.get_absolute() >= b.get_absolute(); } - constexpr auto operator+(address const a, std::uint16_t const b) -> address + constexpr auto operator+(address const a, unsigned const b) -> address { return address{ static_cast(a.get_absolute() + b) }; } - constexpr auto operator+(std::uint16_t const a, address const b) -> address + constexpr auto operator+(unsigned const a, address const b) -> address { return address{ static_cast(a + b.get_absolute()) }; } - constexpr auto operator-(address const a, std::uint16_t const b) -> address + constexpr auto operator-(address const a, unsigned const b) -> address { return address{ static_cast(a.get_absolute() - b) }; } - constexpr auto operator%(address const a, std::uint16_t const b) -> address + constexpr auto operator%(address const a, unsigned const b) -> address { return address{ static_cast(a.get_absolute() % b) }; } -- cgit v1.3.1