Skip to content

Commit

Permalink
More accurate window emulation, especially in double speed mode
Browse files Browse the repository at this point in the history
  • Loading branch information
LIJI32 committed Aug 13, 2024
1 parent d5c6ed9 commit b8e32e6
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 34 deletions.
108 changes: 83 additions & 25 deletions Core/display.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand All @@ -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++;
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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;
}
}
Expand Down Expand Up @@ -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);
Expand All @@ -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;
}
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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) {
Expand Down Expand Up @@ -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;
Expand All @@ -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);
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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) {
Expand All @@ -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;
}
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down
4 changes: 4 additions & 0 deletions Core/gb.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
18 changes: 9 additions & 9 deletions Core/memory.c
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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 */
Expand All @@ -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 &&
Expand Down Expand Up @@ -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:
Expand Down
2 changes: 2 additions & 0 deletions Core/sm83_cpu.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down

0 comments on commit b8e32e6

Please sign in to comment.