From b8e32e6d539f3ed716d8c1572f5b70dbcb967f1c Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 11 Aug 2024 19:45:58 +0300 Subject: [PATCH] More accurate window emulation, especially in double speed mode --- Core/display.c | 108 +++++++++++++++++++++++++++++++++++++----------- Core/gb.h | 4 ++ Core/memory.c | 18 ++++---- Core/sm83_cpu.c | 2 + 4 files changed, 98 insertions(+), 34 deletions(-) diff --git a/Core/display.c b/Core/display.c index 40a7d4a32..d24728c47 100644 --- a/Core/display.c +++ b/Core/display.c @@ -420,6 +420,21 @@ void GB_set_light_temperature(GB_gameboy_t *gb, double temperature) } } +static void wy_check(GB_gameboy_t *gb) +{ + if (!(gb->io_registers[GB_IO_LCDC] & GB_LCDC_ENABLE)) return; + + uint8_t comparison = gb->current_line; + if ((!GB_is_cgb(gb) || gb->cgb_double_speed) && gb->ly_for_comparison != (uint8_t)-1) { + comparison = gb->ly_for_comparison; + } + + if ((gb->io_registers[GB_IO_LCDC] & GB_LCDC_WIN_ENABLE) && + gb->io_registers[GB_IO_WY] == comparison) { + gb->wy_triggered = true; + } +} + void GB_STAT_update(GB_gameboy_t *gb) { if (!(gb->io_registers[GB_IO_LCDC] & GB_LCDC_ENABLE)) return; @@ -585,7 +600,13 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) // (gb->position_in_line + 16 < 8) is (gb->position_in_line < -8) in unsigned logic if (((uint8_t)(gb->position_in_line + 16) < 8)) { - if ((gb->position_in_line & 7) == (gb->io_registers[GB_IO_SCX] & 7)) { + if (gb->position_in_line == (uint8_t)-17) { + gb->position_in_line = -16; + } + else if ((gb->position_in_line & 7) == (gb->io_registers[GB_IO_SCX] & 7)) { + gb->position_in_line = -8; + } + else if (gb->window_is_being_fetched && (gb->position_in_line & 7) == 6 && (gb->io_registers[GB_IO_SCX] & 7) == 7) { // TODO: Why does this happen? gb->position_in_line = -8; } else if (gb->position_in_line == (uint8_t) -9) { @@ -597,6 +618,8 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) } } + gb->window_is_being_fetched = false; + /* Drop pixels for scrollings */ if (gb->position_in_line >= 160 || (gb->disable_rendering && !gb->sgb)) { gb->position_in_line++; @@ -689,7 +712,6 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) gb->position_in_line++; gb->lcd_x++; - gb->window_is_being_fetched = false; } static inline void dma_sync(GB_gameboy_t *gb, unsigned *cycles) @@ -1371,7 +1393,7 @@ static inline uint16_t mode3_batching_length(GB_gameboy_t *gb) if (gb->wx_triggered) return 0; if (gb->wy_triggered) { if (gb->io_registers[GB_IO_LCDC] & GB_LCDC_WIN_ENABLE) { - if ((gb->io_registers[GB_IO_WX] < 8 || gb->io_registers[GB_IO_WX] == 166)) { + if ((gb->io_registers[GB_IO_WX] < 7 || gb->io_registers[GB_IO_WX] == 166 || gb->io_registers[GB_IO_WX] == 167)) { return 0; } } @@ -1425,7 +1447,33 @@ static void update_frame_parity(GB_gameboy_t *gb) */ void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force) { - gb->frame_parity_ticks += cycles; + if (gb->wy_triggered) { + gb->wy_check_scheduled = false; + } + else if (gb->wy_check_scheduled) { + force = true; + unsigned cycles_to_check; + // TODO: When speed-switching while the LCD is on, the modulo might be affected. Odd-modes are going to be fun. + if (gb->cgb_double_speed) { + cycles_to_check = (8 - ((gb->wy_check_modulo + 6) & 7)); + } + else if (GB_is_cgb(gb)) { + cycles_to_check = (8 - ((gb->wy_check_modulo + 0) & 7)); + } + else { + cycles_to_check = (8 - ((gb->wy_check_modulo + 2) & 7)); + } + + if (cycles >= cycles_to_check) { + gb->wy_check_scheduled = false; + GB_display_run(gb, cycles_to_check, true); + wy_check(gb); + if (gb->display_state == 21 && GB_is_cgb(gb) && !gb->cgb_double_speed) { + gb->wy_just_checked = true; + } + cycles -= cycles_to_check; + } + } if (unlikely((gb->io_registers[GB_IO_LCDC] & GB_LCDC_ENABLE) && (signed)(gb->cycles_for_line * 2 + cycles + gb->display_cycles) > LINE_LENGTH * 2)) { unsigned first_batch = (LINE_LENGTH * 2 - gb->cycles_for_line * 2 + gb->display_cycles); @@ -1447,6 +1495,9 @@ void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force) } gb->cycles_since_vblank_callback += cycles / 2; + gb->frame_parity_ticks += cycles; + gb->wy_check_modulo += cycles; + if (cycles < gb->frame_repeat_countdown) { gb->frame_repeat_countdown -= cycles; } @@ -1480,6 +1531,7 @@ void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force) GB_STATE(gb, display, 15); GB_STATE(gb, display, 16); GB_STATE(gb, display, 17); + GB_STATE(gb, display, 19); GB_STATE(gb, display, 20); GB_STATE(gb, display, 21); @@ -1490,6 +1542,7 @@ void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force) GB_STATE(gb, display, 27); GB_STATE(gb, display, 28); GB_STATE(gb, display, 29); + GB_STATE(gb, display, 31); GB_STATE(gb, display, 32); GB_STATE(gb, display, 33); @@ -1505,6 +1558,9 @@ void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force) GB_STATE(gb, display, 43); } + gb->wy_check_modulo = cycles; + gb->wy_just_checked = false; + if (!(gb->io_registers[GB_IO_LCDC] & GB_LCDC_ENABLE)) { while (true) { if (gb->cycles_since_vblank_callback < LCDC_PERIOD) { @@ -1592,6 +1648,7 @@ void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force) } gb->n_visible_objs = gb->orig_n_visible_objs; gb->current_line++; + wy_check(gb); gb->cycles_for_line = 0; if (gb->current_line != LINES) { gb->cycles_for_line = 2; @@ -1616,6 +1673,8 @@ void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force) while (true) { /* Lines 0 - 143 */ for (; gb->current_line < LINES; gb->current_line++) { + wy_check(gb); + if (unlikely(gb->lcd_line_callback)) { gb->lcd_line_callback(gb, gb->current_line); } @@ -1649,6 +1708,8 @@ void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force) gb->mode_for_interrupt = 2; gb->oam_write_blocked = true; gb->ly_for_comparison = gb->current_line; + wy_check(gb); + GB_STAT_update(gb); gb->mode_for_interrupt = -1; GB_STAT_update(gb); @@ -1698,11 +1759,6 @@ void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force) GB_SLEEP(gb, display, 32, 2); mode_3_start: gb->disable_window_pixel_insertion_glitch = false; - /* TODO: Timing seems incorrect, might need an access conflict handling. */ - if ((gb->io_registers[GB_IO_LCDC] & GB_LCDC_WIN_ENABLE) && - gb->io_registers[GB_IO_WY] == gb->current_line) { - gb->wy_triggered = true; - } fifo_clear(&gb->bg_fifo); fifo_clear(&gb->oam_fifo); @@ -1735,8 +1791,11 @@ void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force) has weird artifacts (It appears to activate the window during HBlank, as PPU X is temporarily 160 at that point. The code should be updated to represent this, and this will fix the time travel hack in WX's access conflict code. */ - - if (!gb->wx_triggered && gb->wy_triggered && (gb->io_registers[GB_IO_LCDC] & GB_LCDC_WIN_ENABLE)) { + gb->wx_166_interrupt_glitch = false; + if (unlikely(gb->wy_just_checked)) { + gb->wy_just_checked = false; + } + else if (!gb->wx_triggered && gb->wy_triggered && (gb->io_registers[GB_IO_LCDC] & GB_LCDC_WIN_ENABLE)) { bool should_activate_window = false; if (unlikely(gb->io_registers[GB_IO_WX] == 0)) { if (gb->position_in_line == (uint8_t)-7) { @@ -1749,7 +1808,7 @@ void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force) should_activate_window = true; } } - else if (gb->io_registers[GB_IO_WX] < 166 + GB_is_cgb(gb)) { + else if (gb->io_registers[GB_IO_WX] < 166 + GB_is_cgb(gb)) { // TODO: 166 on the CGB behaves a bit weird if (gb->io_registers[GB_IO_WX] == (uint8_t) (gb->position_in_line + 7)) { should_activate_window = true; } @@ -1770,10 +1829,13 @@ void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force) gb->window_tile_x = 0; fifo_clear(&gb->bg_fifo); /* TODO: Verify fetcher access timings in this case */ - if (gb->io_registers[GB_IO_WX] == 0 && (gb->io_registers[GB_IO_SCX] & 7)) { - gb->cycles_for_line++; + if (gb->io_registers[GB_IO_WX] == 0 && (gb->io_registers[GB_IO_SCX] & 7) && !GB_is_cgb(gb)) { + gb->cycles_for_line += 1; GB_SLEEP(gb, display, 42, 1); } + else if (gb->io_registers[GB_IO_WX] == 166) { + gb->wx_166_interrupt_glitch = true; + } gb->wx_triggered = true; gb->fetcher_state = GB_FETCHER_GET_TILE_T1; gb->window_is_being_fetched = true; @@ -1783,8 +1845,7 @@ void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force) } } - /* TODO: What happens when WX=0?*/ - if (!GB_is_cgb(gb) && gb->wx_triggered && !gb->window_is_being_fetched && + if ((!GB_is_cgb(gb) || gb->io_registers[GB_IO_WX] == 0) && gb->wx_triggered && !gb->window_is_being_fetched && gb->fetcher_state == GB_FETCHER_GET_TILE_T1 && gb->io_registers[GB_IO_WX] == (uint8_t) (gb->position_in_line + 7) && gb->bg_fifo.size == 8) { // Insert a pixel right at the FIFO's end gb->insert_bg_pixel = true; @@ -1804,7 +1865,7 @@ void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force) (gb->io_registers[GB_IO_LCDC] & GB_LCDC_OBJ_EN || GB_is_cgb(gb)) && gb->objects_x[gb->n_visible_objs - 1] == x_for_object_match(gb)) { - while (gb->fetcher_state < 5 || fifo_size(&gb->bg_fifo) == 0) { + while (gb->fetcher_state < GB_FETCHER_GET_TILE_DATA_HIGH_T2 || fifo_size(&gb->bg_fifo) == 0) { advance_fetcher_state_machine(gb, &cycles); gb->cycles_for_line++; GB_SLEEP(gb, display, 27, 1); @@ -1886,6 +1947,10 @@ void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force) gb->cycles_for_line++; GB_SLEEP(gb, display, 21, 1); + if (unlikely(gb->wx_166_interrupt_glitch)) { + gb->mode_for_interrupt = 0; + GB_STAT_update(gb); + } } skip_slow_mode_3: gb->position_in_line = -16; @@ -1978,14 +2043,7 @@ void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force) gb->cycles_for_line = 0; GB_SLEEP(gb, display, 11, LINE_LENGTH - cycles_for_line - 2); } - /* - TODO: Verify double speed timing - TODO: Timing differs on a DMG - */ - if ((gb->io_registers[GB_IO_LCDC] & GB_LCDC_WIN_ENABLE) && - (gb->io_registers[GB_IO_WY] == gb->current_line)) { - gb->wy_triggered = true; - } + gb->cycles_for_line = 0; GB_SLEEP(gb, display, 31, 2); if (gb->current_line != LINES - 1) { diff --git a/Core/gb.h b/Core/gb.h index c5fd801e1..06e79522d 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -650,6 +650,10 @@ struct GB_gameboy_internal_s { bool last_tileset; bool cgb_wx_glitch; bool line_has_fractional_scrolling; + uint8_t wy_check_modulo; + bool wy_check_scheduled; + bool wy_just_checked; + bool wx_166_interrupt_glitch; ) GB_SECTION(accessory, diff --git a/Core/memory.c b/Core/memory.c index b6690ed58..c55b6c6a2 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -1394,14 +1394,12 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) /* Hardware registers */ switch (addr & 0xFF) { - case GB_IO_WY: - if (value == gb->current_line) { - gb->wy_triggered = true; - } + case GB_IO_WX: gb->io_registers[addr & 0xFF] = value; GB_update_wx_glitch(gb); break; + case GB_IO_IF: case GB_IO_SCX: case GB_IO_SCY: @@ -1425,8 +1423,12 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) } return; - case GB_IO_LYC: + case GB_IO_WY: + gb->io_registers[addr & 0xFF] = value; + gb->wy_check_scheduled = true; + return; + case GB_IO_LYC: /* TODO: Probably completely wrong in double speed mode */ /* TODO: This hack is disgusting */ @@ -1440,7 +1442,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) /* These are the states when LY changes, let the display routine call GB_STAT_update for use so it correctly handles T-cycle accurate LYC writes */ - if (!GB_is_cgb(gb) || ( + if (!GB_is_cgb(gb) || ( gb->display_state != 35 && gb->display_state != 26 && gb->display_state != 15 && @@ -1523,9 +1525,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) } } gb->io_registers[GB_IO_LCDC] = value; - if (!(value & GB_LCDC_WIN_ENABLE)) { - gb->wx_triggered = false; - } + gb->wy_check_scheduled = true; return; case GB_IO_STAT: diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index d64a93828..de2540328 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -32,6 +32,7 @@ static const conflict_t cgb_conflict_map[0x80] = { [GB_IO_LCDC] = GB_CONFLICT_LCDC_CGB, [GB_IO_IF] = GB_CONFLICT_WRITE_CPU, [GB_IO_LYC] = GB_CONFLICT_WRITE_CPU, + [GB_IO_WY] = GB_CONFLICT_READ_OLD, [GB_IO_STAT] = GB_CONFLICT_STAT_CGB, [GB_IO_BGP] = GB_CONFLICT_PALETTE_CGB, [GB_IO_OBP0] = GB_CONFLICT_PALETTE_CGB, @@ -44,6 +45,7 @@ static const conflict_t cgb_double_conflict_map[0x80] = { [GB_IO_LCDC] = GB_CONFLICT_LCDC_CGB_DOUBLE, [GB_IO_IF] = GB_CONFLICT_WRITE_CPU, [GB_IO_LYC] = GB_CONFLICT_READ_OLD, + [GB_IO_WY] = GB_CONFLICT_READ_OLD, [GB_IO_STAT] = GB_CONFLICT_STAT_CGB_DOUBLE, [GB_IO_NR10] = GB_CONFLICT_NR10_CGB_DOUBLE, [GB_IO_SCX] = GB_CONFLICT_SCX_CGB_DOUBLE,