From 06c6ce7d657a8576e556061d9a27deabf029f6cd Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 11 Jul 2024 00:21:04 +0300 Subject: [PATCH] More accurate fetcher timings. More accurate visual glitches in Mr. Chin's Gourmet Paradise; Turrican. --- Core/display.c | 199 +++++++++++++++++++++++------------------------- Core/gb.h | 1 + Core/sm83_cpu.c | 26 ++----- 3 files changed, 105 insertions(+), 121 deletions(-) diff --git a/Core/display.c b/Core/display.c index 94a036e..757818f 100644 --- a/Core/display.c +++ b/Core/display.c @@ -550,33 +550,6 @@ static void add_object_from_index(GB_gameboy_t *gb, unsigned index) } } -static uint8_t data_for_tile_sel_glitch(GB_gameboy_t *gb, bool *should_use, bool *cgb_d_glitch) -{ - /* - Based on Matt Currie's research here: - https://github.com/mattcurrie/mealybug-tearoom-tests/blob/master/the-comprehensive-game-boy-ppu-documentation.md#tile_sel-bit-4 - */ - *should_use = true; - *cgb_d_glitch = false; - - if (gb->io_registers[GB_IO_LCDC] & GB_LCDC_TILE_SEL) { - if (gb->model != GB_MODEL_CGB_D) { - *should_use = !(gb->current_tile & 0x80); - return gb->current_tile; - } - *cgb_d_glitch = true; - *should_use = false; - gb->io_registers[GB_IO_LCDC] &= ~GB_LCDC_TILE_SEL; - if (gb->fetcher_state == 3) { - *cgb_d_glitch = true; - return 0; - } - return 0; - } - return gb->data_for_sel_glitch; -} - - static void render_pixel_if_possible(GB_gameboy_t *gb) { const GB_fifo_item_t *fifo_item = NULL; @@ -776,29 +749,48 @@ static inline uint8_t vram_read(GB_gameboy_t *gb, uint16_t addr) return gb->vram[addr]; } +typedef enum { + /* VRAM reads take 2 T-cycles. In read address is determined in the first + cycle, and the read actually completes in the second cycle.*/ + GB_FETCHER_GET_TILE_T1, + GB_FETCHER_GET_TILE_T2, + GB_FETCHER_GET_TILE_DATA_LOWER_T1, + GB_FETCHER_GET_TILE_DATA_LOWER_T2, + GB_FETCHER_GET_TILE_DATA_HIGH_T1, + GB_FETCHER_GET_TILE_DATA_HIGH_T2, + GB_FETCHER_PUSH, +} fetcher_step_t; + +static uint8_t data_for_tile_sel_glitch(GB_gameboy_t *gb, bool *should_use, bool *cgb_d_glitch) +{ + /* + Based on Matt Currie's research here: + https://github.com/mattcurrie/mealybug-tearoom-tests/blob/master/the-comprehensive-game-boy-ppu-documentation.md#tile_sel-bit-4 + */ + *should_use = true; + *cgb_d_glitch = false; + + if (gb->last_tileset) { + if (gb->model != GB_MODEL_CGB_D) { + *should_use = !(gb->current_tile & 0x80); + return gb->current_tile; + } + *cgb_d_glitch = true; + *should_use = false; + gb->last_tile_data_address &= ~0x1000; + if (gb->fetcher_state == GB_FETCHER_GET_TILE_DATA_LOWER_T2) { + *cgb_d_glitch = true; + return 0; + } + return 0; + } + return gb->data_for_sel_glitch; +} + static void advance_fetcher_state_machine(GB_gameboy_t *gb, unsigned *cycles) { - typedef enum { - GB_FETCHER_GET_TILE, - GB_FETCHER_GET_TILE_DATA_LOWER, - GB_FETCHER_GET_TILE_DATA_HIGH, - GB_FETCHER_PUSH, - GB_FETCHER_SLEEP, - } fetcher_step_t; - - static const fetcher_step_t fetcher_state_machine [8] = { - GB_FETCHER_SLEEP, - GB_FETCHER_GET_TILE, - GB_FETCHER_SLEEP, - GB_FETCHER_GET_TILE_DATA_LOWER, - GB_FETCHER_SLEEP, - GB_FETCHER_GET_TILE_DATA_HIGH, - GB_FETCHER_PUSH, - GB_FETCHER_PUSH, - }; - switch (fetcher_state_machine[gb->fetcher_state & 7]) { - case GB_FETCHER_GET_TILE: { - dma_sync(gb, cycles); + switch ((fetcher_step_t)gb->fetcher_state) { + case GB_FETCHER_GET_TILE_T1: { uint16_t map = 0x1800; if (!(gb->io_registers[GB_IO_LCDC] & GB_LCDC_WIN_ENABLE)) { @@ -806,15 +798,13 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb, unsigned *cycles) gb->wx166_glitch = false; } - /* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */ - if (gb->io_registers[GB_IO_LCDC] & GB_LCDC_BG_MAP && !gb->wx_triggered) { + if (gb->io_registers[GB_IO_LCDC] & GB_LCDC_BG_MAP && !gb->wx_triggered) { map = 0x1C00; } else if (gb->io_registers[GB_IO_LCDC] & GB_LCDC_WIN_MAP && gb->wx_triggered) { map = 0x1C00; } - /* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */ uint8_t y = fetcher_y(gb); uint8_t x = 0; if (gb->wx_triggered) { @@ -824,16 +814,18 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb, unsigned *cycles) x = gb->io_registers[GB_IO_SCX] >> 3; } else { - /* TODO: There is some CGB timing error around here. - Adjusting SCX by 7 or less shouldn't have an effect on a CGB, - but SameBoy is affected by a change of both 7 and 6 (but not less). */ - x = ((gb->io_registers[GB_IO_SCX] + gb->position_in_line + 8) / 8) & 0x1F; + x = ((gb->io_registers[GB_IO_SCX] + gb->position_in_line + 8 - (GB_is_cgb(gb) && !gb->during_object_fetch)) / 8) & 0x1F; } if (gb->model > GB_MODEL_CGB_C) { /* This value is cached on the CGB-D and newer, so it cannot be used to mix tiles together */ gb->fetcher_y = y; } gb->last_tile_index_address = map + x + y / 8 * 32; + } + gb->fetcher_state++; + break; + case GB_FETCHER_GET_TILE_T2: { + dma_sync(gb, cycles); gb->current_tile = vram_read(gb, gb->last_tile_index_address); if (GB_is_cgb(gb)) { /* The CGB actually accesses both the tile index AND the attributes in the same T-cycle. @@ -844,60 +836,56 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb, unsigned *cycles) gb->fetcher_state++; break; - case GB_FETCHER_GET_TILE_DATA_LOWER: { + case GB_FETCHER_GET_TILE_DATA_LOWER_T1: { + uint8_t y_flip = 0; + uint16_t tile_address = 0; + uint8_t y = gb->model > GB_MODEL_CGB_C ? gb->fetcher_y : fetcher_y(gb); + + gb->last_tileset = gb->io_registers[GB_IO_LCDC] & GB_LCDC_TILE_SEL; + if (gb->last_tileset) { + tile_address = gb->current_tile * 0x10; + } + else { + tile_address = (int8_t)gb->current_tile * 0x10 + 0x1000; + } + if (gb->current_tile_attributes & 8) { + tile_address += 0x2000; + } + if (gb->current_tile_attributes & 0x40) { + y_flip = 0x7; + } + gb->last_tile_data_address = tile_address + ((y & 7) ^ y_flip) * 2; + } + gb->fetcher_state++; + break; + + case GB_FETCHER_GET_TILE_DATA_LOWER_T2: { dma_sync(gb, cycles); bool use_glitched = false; bool cgb_d_glitch = false; if (gb->tile_sel_glitch) { gb->current_tile_data[0] = data_for_tile_sel_glitch(gb, &use_glitched, &cgb_d_glitch); } - uint8_t y_flip = 0; - uint16_t tile_address = 0; - uint8_t y = gb->model > GB_MODEL_CGB_C ? gb->fetcher_y : fetcher_y(gb); - - /* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */ - if (gb->io_registers[GB_IO_LCDC] & GB_LCDC_TILE_SEL) { - tile_address = gb->current_tile * 0x10; - } - else { - tile_address = (int8_t)gb->current_tile * 0x10 + 0x1000; - } - if (gb->current_tile_attributes & 8) { - tile_address += 0x2000; - } - if (gb->current_tile_attributes & 0x40) { - y_flip = 0x7; - } if (!use_glitched) { - gb->current_tile_data[0] = - vram_read(gb, tile_address + ((y & 7) ^ y_flip) * 2); + gb->current_tile_data[0] = vram_read(gb, gb->last_tile_data_address); } - if ((gb->io_registers[GB_IO_LCDC] & GB_LCDC_TILE_SEL) && gb->tile_sel_glitch) { - gb->data_for_sel_glitch = - vram_read(gb, tile_address + ((y & 7) ^ y_flip) * 2); + if (gb->last_tileset && gb->tile_sel_glitch) { + gb->data_for_sel_glitch = vram_read(gb, gb->last_tile_data_address); } else if (cgb_d_glitch) { - gb->data_for_sel_glitch = vram_read(gb, gb->current_tile * 0x10 + ((y & 7) ^ y_flip) * 2); + gb->data_for_sel_glitch = vram_read(gb, gb->last_tile_data_address & ~0x1000); } } gb->fetcher_state++; break; - case GB_FETCHER_GET_TILE_DATA_HIGH: { - dma_sync(gb, cycles); - /* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */ - - bool use_glitched = false; - bool cgb_d_glitch = false; - if (gb->tile_sel_glitch) { - gb->current_tile_data[1] = data_for_tile_sel_glitch(gb, &use_glitched, &cgb_d_glitch); - } - + case GB_FETCHER_GET_TILE_DATA_HIGH_T1: { uint16_t tile_address = 0; uint8_t y = gb->model > GB_MODEL_CGB_C ? gb->fetcher_y : fetcher_y(gb); - if (gb->io_registers[GB_IO_LCDC] & GB_LCDC_TILE_SEL) { + gb->last_tileset = gb->io_registers[GB_IO_LCDC] & GB_LCDC_TILE_SEL; + if (gb->last_tileset) { tile_address = gb->current_tile * 0x10; } else { @@ -910,18 +898,31 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb, unsigned *cycles) if (gb->current_tile_attributes & 0x40) { y_flip = 0x7; } - gb->last_tile_data_address = tile_address + ((y & 7) ^ y_flip) * 2 + 1 - cgb_d_glitch; + gb->last_tile_data_address = tile_address + ((y & 7) ^ y_flip) * 2 + 1; + } + gb->fetcher_state++; + break; + + case GB_FETCHER_GET_TILE_DATA_HIGH_T2: { + dma_sync(gb, cycles); + bool use_glitched = false; + bool cgb_d_glitch = false; + if (gb->tile_sel_glitch) { + gb->current_tile_data[1] = data_for_tile_sel_glitch(gb, &use_glitched, &cgb_d_glitch); + if (cgb_d_glitch) { + gb->last_tile_data_address--; + } + } if (!use_glitched) { gb->data_for_sel_glitch = gb->current_tile_data[1] = vram_read(gb, gb->last_tile_data_address); } - if ((gb->io_registers[GB_IO_LCDC] & GB_LCDC_TILE_SEL) && gb->tile_sel_glitch) { + if (gb->last_tileset && gb->tile_sel_glitch) { gb->data_for_sel_glitch = vram_read(gb, gb->last_tile_data_address); } else if (cgb_d_glitch) { - gb->data_for_sel_glitch = vram_read(gb, gb->current_tile * 0x10 + ((y & 7) ^ y_flip) * 2 + 1); - + gb->data_for_sel_glitch = vram_read(gb, (gb->tile_sel_glitch & ~0x1000) + 1); } } if (gb->wx_triggered) { @@ -930,6 +931,7 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb, unsigned *cycles) } // fallthrough + default: case GB_FETCHER_PUSH: { if (gb->fetcher_state < 7) { gb->fetcher_state++; @@ -956,14 +958,6 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb, unsigned *cycles) gb->fetcher_state = 0; } break; - - case GB_FETCHER_SLEEP: - { - gb->fetcher_state++; - } - break; - - nodefault; } } @@ -1000,7 +994,6 @@ static inline void get_tile_data(const GB_gameboy_t *gb, uint8_t tile_x, uint8_t uint16_t tile_address = 0; - /* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */ if (gb->io_registers[GB_IO_LCDC] & GB_LCDC_TILE_SEL) { tile_address = current_tile * 0x10; } diff --git a/Core/gb.h b/Core/gb.h index 65372dd..89c845f 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -647,6 +647,7 @@ struct GB_gameboy_internal_s { bool insert_bg_pixel; uint8_t cpu_vram_bus; uint32_t frame_parity_ticks; + bool last_tileset; ) GB_SECTION(accessory, diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index 5272cc6..fa5f632 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -271,11 +271,10 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value) uint8_t old = gb->io_registers[GB_IO_LCDC]; if ((~value & old) & GB_LCDC_TILE_SEL) { GB_advance_cycles(gb, gb->pending_cycles); - GB_write_memory(gb, addr, value ^ GB_LCDC_TILE_SEL); // Write with the old TILE_SET first + GB_write_memory(gb, addr, value); gb->tile_sel_glitch = true; GB_advance_cycles(gb, 1); gb->tile_sel_glitch = false; - GB_write_memory(gb, addr, value); gb->pending_cycles = 3; } else { @@ -288,23 +287,14 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value) case GB_CONFLICT_LCDC_CGB_DOUBLE: { uint8_t old = gb->io_registers[GB_IO_LCDC]; // TODO: Verify for CGB ≤ C for BG_EN and OBJ_EN. + GB_advance_cycles(gb, gb->pending_cycles - 2); + GB_write_memory(gb, addr, (value & ~(GB_LCDC_BG_EN | GB_LCDC_ENABLE)) | (old & (GB_LCDC_BG_EN | GB_LCDC_ENABLE))); // TODO: This condition is different from single speed mode. Why? What about odd modes? - if ((value ^ old) & GB_LCDC_TILE_SEL) { - GB_advance_cycles(gb, gb->pending_cycles - 2); - GB_write_memory(gb, addr, (value & (GB_LCDC_OBJ_EN | GB_LCDC_BG_EN)) | (old & ~(GB_LCDC_OBJ_EN | GB_LCDC_BG_EN))); - gb->tile_sel_glitch = true; - GB_advance_cycles(gb, 2); - gb->tile_sel_glitch = false; - GB_write_memory(gb, addr, value); - gb->pending_cycles = 4; - } - else { - GB_advance_cycles(gb, gb->pending_cycles - 2); - GB_write_memory(gb, addr, (value & (GB_LCDC_OBJ_EN | GB_LCDC_BG_EN)) | (old & ~(GB_LCDC_OBJ_EN | GB_LCDC_BG_EN))); - GB_advance_cycles(gb, 2); - GB_write_memory(gb, addr, value); - gb->pending_cycles = 4; - } + gb->tile_sel_glitch = ((value ^ old) & GB_LCDC_TILE_SEL); + GB_advance_cycles(gb, 2); + gb->tile_sel_glitch = false; + GB_write_memory(gb, addr, value); + gb->pending_cycles = 4; break; }