From 7fdc58a07ef48b155c28d97f4004f81de7a82c36 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 20 Nov 2020 16:24:16 +0200 Subject: [PATCH] Implement CGB-mode TILE_SEL mixing, fixes cgb-acid-hell and m3_lcdc_tile_sel_change2, closes #308 --- Core/display.c | 76 +++++++++++++++++++++++++++++++++++++++++-------- Core/gb.h | 2 ++ Core/sm83_cpu.c | 30 +++++++++++++++++++ 3 files changed, 96 insertions(+), 12 deletions(-) diff --git a/Core/display.c b/Core/display.c index 2eb8c42..b57317b 100644 --- a/Core/display.c +++ b/Core/display.c @@ -430,6 +430,23 @@ 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) +{ + /* + 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; + if (gb->io_registers[GB_IO_LCDC] & 0x10) { + *should_use = !(gb->current_tile & 0x80); + /* if (gb->model != GB_MODEL_CGB_D) */ return gb->current_tile; + // TODO: CGB D behaves differently + } + return gb->data_for_sel_glitch; +} + + static void render_pixel_if_possible(GB_gameboy_t *gb) { GB_fifo_item_t *fifo_item = NULL; @@ -621,6 +638,10 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) break; case GB_FETCHER_GET_TILE_DATA_LOWER: { + bool use_glitched = false; + if (gb->tile_sel_glitch) { + gb->current_tile_data[0] = data_for_tile_sel_glitch(gb, &use_glitched); + } 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); @@ -638,20 +659,32 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) if (gb->current_tile_attributes & 0x40) { y_flip = 0x7; } - gb->current_tile_data[0] = + if (!use_glitched) { + gb->current_tile_data[0] = + gb->vram[tile_address + ((y & 7) ^ y_flip) * 2]; + if (gb->vram_ppu_blocked) { + gb->current_tile_data[0] = 0xFF; + } + } + else { + gb->data_for_sel_glitch = gb->vram[tile_address + ((y & 7) ^ y_flip) * 2]; - if (gb->vram_ppu_blocked) { - gb->current_tile_data[0] = 0xFF; + if (gb->vram_ppu_blocked) { + gb->data_for_sel_glitch = 0xFF; + } } } gb->fetcher_state++; break; case GB_FETCHER_GET_TILE_DATA_HIGH: { - /* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. - Additionally, on CGB-D and newer mixing two tiles by changing the tileset - bit mid-fetching causes a glitched mixing of the two, in comparison to the - more logical DMG version. */ + /* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */ + + bool use_glitched = false; + if (gb->tile_sel_glitch) { + gb->current_tile_data[1] = data_for_tile_sel_glitch(gb, &use_glitched); + } + uint16_t tile_address = 0; uint8_t y = gb->model > GB_MODEL_CGB_C ? gb->fetcher_y : fetcher_y(gb); @@ -669,10 +702,20 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) y_flip = 0x7; } gb->last_tile_data_address = tile_address + ((y & 7) ^ y_flip) * 2 + 1; - gb->current_tile_data[1] = - gb->vram[gb->last_tile_data_address]; - if (gb->vram_ppu_blocked) { - gb->current_tile_data[1] = 0xFF; + if (!use_glitched) { + gb->current_tile_data[1] = + gb->vram[gb->last_tile_data_address]; + if (gb->vram_ppu_blocked) { + gb->current_tile_data[1] = 0xFF; + } + } + else { + if ((gb->io_registers[GB_IO_LCDC] & 0x10) && gb->tile_sel_glitch) { + gb->data_for_sel_glitch = gb->vram[gb->last_tile_data_address]; + if (gb->vram_ppu_blocked) { + gb->data_for_sel_glitch = 0xFF; + } + } } } if (gb->wx_triggered) { @@ -1112,7 +1155,8 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) object->flags & 0x80, gb->object_priority == GB_OBJECT_PRIORITY_INDEX? gb->visible_objs[gb->n_visible_objs - 1] : 0, object->flags & 0x20); - + + gb->data_for_sel_glitch = gb->vram_ppu_blocked? 0xFF : gb->vram[line_address + 1]; gb->n_visible_objs--; } @@ -1128,6 +1172,14 @@ abort_fetching_object: GB_SLEEP(gb, display, 21, 1); } + /* TODO: Verify */ + if (gb->fetcher_state == 4 || gb->fetcher_state == 5) { + gb->data_for_sel_glitch = gb->current_tile_data[0]; + } + else { + gb->data_for_sel_glitch = gb->current_tile_data[1]; + } + while (gb->lcd_x != 160 && !gb->disable_rendering && gb->screen && !gb->sgb) { /* Oh no! The PPU and LCD desynced! Fill the rest of the line whith white. */ uint32_t *dest = NULL; diff --git a/Core/gb.h b/Core/gb.h index c42af4b..0d0aaa9 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -548,6 +548,7 @@ struct GB_gameboy_internal_s { uint16_t last_tile_data_address; uint16_t last_tile_index_address; bool cgb_repeated_a_frame; + uint8_t data_for_sel_glitch; ); /* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */ @@ -692,6 +693,7 @@ struct GB_gameboy_internal_s { /* Temporary state */ bool wx_just_changed; + bool tile_sel_glitch; ); }; diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index ffc5997..7107ed1 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -21,10 +21,12 @@ typedef enum { GB_CONFLICT_DMG_LCDC, GB_CONFLICT_SGB_LCDC, GB_CONFLICT_WX, + GB_CONFLICT_CGB_LCDC, } GB_conflict_t; /* Todo: How does double speed mode affect these? */ static const GB_conflict_t cgb_conflict_map[0x80] = { + [GB_IO_LCDC] = GB_CONFLICT_CGB_LCDC, [GB_IO_IF] = GB_CONFLICT_WRITE_CPU, [GB_IO_LYC] = GB_CONFLICT_WRITE_CPU, [GB_IO_STAT] = GB_CONFLICT_STAT_CGB, @@ -241,6 +243,34 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value) gb->wx_just_changed = false; gb->pending_cycles = 3; return; + + case GB_CONFLICT_CGB_LCDC: + if ((value ^ gb->io_registers[GB_IO_LCDC]) & 0x10) { + // Todo: This is difference is because my timing is off in one of the models + if (gb->model > GB_MODEL_CGB_C) { + GB_advance_cycles(gb, gb->pending_cycles); + 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 { + GB_advance_cycles(gb, gb->pending_cycles - 1); + gb->tile_sel_glitch = true; + GB_advance_cycles(gb, 1); + gb->tile_sel_glitch = false; + GB_write_memory(gb, addr, value); + gb->pending_cycles = 4; + } + } + else { + GB_advance_cycles(gb, gb->pending_cycles); + GB_write_memory(gb, addr, value); + gb->pending_cycles = 4; + } + return; + } }