More accurate emulation of STOP mode

This commit is contained in:
Lior Halphon 2020-02-15 19:21:43 +02:00
parent 08eb2f3d98
commit bec09a012c
4 changed files with 71 additions and 28 deletions

View File

@ -136,23 +136,18 @@ static void display_vblank(GB_gameboy_t *gb)
} }
} }
if ((!gb->disable_rendering || gb->sgb) && ((!(gb->io_registers[GB_IO_LCDC] & 0x80) || gb->stopped) || gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON)) { bool is_ppu_stopped = !GB_is_cgb(gb) && gb->stopped && gb->io_registers[GB_IO_LCDC] & 0x80;
if (!gb->disable_rendering && ((!(gb->io_registers[GB_IO_LCDC] & 0x80) || is_ppu_stopped) || gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON)) {
/* LCD is off, set screen to white or black (if LCD is on in stop mode) */ /* LCD is off, set screen to white or black (if LCD is on in stop mode) */
if (gb->sgb) { if (!GB_is_sgb(gb)) {
for (unsigned i = 0; i < WIDTH * LINES; i++) {
gb->sgb->screen_buffer[i] = 0x0;
}
}
else {
uint32_t color = 0; uint32_t color = 0;
if (GB_is_cgb(gb)) { if (GB_is_cgb(gb)) {
color = (gb->io_registers[GB_IO_LCDC] & 0x80) && gb->stopped ? color = gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF);
gb->rgb_encode_callback(gb, 0, 0, 0) :
gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF);
} }
else { else {
color = (gb->io_registers[GB_IO_LCDC] & 0x80) && gb->stopped ? color = is_ppu_stopped ?
gb->background_palettes_rgb[3] : gb->background_palettes_rgb[0] :
gb->background_palettes_rgb[4]; gb->background_palettes_rgb[4];
} }
if (gb->border_mode == GB_BORDER_ALWAYS) { if (gb->border_mode == GB_BORDER_ALWAYS) {
@ -412,6 +407,10 @@ static void add_object_from_index(GB_gameboy_t *gb, unsigned index)
if (gb->dma_steps_left && (gb->dma_cycles >= 0 || gb->is_dma_restarting)) { if (gb->dma_steps_left && (gb->dma_cycles >= 0 || gb->is_dma_restarting)) {
return; return;
} }
if (gb->oam_ppu_blocked) {
return;
}
/* This reverse sorts the visible objects by location and priority */ /* This reverse sorts the visible objects by location and priority */
GB_object_t *objects = (GB_object_t *) &gb->oam; GB_object_t *objects = (GB_object_t *) &gb->oam;
@ -499,6 +498,9 @@ static void render_pixel_if_possible(GB_gameboy_t *gb)
icd_pixel = pixel; icd_pixel = pixel;
} }
} }
else if (gb->cgb_palettes_ppu_blocked) {
*dest = gb->rgb_encode_callback(gb, 0, 0, 0);
}
else { else {
*dest = gb->background_palettes_rgb[fifo_item->palette * 4 + pixel]; *dest = gb->background_palettes_rgb[fifo_item->palette * 4 + pixel];
} }
@ -521,6 +523,9 @@ static void render_pixel_if_possible(GB_gameboy_t *gb)
//gb->icd_pixel_callback(gb, pixel); //gb->icd_pixel_callback(gb, pixel);
} }
} }
else if (gb->cgb_palettes_ppu_blocked) {
*dest = gb->rgb_encode_callback(gb, 0, 0, 0);
}
else { else {
*dest = gb->sprite_palettes_rgb[oam_fifo_item->palette * 4 + pixel]; *dest = gb->sprite_palettes_rgb[oam_fifo_item->palette * 4 + pixel];
} }
@ -585,10 +590,16 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb)
gb->fetcher_y = y; gb->fetcher_y = y;
} }
gb->current_tile = gb->vram[map + gb->fetcher_x + y / 8 * 32]; gb->current_tile = gb->vram[map + gb->fetcher_x + y / 8 * 32];
if (gb->vram_ppu_blocked) {
gb->current_tile = 0xFF;
}
if (GB_is_cgb(gb)) { if (GB_is_cgb(gb)) {
/* The CGB actually accesses both the tile index AND the attributes in the same T-cycle. /* The CGB actually accesses both the tile index AND the attributes in the same T-cycle.
This probably means the CGB has a 16-bit data bus for the VRAM. */ This probably means the CGB has a 16-bit data bus for the VRAM. */
gb->current_tile_attributes = gb->vram[map + gb->fetcher_x + y / 8 * 32 + 0x2000]; gb->current_tile_attributes = gb->vram[map + gb->fetcher_x + y / 8 * 32 + 0x2000];
if (gb->vram_ppu_blocked) {
gb->current_tile_attributes = 0xFF;
}
} }
gb->fetcher_x++; gb->fetcher_x++;
gb->fetcher_x &= 0x1f; gb->fetcher_x &= 0x1f;
@ -615,7 +626,10 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb)
y_flip = 0x7; y_flip = 0x7;
} }
gb->current_tile_data[0] = gb->current_tile_data[0] =
gb->vram[tile_address + ((y & 7) ^ y_flip) * 2]; gb->vram[tile_address + ((y & 7) ^ y_flip) * 2];
if (gb->vram_ppu_blocked) {
gb->current_tile_data[0] = 0xFF;
}
} }
gb->fetcher_state++; gb->fetcher_state++;
break; break;
@ -642,7 +656,10 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb)
y_flip = 0x7; y_flip = 0x7;
} }
gb->current_tile_data[1] = gb->current_tile_data[1] =
gb->vram[tile_address + ((y & 7) ^ y_flip) * 2 + 1]; gb->vram[tile_address + ((y & 7) ^ y_flip) * 2 + 1];
if (gb->vram_ppu_blocked) {
gb->current_tile_data[1] = 0xFF;
}
} }
gb->fetcher_state++; gb->fetcher_state++;
break; break;
@ -913,7 +930,11 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
gb->cycles_for_line += 6; gb->cycles_for_line += 6;
GB_SLEEP(gb, display, 20, 6); GB_SLEEP(gb, display, 20, 6);
/* TODO: what does the PPU read if DMA is active? */ /* TODO: what does the PPU read if DMA is active? */
GB_object_t *object = &objects[gb->visible_objs[gb->n_visible_objs - 1]]; const GB_object_t *object = &objects[gb->visible_objs[gb->n_visible_objs - 1]];
if (gb->oam_ppu_blocked) {
static const GB_object_t blocked = {0xFF, 0xFF, 0xFF, 0xFF};
object = &blocked;
}
bool height_16 = (gb->io_registers[GB_IO_LCDC] & 4) != 0; /* Todo: Which T-cycle actually reads this? */ bool height_16 = (gb->io_registers[GB_IO_LCDC] & 4) != 0; /* Todo: Which T-cycle actually reads this? */
uint8_t tile_y = (gb->current_line - object->y) & (height_16? 0xF : 7); uint8_t tile_y = (gb->current_line - object->y) & (height_16? 0xF : 7);
@ -933,10 +954,9 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
if (gb->cgb_mode) { if (gb->cgb_mode) {
palette = object->flags & 0x7; palette = object->flags & 0x7;
} }
fifo_overlay_object_row(&gb->oam_fifo, fifo_overlay_object_row(&gb->oam_fifo,
gb->vram[line_address], gb->vram_ppu_blocked? 0xFF : gb->vram[line_address],
gb->vram[line_address + 1], gb->vram_ppu_blocked? 0xFF : gb->vram[line_address + 1],
palette, palette,
object->flags & 0x80, object->flags & 0x80,
gb->object_priority == GB_OBJECT_PRIORITY_INDEX? gb->visible_objs[gb->n_visible_objs - 1] : 0, gb->object_priority == GB_OBJECT_PRIORITY_INDEX? gb->visible_objs[gb->n_visible_objs - 1] : 0,

View File

@ -515,6 +515,9 @@ struct GB_gameboy_internal_s {
uint8_t current_lcd_line; // The LCD can go out of sync since the vsync signal is skipped in some cases. uint8_t current_lcd_line; // The LCD can go out of sync since the vsync signal is skipped in some cases.
uint32_t cycles_in_stop_mode; uint32_t cycles_in_stop_mode;
uint8_t object_priority; uint8_t object_priority;
bool oam_ppu_blocked;
bool vram_ppu_blocked;
bool cgb_palettes_ppu_blocked;
); );
/* 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

@ -236,6 +236,26 @@ static void nop(GB_gameboy_t *gb, uint8_t opcode)
{ {
} }
static void enter_stop_mode(GB_gameboy_t *gb)
{
gb->stopped = true;
gb->oam_ppu_blocked = !gb->oam_read_blocked;
gb->vram_ppu_blocked = !gb->vram_read_blocked;
gb->cgb_palettes_ppu_blocked = !gb->cgb_palettes_blocked;
}
static void leave_stop_mode(GB_gameboy_t *gb)
{
/* The CPU takes more time to wake up then the other components */
for (unsigned i = 0x200; i--;) {
GB_advance_cycles(gb, 0x10);
}
gb->stopped = false;
gb->oam_ppu_blocked = false;
gb->vram_ppu_blocked = false;
gb->cgb_palettes_ppu_blocked = false;
}
static void stop(GB_gameboy_t *gb, uint8_t opcode) static void stop(GB_gameboy_t *gb, uint8_t opcode)
{ {
if (gb->io_registers[GB_IO_KEY1] & 0x1) { if (gb->io_registers[GB_IO_KEY1] & 0x1) {
@ -252,9 +272,8 @@ static void stop(GB_gameboy_t *gb, uint8_t opcode)
gb->cgb_double_speed ^= true; gb->cgb_double_speed ^= true;
gb->io_registers[GB_IO_KEY1] = 0; gb->io_registers[GB_IO_KEY1] = 0;
for (unsigned i = 0x800; i--;) { enter_stop_mode(gb);
GB_advance_cycles(gb, 0x40); leave_stop_mode(gb);
}
if (!needs_alignment) { if (!needs_alignment) {
GB_advance_cycles(gb, 0x4); GB_advance_cycles(gb, 0x4);
@ -270,7 +289,7 @@ static void stop(GB_gameboy_t *gb, uint8_t opcode)
gb->halted = true; gb->halted = true;
} }
else { else {
gb->stopped = true; enter_stop_mode(gb);
} }
} }
@ -1429,11 +1448,7 @@ void GB_cpu_run(GB_gameboy_t *gb)
GB_timing_sync(gb); GB_timing_sync(gb);
GB_advance_cycles(gb, 4); GB_advance_cycles(gb, 4);
if ((gb->io_registers[GB_IO_JOYP] & 0xF) != 0xF) { if ((gb->io_registers[GB_IO_JOYP] & 0xF) != 0xF) {
gb->stopped = false; leave_stop_mode(gb);
/* The CPU takes more time to wake up then the other components */
for (unsigned i = 0x800; i--;) {
GB_advance_cycles(gb, 0x40);
}
GB_advance_cycles(gb, 8); GB_advance_cycles(gb, 8);
} }
return; return;

View File

@ -139,6 +139,11 @@ static void GB_set_internal_div_counter(GB_gameboy_t *gb, uint32_t value)
static void GB_timers_run(GB_gameboy_t *gb, uint8_t cycles) static void GB_timers_run(GB_gameboy_t *gb, uint8_t cycles)
{ {
if (gb->stopped) {
gb->apu.apu_cycles += 4 << !gb->cgb_double_speed;
return;
}
GB_STATE_MACHINE(gb, div, cycles, 1) { GB_STATE_MACHINE(gb, div, cycles, 1) {
GB_STATE(gb, div, 1); GB_STATE(gb, div, 1);
GB_STATE(gb, div, 2); GB_STATE(gb, div, 2);
@ -213,8 +218,8 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles)
// Affected by speed boost // Affected by speed boost
gb->dma_cycles += cycles; gb->dma_cycles += cycles;
GB_timers_run(gb, cycles);
if (!gb->stopped) { if (!gb->stopped) {
GB_timers_run(gb, cycles);
advance_serial(gb, cycles); // TODO: Verify what happens in STOP mode advance_serial(gb, cycles); // TODO: Verify what happens in STOP mode
} }