srctree

Johan Norberg parent fba7f601 a6851a3b
Skip first cycle on odd frames

* When rendering is enabled, each odd ppu frame is one clock shorter than normal. This is done by skipping the first idle tick on the first visible scanline. See: https://www.nesdev.org/wiki/PPU_frame_timing
core/include/nes/core/ppu_registers.h added: 33, removed: 7, total 26
@@ -168,6 +168,7 @@ struct PpuRegisters {
PpuVram vram_addr;
PpuVram temp_vram_addr;
bool write_toggle;
bool odd_frame;
 
uint8_t name_table_latch;
uint8_t name_table;
 
core/src/ppu.cpp added: 33, removed: 7, total 26
@@ -162,6 +162,11 @@ void Ppu::update_counters() {
cycle() = 0;
if (scanline() == kLastScanlineInFrame) {
scanline() = 0;
registers_->odd_frame = !registers_->odd_frame;
if (registers_->odd_frame &&
registers_->mask.is_rendering_enabled()) {
cycle() = 1;
}
} else {
++scanline();
}
 
core/test/src/ippu_helpers.cpp added: 33, removed: 7, total 26
@@ -10,7 +10,7 @@ bool operator==(const PpuRegisters &a, const PpuRegisters &b) {
a.mask == b.mask && a.status == b.status && a.oamaddr == b.oamaddr &&
a.fine_x_scroll == b.fine_x_scroll && a.vram_addr == b.vram_addr &&
a.temp_vram_addr == b.temp_vram_addr &&
a.write_toggle == b.write_toggle;
a.write_toggle == b.write_toggle && a.odd_frame == b.odd_frame;
}
 
// Required by gtest to use pascal case.
@@ -20,7 +20,7 @@ void PrintTo(const PpuRegisters &r, std::ostream *os) {
"Cycle: {} Scanline: {} Ctrl: {:#04x} ScrollX: {:#04x} Mask: "
"{:#04x} OamAddr: {:#04x} "
"Status: {:#04x} VramAddr: {:#06x} TmpVramAddr: {:#06x} "
"WriteToggle: {}\n",
"WriteToggle: {} OddFrame: {}\n",
r.cycle,
r.scanline,
r.ctrl,
@@ -30,7 +30,8 @@ void PrintTo(const PpuRegisters &r, std::ostream *os) {
r.status,
r.vram_addr.value(),
r.temp_vram_addr.value(),
r.write_toggle);
r.write_toggle,
r.odd_frame);
}
 
} // namespace n_e_s::core
 
core/test/src/test_ppu.cpp added: 33, removed: 7, total 26
@@ -261,9 +261,11 @@ TEST_F(PpuTest, ignore_oamdata_during_pre_render_scanline) {
registers.mask = PpuMask(0b00011000);
registers.oamaddr = 0x02;
registers.scanline = 261;
registers.odd_frame = true;
expected.mask = registers.mask;
expected.oamaddr = registers.oamaddr;
expected.scanline = 0;
expected.odd_frame = false;
// Two increases when fetching two tiles for next scanline
expected.vram_addr = PpuVram(0x0002);
 
@@ -441,6 +443,21 @@ TEST_F(PpuTest, increment_vram_addr_by_32_after_reading) {
EXPECT_EQ(expected, registers);
}
 
TEST_F(PpuTest, skips_first_cycle_on_odd_frames_when_rendering_is_enabled) {
registers.mask = expected.mask =
PpuMask(0b000'1000); // Enable background rendering
registers.odd_frame = false;
registers.cycle = kCyclesPerScanline - 1u;
registers.scanline = 261; // pre-render scanline
expected.odd_frame = true;
expected.cycle = 1;
expected.scanline = 0;
 
ppu->execute();
 
EXPECT_EQ(expected, registers);
}
 
TEST_F(PpuTest, visible_two_sub_cycles) {
registers.scanline = expected.scanline = 0;
registers.mask = expected.mask =
@@ -637,6 +654,7 @@ TEST_F(PpuTest, pre_render_two_sub_cycles) {
 
TEST_F(PpuTest, pre_render_scanline) {
registers.scanline = 261u; // Start at pre-render
registers.odd_frame = expected.odd_frame = true;
registers.mask = expected.mask =
PpuMask(0b000'1000); // Enable background rendering
 
@@ -720,6 +738,7 @@ TEST_F(PpuTest, pre_render_scanline) {
// Two unused nametable fetches.
expected.scanline = 0;
expected.cycle = 0;
expected.odd_frame = false;
EXPECT_CALL(mmu, read_byte(0x2000 + 2)).Times(2).WillRepeatedly(Return(2));
for (int i = 337; i <= 340; ++i) {
const auto pixel = ppu->execute();