From bec09a012cae8f1efe5723afac401084420d59d9 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 15 Feb 2020 19:21:43 +0200 Subject: [PATCH] More accurate emulation of STOP mode --- Core/display.c | 56 +++++++++++++++++++++++++++++++++---------------- Core/gb.h | 3 +++ Core/sm83_cpu.c | 33 +++++++++++++++++++++-------- Core/timing.c | 7 ++++++- 4 files changed, 71 insertions(+), 28 deletions(-) diff --git a/Core/display.c b/Core/display.c index 521a3861..d5f3c748 100644 --- a/Core/display.c +++ b/Core/display.c @@ -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) */ - if (gb->sgb) { - for (unsigned i = 0; i < WIDTH * LINES; i++) { - gb->sgb->screen_buffer[i] = 0x0; - } - } - else { + if (!GB_is_sgb(gb)) { uint32_t color = 0; if (GB_is_cgb(gb)) { - color = (gb->io_registers[GB_IO_LCDC] & 0x80) && gb->stopped ? - gb->rgb_encode_callback(gb, 0, 0, 0) : - gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF); + color = gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF); } else { - color = (gb->io_registers[GB_IO_LCDC] & 0x80) && gb->stopped ? - gb->background_palettes_rgb[3] : + color = is_ppu_stopped ? + gb->background_palettes_rgb[0] : gb->background_palettes_rgb[4]; } 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)) { return; } + + if (gb->oam_ppu_blocked) { + return; + } /* This reverse sorts the visible objects by location and priority */ 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; } } + else if (gb->cgb_palettes_ppu_blocked) { + *dest = gb->rgb_encode_callback(gb, 0, 0, 0); + } else { *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); } } + else if (gb->cgb_palettes_ppu_blocked) { + *dest = gb->rgb_encode_callback(gb, 0, 0, 0); + } else { *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->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)) { /* 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. */ 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 &= 0x1f; @@ -615,7 +626,10 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) y_flip = 0x7; } 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++; break; @@ -642,7 +656,10 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb) y_flip = 0x7; } 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++; break; @@ -913,7 +930,11 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) gb->cycles_for_line += 6; GB_SLEEP(gb, display, 20, 6); /* 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? */ 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) { palette = object->flags & 0x7; } - fifo_overlay_object_row(&gb->oam_fifo, - gb->vram[line_address], - gb->vram[line_address + 1], + gb->vram_ppu_blocked? 0xFF : gb->vram[line_address], + gb->vram_ppu_blocked? 0xFF : gb->vram[line_address + 1], palette, object->flags & 0x80, gb->object_priority == GB_OBJECT_PRIORITY_INDEX? gb->visible_objs[gb->n_visible_objs - 1] : 0, diff --git a/Core/gb.h b/Core/gb.h index b413fe5f..56b0a9af 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -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. uint32_t cycles_in_stop_mode; 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 */ diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index 713bb665..f30443dd 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -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) { 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->io_registers[GB_IO_KEY1] = 0; - for (unsigned i = 0x800; i--;) { - GB_advance_cycles(gb, 0x40); - } + enter_stop_mode(gb); + leave_stop_mode(gb); if (!needs_alignment) { GB_advance_cycles(gb, 0x4); @@ -270,7 +289,7 @@ static void stop(GB_gameboy_t *gb, uint8_t opcode) gb->halted = true; } 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_advance_cycles(gb, 4); if ((gb->io_registers[GB_IO_JOYP] & 0xF) != 0xF) { - gb->stopped = false; - /* The CPU takes more time to wake up then the other components */ - for (unsigned i = 0x800; i--;) { - GB_advance_cycles(gb, 0x40); - } + leave_stop_mode(gb); GB_advance_cycles(gb, 8); } return; diff --git a/Core/timing.c b/Core/timing.c index 283558c0..1f3f654e 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -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) { + if (gb->stopped) { + gb->apu.apu_cycles += 4 << !gb->cgb_double_speed; + return; + } + GB_STATE_MACHINE(gb, div, cycles, 1) { GB_STATE(gb, div, 1); GB_STATE(gb, div, 2); @@ -213,8 +218,8 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) // Affected by speed boost gb->dma_cycles += cycles; + GB_timers_run(gb, cycles); if (!gb->stopped) { - GB_timers_run(gb, cycles); advance_serial(gb, cycles); // TODO: Verify what happens in STOP mode }