Skip to content

Commit

Permalink
More accurate fetcher timings. More accurate visual glitches in Mr. C…
Browse files Browse the repository at this point in the history
…hin's Gourmet Paradise; Turrican.
  • Loading branch information
LIJI32 committed Jul 10, 2024
1 parent 9d4d535 commit 06c6ce7
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 112 deletions.
181 changes: 87 additions & 94 deletions Core/display.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -776,45 +749,62 @@ 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)) {
gb->wx_triggered = false;
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) {
Expand All @@ -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.
Expand All @@ -844,19 +836,13 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb, unsigned *cycles)
gb->fetcher_state++;
break;

case GB_FETCHER_GET_TILE_DATA_LOWER: {
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);
}
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);

/* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */
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 {
Expand All @@ -868,36 +854,38 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb, unsigned *cycles)
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);

}
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);
}
else if (cgb_d_glitch) {
gb->data_for_sel_glitch = vram_read(gb, gb->current_tile * 0x10 + ((y & 7) ^ y_flip) * 2);
}
gb->last_tile_data_address = tile_address + ((y & 7) ^ y_flip) * 2;
}
gb->fetcher_state++;
break;

case GB_FETCHER_GET_TILE_DATA_HIGH: {
case GB_FETCHER_GET_TILE_DATA_LOWER_T2: {
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);
gb->current_tile_data[0] = data_for_tile_sel_glitch(gb, &use_glitched, &cgb_d_glitch);
}
if (!use_glitched) {
gb->current_tile_data[0] = vram_read(gb, gb->last_tile_data_address);

}
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->last_tile_data_address & ~0x1000);
}
}
gb->fetcher_state++;
break;

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 {
Expand All @@ -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) {
Expand All @@ -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++;
Expand All @@ -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;
}
}

Expand Down Expand Up @@ -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;
}
Expand Down
1 change: 1 addition & 0 deletions Core/gb.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
26 changes: 8 additions & 18 deletions Core/sm83_cpu.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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;
}

Expand Down

0 comments on commit 06c6ce7

Please sign in to comment.