Emulate LCD-PPU horizontal desync on DMGs

This commit is contained in:
Lior Halphon 2020-03-06 14:41:13 +02:00
parent 4d2f56c42d
commit c6f9d05124
3 changed files with 61 additions and 44 deletions

View File

@ -459,10 +459,10 @@ static void render_pixel_if_possible(GB_gameboy_t *gb)
uint32_t *dest = NULL; uint32_t *dest = NULL;
if (!gb->sgb) { if (!gb->sgb) {
if (gb->border_mode != GB_BORDER_ALWAYS) { if (gb->border_mode != GB_BORDER_ALWAYS) {
dest = gb->screen + gb->position_in_line + gb->current_line * WIDTH; dest = gb->screen + gb->lcd_x + gb->current_line * WIDTH;
} }
else { else {
dest = gb->screen + gb->position_in_line + gb->current_line * BORDERED_WIDTH + (BORDERED_WIDTH - WIDTH) / 2 + (BORDERED_HEIGHT - LINES) / 2 * BORDERED_WIDTH; dest = gb->screen + gb->lcd_x + gb->current_line * BORDERED_WIDTH + (BORDERED_WIDTH - WIDTH) / 2 + (BORDERED_HEIGHT - LINES) / 2 * BORDERED_WIDTH;
} }
} }
@ -476,7 +476,7 @@ static void render_pixel_if_possible(GB_gameboy_t *gb)
} }
if (gb->sgb) { if (gb->sgb) {
if (gb->current_lcd_line < LINES) { if (gb->current_lcd_line < LINES) {
gb->sgb->screen_buffer[gb->position_in_line + gb->current_lcd_line * WIDTH] = gb->stopped? 0 : pixel; gb->sgb->screen_buffer[gb->lcd_x + gb->current_lcd_line * WIDTH] = gb->stopped? 0 : pixel;
} }
} }
else if (gb->model & GB_MODEL_NO_SFC_BIT) { else if (gb->model & GB_MODEL_NO_SFC_BIT) {
@ -500,7 +500,7 @@ static void render_pixel_if_possible(GB_gameboy_t *gb)
} }
if (gb->sgb) { if (gb->sgb) {
if (gb->current_lcd_line < LINES) { if (gb->current_lcd_line < LINES) {
gb->sgb->screen_buffer[gb->position_in_line + gb->current_lcd_line * WIDTH] = gb->stopped? 0 : pixel; gb->sgb->screen_buffer[gb->lcd_x + gb->current_lcd_line * WIDTH] = gb->stopped? 0 : pixel;
} }
} }
else if (gb->model & GB_MODEL_NO_SFC_BIT) { else if (gb->model & GB_MODEL_NO_SFC_BIT) {
@ -524,6 +524,7 @@ static void render_pixel_if_possible(GB_gameboy_t *gb)
} }
gb->position_in_line++; gb->position_in_line++;
gb->lcd_x++;
gb->window_is_being_fetched = false; gb->window_is_being_fetched = false;
} }
@ -937,6 +938,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
fifo_push_bg_row(&gb->bg_fifo, 0, 0, 0, false, false); fifo_push_bg_row(&gb->bg_fifo, 0, 0, 0, false, false);
/* Todo: find out actual access time of SCX */ /* Todo: find out actual access time of SCX */
gb->position_in_line = - (gb->io_registers[GB_IO_SCX] & 7) - 8; gb->position_in_line = - (gb->io_registers[GB_IO_SCX] & 7) - 8;
gb->lcd_x = 0;
gb->fetcher_x = 0; gb->fetcher_x = 0;
gb->extra_penalty_for_sprite_at_0 = (gb->io_registers[GB_IO_SCX] & 7); gb->extra_penalty_for_sprite_at_0 = (gb->io_registers[GB_IO_SCX] & 7);
@ -967,10 +969,19 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
} }
} }
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)) {
if (gb->io_registers[GB_IO_WX] == (uint8_t) (gb->position_in_line + 7) || if (gb->io_registers[GB_IO_WX] == (uint8_t) (gb->position_in_line + 7)) {
(gb->io_registers[GB_IO_WX] == (uint8_t) (gb->position_in_line + 6) && !gb->wx_just_changed)) {
should_activate_window = true; should_activate_window = true;
} }
else if (gb->io_registers[GB_IO_WX] == (uint8_t) (gb->position_in_line + 6) && !gb->wx_just_changed) {
should_activate_window = true;
/* LCD-PPU horizontal desync! It only appears to happen on DMGs, but not all of them.
This doesn't seem to be CPU revision dependent, but most revisions */
if ((gb->model & GB_MODEL_FAMILY_MASK) == GB_MODEL_DMG_FAMILY && !GB_is_sgb(gb)) {
if (gb->lcd_x > 0) {
gb->lcd_x--;
}
}
}
} }
if (should_activate_window) { if (should_activate_window) {
@ -1096,6 +1107,21 @@ abort_fetching_object:
gb->cycles_for_line++; gb->cycles_for_line++;
GB_SLEEP(gb, display, 21, 1); GB_SLEEP(gb, display, 21, 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;
if (gb->border_mode != GB_BORDER_ALWAYS) {
dest = gb->screen + gb->lcd_x + gb->current_line * WIDTH;
}
else {
dest = gb->screen + gb->lcd_x + gb->current_line * BORDERED_WIDTH + (BORDERED_WIDTH - WIDTH) / 2 + (BORDERED_HEIGHT - LINES) / 2 * BORDERED_WIDTH;
}
*dest = gb->background_palettes_rgb[0];
gb->lcd_x++;
}
/* TODO: Verify timing */ /* TODO: Verify timing */
if (!GB_is_cgb(gb) && gb->wy_triggered && (gb->io_registers[GB_IO_LCDC] & 0x20) && gb->io_registers[GB_IO_WX] == 166) { if (!GB_is_cgb(gb) && gb->wy_triggered && (gb->io_registers[GB_IO_LCDC] & 0x20) && gb->io_registers[GB_IO_WX] == 166) {
gb->wx166_glitch = true; gb->wx166_glitch = true;

View File

@ -523,6 +523,7 @@ struct GB_gameboy_internal_s {
uint16_t object_low_line_address; uint16_t object_low_line_address;
bool wy_triggered; bool wy_triggered;
uint8_t window_tile_x; uint8_t window_tile_x;
uint8_t lcd_x; // The LCD can go out of sync since the push signal is skipped in some cases.
); );
/* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */ /* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */

View File

@ -180,6 +180,32 @@ static bool verify_state_compatibility(GB_gameboy_t *gb, GB_gameboy_t *save)
return true; return true;
} }
static void sanitize_state(GB_gameboy_t *gb)
{
if (gb->cartridge_type->has_rumble && gb->rumble_callback) {
gb->rumble_callback(gb, gb->rumble_state);
}
for (unsigned i = 0; i < 32; i++) {
GB_palette_changed(gb, false, i * 2);
GB_palette_changed(gb, true, i * 2);
}
gb->bg_fifo.read_end &= 0xF;
gb->bg_fifo.write_end &= 0xF;
gb->oam_fifo.read_end &= 0xF;
gb->oam_fifo.write_end &= 0xF;
gb->object_low_line_address &= gb->vram_size & ~1;
gb->fetcher_x &= 0x1f;
if (gb->lcd_x > gb->position_in_line) {
gb->lcd_x = gb->position_in_line;
}
if (gb->object_priority == GB_OBJECT_PRIORITY_UNDEFINED) {
gb->object_priority = gb->cgb_mode? GB_OBJECT_PRIORITY_INDEX : GB_OBJECT_PRIORITY_X;
}
}
#define READ_SECTION(gb, f, section) read_section(f, GB_GET_SECTION(gb, section), GB_SECTION_SIZE(section)) #define READ_SECTION(gb, f, section) read_section(f, GB_GET_SECTION(gb, section), GB_SECTION_SIZE(section))
int GB_load_state(GB_gameboy_t *gb, const char *path) int GB_load_state(GB_gameboy_t *gb, const char *path)
@ -252,25 +278,7 @@ int GB_load_state(GB_gameboy_t *gb, const char *path)
errno = 0; errno = 0;
if (gb->cartridge_type->has_rumble && gb->rumble_callback) { sanitize_state(gb);
gb->rumble_callback(gb, gb->rumble_state);
}
for (unsigned i = 0; i < 32; i++) {
GB_palette_changed(gb, false, i * 2);
GB_palette_changed(gb, true, i * 2);
}
gb->bg_fifo.read_end &= 0xF;
gb->bg_fifo.write_end &= 0xF;
gb->oam_fifo.read_end &= 0xF;
gb->oam_fifo.write_end &= 0xF;
gb->object_low_line_address &= gb->vram_size & ~1;
gb->fetcher_x &= 0x1f;
if (gb->object_priority == GB_OBJECT_PRIORITY_UNDEFINED) {
gb->object_priority = gb->cgb_mode? GB_OBJECT_PRIORITY_INDEX : GB_OBJECT_PRIORITY_X;
}
error: error:
fclose(f); fclose(f);
@ -363,25 +371,7 @@ int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t le
memcpy(gb, &save, sizeof(save)); memcpy(gb, &save, sizeof(save));
if (gb->cartridge_type->has_rumble && gb->rumble_callback) { sanitize_state(gb);
gb->rumble_callback(gb, gb->rumble_state);
}
for (unsigned i = 0; i < 32; i++) {
GB_palette_changed(gb, false, i * 2);
GB_palette_changed(gb, true, i * 2);
}
gb->bg_fifo.read_end &= 0xF;
gb->bg_fifo.write_end &= 0xF;
gb->oam_fifo.read_end &= 0xF;
gb->oam_fifo.write_end &= 0xF;
gb->object_low_line_address &= gb->vram_size & ~1;
gb->fetcher_x &= 0x1f;
if (gb->object_priority == GB_OBJECT_PRIORITY_UNDEFINED) {
gb->object_priority = gb->cgb_mode? GB_OBJECT_PRIORITY_INDEX : GB_OBJECT_PRIORITY_X;
}
return 0; return 0;
} }