Implement CGB-mode TILE_SEL mixing, fixes cgb-acid-hell and m3_lcdc_tile_sel_change2, closes #308

This commit is contained in:
Lior Halphon 2020-11-20 16:24:16 +02:00
parent c36bdc22f6
commit 7fdc58a07e
3 changed files with 96 additions and 12 deletions

View File

@ -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) {
@ -1113,6 +1156,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
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;

View File

@ -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;
);
};

View File

@ -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;
}
}