Emulate LCD-PPU horizontal desync on DMGs
This commit is contained in:
parent
4d2f56c42d
commit
c6f9d05124
@ -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;
|
||||||
|
@ -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 */
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user