More accurate window emulation, especially in double speed mode

This commit is contained in:
Lior Halphon
2024-08-11 19:45:58 +03:00
parent d5c6ed9510
commit b8e32e6d53
4 changed files with 98 additions and 34 deletions

View File

@@ -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;
@@ -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) {
@@ -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++;
@@ -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)
@@ -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;
}
}
@@ -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);
@@ -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;
}
@@ -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);
@@ -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);
@@ -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) {
@@ -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;
@@ -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);
}
@@ -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);
@@ -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);
@@ -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) {
@@ -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;
}
@@ -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;
@@ -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;
@@ -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);
@@ -1886,6 +1947,10 @@ abort_fetching_object:
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;
@@ -1978,14 +2043,7 @@ skip_slow_mode_3:
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) {

View File

@@ -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,

View File

@@ -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:
@@ -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 */
@@ -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 &&
@@ -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:

View File

@@ -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,
@@ -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,