Rewrote PPU (currently only emulates DMG correctly) to use the new timing mechanism. Removed “future interrupts” (No longer required because SameBoy is now T-cycle based)

This commit is contained in:
Lior Halphon 2018-02-25 00:48:45 +02:00
parent 42ab746a66
commit ef670986c6
8 changed files with 261 additions and 408 deletions

View File

@ -15,14 +15,13 @@
Todo: Mode lengths are not constants, see http://blog.kevtris.org/blogfiles/Nitty%20Gritty%20Gameboy%20VRAM%20Timing.txt Todo: Mode lengths are not constants, see http://blog.kevtris.org/blogfiles/Nitty%20Gritty%20Gameboy%20VRAM%20Timing.txt
*/ */
/* The display (logically) runs in 8MHz units, so we double our length constants */ #define MODE2_LENGTH (80)
#define MODE2_LENGTH (80 * 2) #define MODE3_LENGTH (172)
#define MODE3_LENGTH (172 * 2) #define MODE0_LENGTH (204)
#define MODE0_LENGTH (204 * 2)
#define LINE_LENGTH (MODE2_LENGTH + MODE3_LENGTH + MODE0_LENGTH) // = 456 #define LINE_LENGTH (MODE2_LENGTH + MODE3_LENGTH + MODE0_LENGTH) // = 456
#define LINES (144) #define LINES (144)
#define WIDTH (160) #define WIDTH (160)
#define FRAME_LENGTH (LCDC_PERIOD * 2) #define FRAME_LENGTH (LCDC_PERIOD)
#define VIRTUAL_LINES (FRAME_LENGTH / LINE_LENGTH) // = 154 #define VIRTUAL_LINES (FRAME_LENGTH / LINE_LENGTH) // = 154
typedef struct __attribute__((packed)) { typedef struct __attribute__((packed)) {
@ -199,7 +198,7 @@ static uint32_t get_pixel(GB_gameboy_t *gb, uint8_t x, uint8_t y)
} }
static void display_vblank(GB_gameboy_t *gb) static void display_vblank(GB_gameboy_t *gb)
{ {
gb->vblank_just_occured = true; gb->vblank_just_occured = true;
if (gb->turbo) { if (gb->turbo) {
@ -308,406 +307,258 @@ void GB_set_color_correction_mode(GB_gameboy_t *gb, GB_color_correction_mode_t m
*/ */
static void update_display_state(GB_gameboy_t *gb, uint8_t cycles) void GB_STAT_update(GB_gameboy_t *gb)
{ {
if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) { bool previous_interrupt_line = gb->stat_interrupt_line;
/* LCD is disabled, state is constant */ gb->stat_interrupt_line = false;
/* Set LY=LYC bit */
/* When the LCD is off, LY is 0 and STAT mode is 0. if (gb->ly_for_comparison == gb->io_registers[GB_IO_LYC]) {
Todo: Verify the LY=LYC flag should be on. */
gb->io_registers[GB_IO_LY] = 0;
gb->io_registers[GB_IO_STAT] &= ~3;
gb->io_registers[GB_IO_STAT] |= 4; gb->io_registers[GB_IO_STAT] |= 4;
gb->effective_scx = gb->io_registers[GB_IO_SCX];
if (gb->hdma_on_hblank) {
gb->hdma_on_hblank = false;
gb->hdma_on = false;
/* Todo: is this correct? */
gb->hdma_steps_left = 0xff;
}
gb->oam_read_blocked = false;
gb->vram_read_blocked = false;
gb->oam_write_blocked = false;
gb->vram_write_blocked = false;
/* Keep sending vblanks to user even if the screen is off */
gb->display_cycles += cycles;
if (gb->display_cycles >= FRAME_LENGTH) {
/* VBlank! */
gb->display_cycles -= FRAME_LENGTH;
display_vblank(gb);
}
/* Reset window rendering state */
gb->wy_diff = 0;
gb->window_disabled_while_active = false;
return;
}
uint8_t atomic_increase = gb->cgb_double_speed? 4 : 8;
/* According to AntonioND's docs this value should be 0 in CGB mode, but tests I ran on my CGB seem to contradict
these findings.
Todo: Investigate what causes the difference between our findings */
uint8_t stat_delay = gb->cgb_double_speed? 4 : 8; // (gb->cgb_mode? 0 : 8);
/* Todo: Is this correct for DMG mode CGB? */
uint8_t scx_delay = (gb->effective_scx & 7) * 2;
if (gb->cgb_double_speed) {
scx_delay = (scx_delay + 2) & ~3;
} }
else { else {
scx_delay = (scx_delay + (gb->first_scanline ? 4 : 0)) & ~7; gb->io_registers[GB_IO_STAT] &= ~4;
} }
/* Todo: These are correct for DMG, DMG-mode CGB, and single speed CGB. Is is correct for double speed CGB? */ switch (gb->io_registers[GB_IO_STAT] & 3) {
uint8_t oam_blocking_rush = gb->cgb_double_speed? 4 : 8; case 0: gb->stat_interrupt_line = (gb->io_registers[GB_IO_STAT] & 8); break;
uint8_t vram_blocking_rush = gb->is_cgb? 0 : 8; case 1: gb->stat_interrupt_line = gb->io_registers[GB_IO_STAT] & 0x10; break;
case 2: gb->stat_interrupt_line = gb->io_registers[GB_IO_STAT] & 0x20; break;
}
for (; cycles; cycles -= atomic_increase) { /* User requested a LY=LYC interrupt and the LY=LYC bit is on */
bool dmg_future_stat = false; if ((gb->io_registers[GB_IO_STAT] & 0x44) == 0x44) {
gb->io_registers[GB_IO_IF] |= gb->future_interrupts & 3; gb->stat_interrupt_line = true;
gb->future_interrupts &= ~3; }
bool previous_stat_interrupt_line = gb->stat_interrupt_line; if (gb->stat_interrupt_line && ! previous_interrupt_line) {
gb->stat_interrupt_line = false; gb->io_registers[GB_IO_IF] |= 2;
}
gb->display_cycles += atomic_increase; }
/* The very first line is 1 M-cycle shorter when the LCD turns on. Verified on SGB2, CGB in CGB mode and
CGB in double speed mode. */
if (gb->first_scanline && gb->display_cycles >= LINE_LENGTH - 0x10) {
gb->first_scanline = false;
gb->display_cycles += 8;
}
bool should_compare_ly = true;
uint8_t ly_for_comparison = gb->io_registers[GB_IO_LY] = gb->display_cycles / LINE_LENGTH;
bool just_entered_hblank = false;
/* Handle cycle completion. STAT's initial value depends on model and mode */
if (gb->display_cycles == FRAME_LENGTH) {
/* VBlank! */
gb->display_cycles = 0;
gb->io_registers[GB_IO_STAT] &= ~3;
if (gb->is_cgb) {
gb->io_registers[GB_IO_STAT] |= 1;
}
ly_for_comparison = gb->io_registers[GB_IO_LY] = 0;
/* Todo: verify timing */
gb->oam_read_blocked = true;
gb->vram_read_blocked = false;
gb->oam_write_blocked = true;
gb->vram_write_blocked = false;
static unsigned scx_delay(GB_gameboy_t *gb)
/* Reset window rendering state */ {
gb->wy_diff = 0; return (gb->effective_scx & 7) + 0;
gb->window_disabled_while_active = false;
}
/* Entered VBlank state, update STAT and IF */
else if (gb->display_cycles == LINES * LINE_LENGTH + stat_delay) {
gb->io_registers[GB_IO_STAT] &= ~3;
gb->io_registers[GB_IO_STAT] |= 1;
if (gb->is_cgb) {
gb->future_interrupts |= 1;
}
else {
gb->io_registers[GB_IO_IF] |= 1;
}
/* Entering VBlank state triggers the OAM interrupt. In CGB, it happens 4 cycles earlier */
if (gb->io_registers[GB_IO_STAT] & 0x20 && !gb->is_cgb) {
gb->stat_interrupt_line = true;
}
if (gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON) {
if (!gb->is_cgb) {
display_vblank(gb);
gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED;
}
else {
gb->frame_skip_state = GB_FRAMESKIP_FIRST_FRAME_SKIPPED;
}
}
else {
gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED;
display_vblank(gb);
}
}
}
void GB_lcd_off(GB_gameboy_t *gb)
{
gb->display_state = 0;
gb->display_cycles = 0;
/* When the LCD is disabled, state is constant */
/* When the LCD is off, LY is 0 and STAT mode is 0.
Todo: Verify the LY=LYC flag should be on. */
gb->io_registers[GB_IO_LY] = 0;
gb->io_registers[GB_IO_STAT] &= ~3;
gb->io_registers[GB_IO_STAT] |= 4;
gb->effective_scx = gb->io_registers[GB_IO_SCX];
if (gb->hdma_on_hblank) {
gb->hdma_on_hblank = false;
gb->hdma_on = false;
/* Handle line 0 right after turning the LCD on */ /* Todo: is this correct? */
else if (gb->first_scanline) { gb->hdma_steps_left = 0xff;
/* OAM and VRAM blocking is not rushed in the very first scanline */ }
if (gb->display_cycles == atomic_increase) {
gb->io_registers[GB_IO_STAT] &= ~3; gb->oam_read_blocked = false;
gb->oam_read_blocked = false; gb->vram_read_blocked = false;
gb->vram_read_blocked = false; gb->oam_write_blocked = false;
gb->oam_write_blocked = false; gb->vram_write_blocked = false;
gb->vram_write_blocked = false;
} /* Reset window rendering state */
else if (gb->display_cycles == MODE2_LENGTH) { gb->wy_diff = 0;
gb->io_registers[GB_IO_STAT] &= ~3; gb->window_disabled_while_active = false;
gb->io_registers[GB_IO_STAT] |= 3; gb->current_line = 0;
gb->effective_scx = gb->io_registers[GB_IO_SCX]; gb->ly_for_comparison = 0;
gb->oam_read_blocked = true;
gb->vram_read_blocked = true; GB_STAT_update(gb);
gb->oam_write_blocked = true;
gb->vram_write_blocked = true;
}
else if (gb->display_cycles == MODE2_LENGTH + MODE3_LENGTH + scx_delay) {
gb->io_registers[GB_IO_STAT] &= ~3;
gb->oam_read_blocked = false;
gb->vram_read_blocked = false;
gb->oam_write_blocked = false;
gb->vram_write_blocked = false;
just_entered_hblank = true;
}
}
/* Handle STAT changes for lines 0-143 */
else if (gb->display_cycles < LINES * LINE_LENGTH) {
unsigned position_in_line = gb->display_cycles % LINE_LENGTH;
/* Handle OAM and VRAM blocking */
/* Todo: verify CGB timing for write blocking */
if (position_in_line == stat_delay - oam_blocking_rush ||
// In case stat_delay is 0
(position_in_line == LINE_LENGTH + stat_delay - oam_blocking_rush && gb->io_registers[GB_IO_LY] != 143)) {
gb->oam_read_blocked = true;
gb->oam_write_blocked = gb->is_cgb;
}
else if (position_in_line == MODE2_LENGTH + stat_delay - vram_blocking_rush) {
gb->vram_read_blocked = true;
gb->vram_write_blocked = gb->is_cgb;
}
if (position_in_line == stat_delay) {
gb->oam_write_blocked = true;
}
else if (!gb->is_cgb && position_in_line == MODE2_LENGTH + stat_delay - oam_blocking_rush) {
gb->oam_write_blocked = false;
}
else if (position_in_line == MODE2_LENGTH + stat_delay) {
gb->vram_write_blocked = true;
gb->oam_write_blocked = true;
}
/* Handle everything else */
/* OAM interrupt happens slightly before STAT is actually updated. (About 1-3 T-cycles)
Todo: Test double speed CGB */
if (position_in_line == 0) {
if (gb->io_registers[GB_IO_STAT] & 0x20) {
gb->stat_interrupt_line = true;
dmg_future_stat = true;
}
if (gb->display_cycles != 0) {
should_compare_ly = gb->is_cgb;
ly_for_comparison--;
}
}
else if (position_in_line == stat_delay) {
gb->io_registers[GB_IO_STAT] &= ~3;
gb->io_registers[GB_IO_STAT] |= 2;
}
else if (position_in_line == MODE2_LENGTH + stat_delay) {
gb->io_registers[GB_IO_STAT] &= ~3;
gb->io_registers[GB_IO_STAT] |= 3;
gb->effective_scx = gb->io_registers[GB_IO_SCX];
gb->previous_lcdc_x = - (gb->effective_scx & 0x7);
}
else if (position_in_line == MODE2_LENGTH + MODE3_LENGTH + stat_delay + scx_delay) {
just_entered_hblank = true;
gb->io_registers[GB_IO_STAT] &= ~3;
gb->oam_read_blocked = false;
gb->vram_read_blocked = false;
gb->oam_write_blocked = false;
gb->vram_write_blocked = false;
if (gb->hdma_on_hblank) {
gb->hdma_on = true;
gb->hdma_cycles = 0;
}
}
}
/* Line 153 is special */
else if (gb->display_cycles >= (VIRTUAL_LINES - 1) * LINE_LENGTH) {
/* DMG */
if (!gb->is_cgb) {
switch (gb->display_cycles - (VIRTUAL_LINES - 1) * LINE_LENGTH) {
case 0:
should_compare_ly = false;
break;
case 8:
gb->io_registers[GB_IO_LY] = 0;
ly_for_comparison = VIRTUAL_LINES - 1;
break;
case 16:
gb->io_registers[GB_IO_LY] = 0;
should_compare_ly = false;
break;
default:
gb->io_registers[GB_IO_LY] = 0;
ly_for_comparison = 0;
}
}
/* CGB in DMG mode */
else if (!gb->cgb_mode) {
switch (gb->display_cycles - (VIRTUAL_LINES - 1) * LINE_LENGTH) {
case 0:
ly_for_comparison = VIRTUAL_LINES - 2;
break;
case 8:
break;
case 16:
gb->io_registers[GB_IO_LY] = 0;
break;
default:
gb->io_registers[GB_IO_LY] = 0;
ly_for_comparison = 0;
}
}
/* Single speed CGB */
else if (!gb->cgb_double_speed) {
switch (gb->display_cycles - (VIRTUAL_LINES - 1) * LINE_LENGTH) {
case 0:
break;
case 8:
gb->io_registers[GB_IO_LY] = 0;
break;
default:
gb->io_registers[GB_IO_LY] = 0;
ly_for_comparison = 0;
}
}
/* Double speed CGB */
else {
switch (gb->display_cycles - (VIRTUAL_LINES - 1) * LINE_LENGTH) {
case 0:
ly_for_comparison = VIRTUAL_LINES - 2;
break;
case 4:
case 8:
break;
case 12:
case 16:
gb->io_registers[GB_IO_LY] = 0;
break;
default:
gb->io_registers[GB_IO_LY] = 0;
ly_for_comparison = 0;
}
}
}
/* Lines 144 - 152 */
else {
if (gb->display_cycles % LINE_LENGTH == 0) {
should_compare_ly = gb->is_cgb;
ly_for_comparison--;
}
}
/* Set LY=LYC bit */
if (should_compare_ly && (ly_for_comparison == gb->io_registers[GB_IO_LYC])) {
gb->io_registers[GB_IO_STAT] |= 4;
}
else {
gb->io_registers[GB_IO_STAT] &= ~4;
}
if (!gb->stat_interrupt_line) {
switch (gb->io_registers[GB_IO_STAT] & 3) {
case 0:
gb->stat_interrupt_line = (gb->io_registers[GB_IO_STAT] & 8);
if (!gb->cgb_double_speed && just_entered_hblank && ((gb->effective_scx + (gb->first_scanline ? 2 : 0)) & 3) == 3) {
gb->stat_interrupt_line = false;
}
else if (just_entered_hblank && ((gb->effective_scx + (gb->first_scanline ? 2 : 0)) & 3) != 0) {
dmg_future_stat = true;
}
break;
case 1: gb->stat_interrupt_line = gb->io_registers[GB_IO_STAT] & 0x10; break;
case 2: gb->stat_interrupt_line = gb->io_registers[GB_IO_STAT] & 0x20; break;
}
/* User requested a LY=LYC interrupt and the LY=LYC bit is on */
if ((gb->io_registers[GB_IO_STAT] & 0x44) == 0x44) {
gb->stat_interrupt_line = true;
dmg_future_stat = false;
}
}
/* On the CGB, the last cycle of line 144 triggers an OAM interrupt
Todo: Verify timing for CGB in CGB mode and double speed CGB */
if (gb->is_cgb &&
gb->display_cycles == LINES * LINE_LENGTH + stat_delay - atomic_increase &&
(gb->io_registers[GB_IO_STAT] & 0x20)) {
gb->stat_interrupt_line = true;
}
if (gb->stat_interrupt_line && !previous_stat_interrupt_line) {
if (gb->is_cgb || dmg_future_stat) {
gb->future_interrupts |= 2;
}
else {
gb->io_registers[GB_IO_IF] |= 2;
}
}
};
} }
void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
{ {
if (gb->display_hack == 1) return;
if (gb->display_hack == 2) cycles *= 2; GB_STATE_MACHINE(gb, display, cycles, 2) {
update_display_state(gb, cycles); GB_STATE(gb, display, 1);
if (gb->disable_rendering) { GB_STATE(gb, display, 2);
return; GB_STATE(gb, display, 3);
GB_STATE(gb, display, 4);
GB_STATE(gb, display, 5);
GB_STATE(gb, display, 6);
GB_STATE(gb, display, 7);
GB_STATE(gb, display, 8);
GB_STATE(gb, display, 9);
GB_STATE(gb, display, 10);
GB_STATE(gb, display, 11);
GB_STATE(gb, display, 12);
GB_STATE(gb, display, 13);
GB_STATE(gb, display, 14);
GB_STATE(gb, display, 15);
GB_STATE(gb, display, 16);
GB_STATE(gb, display, 17);
GB_STATE(gb, display, 18);
} }
/*
Display controller bug: For some reason, the OAM STAT interrupt is called, as expected, for LY = 0..143.
However, it is also called from LY = 144.
See http://forums.nesdev.com/viewtopic.php?f=20&t=13727
*/
if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) { if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) {
/* LCD is disabled, do nothing */ while (true) {
return; GB_SLEEP(gb, display, 18, LCDC_PERIOD);
} display_vblank(gb);
if (gb->display_cycles >= LINE_LENGTH * 144) { /* VBlank */ }
return; return;
} }
/* Handle the very first line 0 */
gb->current_line = 0;
gb->ly_for_comparison = 0;
gb->io_registers[GB_IO_STAT] &= ~3;
gb->oam_read_blocked = false;
gb->vram_read_blocked = false;
gb->oam_write_blocked = false;
gb->vram_write_blocked = false;
GB_STAT_update(gb);
GB_SLEEP(gb, display, 1, MODE2_LENGTH - 4);
uint8_t effective_ly = gb->display_cycles / LINE_LENGTH; gb->io_registers[GB_IO_STAT] &= ~3;
gb->io_registers[GB_IO_STAT] |= 3;
gb->effective_scx = gb->io_registers[GB_IO_SCX];
if (gb->display_cycles % LINE_LENGTH < MODE2_LENGTH) { /* Mode 2 */ gb->oam_read_blocked = true;
return; gb->vram_read_blocked = true;
} gb->oam_write_blocked = true;
gb->vram_write_blocked = true;
GB_STAT_update(gb);
/* Render */ GB_SLEEP(gb, display, 2, MODE3_LENGTH + scx_delay(gb) + 2);
int16_t current_lcdc_x = (gb->display_cycles % LINE_LENGTH - MODE2_LENGTH) / 2 - (gb->effective_scx & 0x7) - 7;
for (;gb->previous_lcdc_x < current_lcdc_x; gb->previous_lcdc_x++) { gb->io_registers[GB_IO_STAT] &= ~3;
if (gb->previous_lcdc_x >= WIDTH) { gb->oam_read_blocked = false;
continue; gb->vram_read_blocked = false;
gb->oam_write_blocked = false;
gb->vram_write_blocked = false;
GB_SLEEP(gb, display, 17, 1);
GB_STAT_update(gb);
/* Mode 0 is shorter in the very first line */
GB_SLEEP(gb, display, 3, MODE0_LENGTH - scx_delay(gb) - 2 - 1 - 4);
gb->current_line = 1;
while (true) {
/* Lines 0 - 143 */
for (; gb->current_line < LINES; gb->current_line++) {
gb->io_registers[GB_IO_LY] = gb->current_line;
gb->oam_read_blocked = true;
gb->oam_write_blocked = false;
gb->ly_for_comparison = gb->current_line? -1 : gb->current_line;
GB_STAT_update(gb);
GB_SLEEP(gb, display, 4, 4);
gb->io_registers[GB_IO_STAT] &= ~3;
gb->io_registers[GB_IO_STAT] |= 2;
gb->oam_write_blocked = true;
gb->ly_for_comparison = gb->current_line;
GB_STAT_update(gb);
GB_SLEEP(gb, display, 5, MODE2_LENGTH - 4);
gb->vram_read_blocked = true;
gb->vram_write_blocked = false;
gb->oam_write_blocked = false;
GB_STAT_update(gb);
GB_SLEEP(gb, display, 6, 4);
gb->io_registers[GB_IO_STAT] &= ~3;
gb->io_registers[GB_IO_STAT] |= 3;
gb->effective_scx = gb->io_registers[GB_IO_SCX];
gb->vram_write_blocked = true;
gb->oam_write_blocked = true;
GB_STAT_update(gb);
for (gb->position_in_line = 0; gb->position_in_line < WIDTH + 7; gb->position_in_line++) {
if (!gb->disable_rendering) {
signed screen_pos = (signed) gb->position_in_line - (gb->effective_scx & 0x7);
if (((screen_pos + gb->effective_scx) & 7) == 0) {
gb->effective_scy = gb->io_registers[GB_IO_SCY];
}
if (screen_pos >= 0 && screen_pos < WIDTH) {
gb->screen[gb->current_line * WIDTH + screen_pos] = get_pixel(gb, screen_pos, gb->current_line);
}
}
GB_SLEEP(gb, display, 15, 1);
}
GB_SLEEP(gb, display, 7, MODE3_LENGTH + scx_delay(gb) - WIDTH - 7);
gb->io_registers[GB_IO_STAT] &= ~3;
gb->oam_read_blocked = false;
gb->vram_read_blocked = false;
gb->oam_write_blocked = false;
gb->vram_write_blocked = false;
if (gb->hdma_on_hblank) {
gb->hdma_on = true;
gb->hdma_cycles = 0;
}
GB_SLEEP(gb, display, 16, 1);
GB_STAT_update(gb);
GB_SLEEP(gb, display, 8, MODE0_LENGTH - scx_delay(gb) - 4 - 1);
} }
if (((gb->previous_lcdc_x + gb->effective_scx) & 7) == 0) { /* Lines 144 - 152 */
gb->effective_scy = gb->io_registers[GB_IO_SCY]; for (; gb->current_line < VIRTUAL_LINES - 1; gb->current_line++) {
gb->io_registers[GB_IO_LY] = gb->current_line;
gb->ly_for_comparison = -1;
GB_STAT_update(gb);
GB_SLEEP(gb, display, 9, 4);
gb->ly_for_comparison = gb->current_line;
if (gb->current_line == LINES) {
/* Entering VBlank state triggers the OAM interrupt. In CGB, it happens 4 cycles earlier */
gb->io_registers[GB_IO_STAT] &= ~3;
gb->io_registers[GB_IO_STAT] |= 2;
GB_STAT_update(gb);
gb->io_registers[GB_IO_STAT] &= ~3;
gb->io_registers[GB_IO_STAT] |= 1;
gb->io_registers[GB_IO_IF] |= 1;
if (gb->io_registers[GB_IO_STAT] & 0x20) {
gb->stat_interrupt_line = true;
}
if (gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON) {
display_vblank(gb);
gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED;
}
else {
gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED;
display_vblank(gb);
}
}
GB_STAT_update(gb);
GB_SLEEP(gb, display, 10, LINE_LENGTH - 4);
} }
if (gb->previous_lcdc_x < 0) { /* Lines 153 */
continue; gb->io_registers[GB_IO_LY] = 153;
} gb->ly_for_comparison = -1;
gb->screen[effective_ly * WIDTH + gb->previous_lcdc_x] = GB_STAT_update(gb);
get_pixel(gb, gb->previous_lcdc_x, effective_ly); GB_SLEEP(gb, display, 11, 4);
gb->io_registers[GB_IO_LY] = 0;
gb->ly_for_comparison = 153;
GB_STAT_update(gb);
GB_SLEEP(gb, display, 12, 4);
gb->ly_for_comparison = -1;
GB_STAT_update(gb);
GB_SLEEP(gb, display, 13, 4);
gb->ly_for_comparison = 0;
GB_STAT_update(gb);
GB_SLEEP(gb, display, 14, LINE_LENGTH - 12);
gb->io_registers[GB_IO_STAT] &= ~3;
/* Reset window rendering state */
gb->wy_diff = 0;
gb->window_disabled_while_active = false;
gb->current_line = 0;
} }
} }

View File

@ -6,6 +6,8 @@
void GB_display_run(GB_gameboy_t *gb, uint8_t cycles); void GB_display_run(GB_gameboy_t *gb, uint8_t cycles);
void GB_palette_changed(GB_gameboy_t *gb, bool background_palette, uint8_t index); void GB_palette_changed(GB_gameboy_t *gb, bool background_palette, uint8_t index);
void GB_window_related_write(GB_gameboy_t *gb, uint8_t addr, uint8_t value); void GB_window_related_write(GB_gameboy_t *gb, uint8_t addr, uint8_t value);
void GB_STAT_update(GB_gameboy_t *gb);
void GB_lcd_off(GB_gameboy_t *gb);
#endif #endif
typedef enum { typedef enum {

View File

@ -332,15 +332,12 @@ struct GB_gameboy_internal_s {
/* Timing */ /* Timing */
GB_SECTION(timing, GB_SECTION(timing,
uint32_t display_cycles; // In 8 MHz units GB_UNIT(display);
GB_UNIT(div); GB_UNIT(div);
uint32_t div_counter; uint32_t div_counter;
uint8_t tima_reload_state; /* After TIMA overflows, it becomes 0 for 4 cycles before actually reloading. */ uint8_t tima_reload_state; /* After TIMA overflows, it becomes 0 for 4 cycles before actually reloading. */
uint16_t serial_cycles; uint16_t serial_cycles;
uint16_t serial_length; uint16_t serial_length;
uint8_t future_interrupts; /* Interrupts can occur in any T-cycle. Some timings result in different interrupt
timing when the CPU is in halt mode, and might also affect the DI instruction. */
uint8_t display_hack; // Temporary hack until the display is rewritten to operate in T-cycle rates;
); );
/* APU */ /* APU */
@ -371,7 +368,7 @@ struct GB_gameboy_internal_s {
uint8_t oam[0xA0]; uint8_t oam[0xA0];
uint8_t background_palettes_data[0x40]; uint8_t background_palettes_data[0x40];
uint8_t sprite_palettes_data[0x40]; uint8_t sprite_palettes_data[0x40];
int16_t previous_lcdc_x; uint8_t position_in_line;
bool stat_interrupt_line; bool stat_interrupt_line;
uint8_t effective_scx; uint8_t effective_scx;
uint8_t wy_diff; uint8_t wy_diff;
@ -385,13 +382,14 @@ struct GB_gameboy_internal_s {
GB_FRAMESKIP_FIRST_FRAME_SKIPPED, // This state is 'skipped' when emulating a DMG GB_FRAMESKIP_FIRST_FRAME_SKIPPED, // This state is 'skipped' when emulating a DMG
GB_FRAMESKIP_SECOND_FRAME_RENDERED, GB_FRAMESKIP_SECOND_FRAME_RENDERED,
} frame_skip_state; } frame_skip_state;
bool first_scanline; // The very first scan line after turning the LCD behaves differently.
bool oam_read_blocked; bool oam_read_blocked;
bool vram_read_blocked; bool vram_read_blocked;
bool oam_write_blocked; bool oam_write_blocked;
bool vram_write_blocked; bool vram_write_blocked;
bool window_disabled_while_active; bool window_disabled_while_active;
uint8_t effective_scy; // SCY is latched when starting to draw a tile uint8_t effective_scy; // SCY is latched when starting to draw a tile
uint8_t current_line;
uint16_t ly_for_comparison;
); );
/* 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

@ -52,6 +52,7 @@ void GB_update_joyp(GB_gameboy_t *gb)
if (previous_state != (gb->io_registers[GB_IO_JOYP] & 0xF)) { if (previous_state != (gb->io_registers[GB_IO_JOYP] & 0xF)) {
/* The joypad interrupt DOES occur on CGB (Tested on CGB-CPU-06), unlike what some documents say. */ /* The joypad interrupt DOES occur on CGB (Tested on CGB-CPU-06), unlike what some documents say. */
gb->io_registers[GB_IO_IF] |= 0x10; gb->io_registers[GB_IO_IF] |= 0x10;
gb->stopped = false;
} }
gb->io_registers[GB_IO_JOYP] |= 0xC0; // No SGB support gb->io_registers[GB_IO_JOYP] |= 0xC0; // No SGB support
} }

View File

@ -136,7 +136,7 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr)
if (addr < 0xFF80) { if (addr < 0xFF80) {
switch (addr & 0xFF) { switch (addr & 0xFF) {
case GB_IO_IF: case GB_IO_IF:
return gb->io_registers[GB_IO_IF] | 0xE0 | gb->future_interrupts; return gb->io_registers[GB_IO_IF] | 0xE0;
case GB_IO_TAC: case GB_IO_TAC:
return gb->io_registers[GB_IO_TAC] | 0xF8; return gb->io_registers[GB_IO_TAC] | 0xF8;
case GB_IO_STAT: case GB_IO_STAT:
@ -417,10 +417,8 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
GB_window_related_write(gb, addr & 0xFF, value); GB_window_related_write(gb, addr & 0xFF, value);
break; break;
case GB_IO_IF: case GB_IO_IF:
gb->future_interrupts = 0;
case GB_IO_SCX: case GB_IO_SCX:
case GB_IO_SCY: case GB_IO_SCY:
case GB_IO_LYC:
case GB_IO_BGP: case GB_IO_BGP:
case GB_IO_OBP0: case GB_IO_OBP0:
case GB_IO_OBP1: case GB_IO_OBP1:
@ -433,6 +431,10 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
case GB_IO_UNKNOWN5: case GB_IO_UNKNOWN5:
gb->io_registers[addr & 0xFF] = value; gb->io_registers[addr & 0xFF] = value;
return; return;
case GB_IO_LYC:
gb->io_registers[addr & 0xFF] = value;
GB_STAT_update(gb);
return;
case GB_IO_TIMA: case GB_IO_TIMA:
if (gb->tima_reload_state != GB_TIMA_RELOADED) { if (gb->tima_reload_state != GB_TIMA_RELOADED) {
@ -456,7 +458,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
case GB_IO_LCDC: case GB_IO_LCDC:
if ((value & 0x80) && !(gb->io_registers[GB_IO_LCDC] & 0x80)) { if ((value & 0x80) && !(gb->io_registers[GB_IO_LCDC] & 0x80)) {
gb->display_cycles = 0; gb->display_cycles = 0;
gb->first_scanline = true; gb->display_state = 0;
if (gb->frame_skip_state == GB_FRAMESKIP_SECOND_FRAME_RENDERED) { if (gb->frame_skip_state == GB_FRAMESKIP_SECOND_FRAME_RENDERED) {
gb->frame_skip_state = GB_FRAMESKIP_LCD_TURNED_ON; gb->frame_skip_state = GB_FRAMESKIP_LCD_TURNED_ON;
} }
@ -464,6 +466,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
else if (!(value & 0x80) && (gb->io_registers[GB_IO_LCDC] & 0x80)) { else if (!(value & 0x80) && (gb->io_registers[GB_IO_LCDC] & 0x80)) {
/* Sync after turning off LCD */ /* Sync after turning off LCD */
GB_timing_sync(gb); GB_timing_sync(gb);
GB_lcd_off(gb);
} }
/* Writing to LCDC might enable to disable the window, so we write it via GB_window_related_write */ /* Writing to LCDC might enable to disable the window, so we write it via GB_window_related_write */
GB_window_related_write(gb, addr & 0xFF, value); GB_window_related_write(gb, addr & 0xFF, value);
@ -481,6 +484,8 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
gb->io_registers[GB_IO_STAT] |= value & ~7; gb->io_registers[GB_IO_STAT] |= value & ~7;
/* Set unused bit to 1 */ /* Set unused bit to 1 */
gb->io_registers[GB_IO_STAT] |= 0x80; gb->io_registers[GB_IO_STAT] |= 0x80;
GB_STAT_update(gb);
return; return;
case GB_IO_DIV: case GB_IO_DIV:

View File

@ -57,7 +57,7 @@ void GB_timing_sync(GB_gameboy_t *gb)
return; return;
} }
/* Prevent syncing if not enough time has passed.*/ /* Prevent syncing if not enough time has passed.*/
if (gb->cycles_since_last_sync < LCDC_PERIOD / 8) return; if (gb->cycles_since_last_sync < LCDC_PERIOD / 4) return;
uint64_t target_nanoseconds = gb->cycles_since_last_sync * 1000000000LL / 2 / GB_get_clock_rate(gb); /* / 2 because we use 8MHz units */ uint64_t target_nanoseconds = gb->cycles_since_last_sync * 1000000000LL / 2 / GB_get_clock_rate(gb); /* / 2 because we use 8MHz units */
int64_t nanoseconds = get_nanoseconds(); int64_t nanoseconds = get_nanoseconds();
@ -136,7 +136,7 @@ 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)
{ {
GB_STATE_MACHINE(gb, div, cycles) { 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);
} }

View File

@ -18,7 +18,7 @@ enum {
#define GB_HALT_VALUE (0xFFFF) #define GB_HALT_VALUE (0xFFFF)
#define GB_SLEEP(gb, unit, state, cycles) do {\ #define GB_SLEEP(gb, unit, state, cycles) do {\
(gb)->unit##_cycles -= cycles; \ (gb)->unit##_cycles -= (cycles) * __state_machine_divisor; \
if ((gb)->unit##_cycles <= 0) {\ if ((gb)->unit##_cycles <= 0) {\
(gb)->unit##_state = state;\ (gb)->unit##_state = state;\
return;\ return;\
@ -28,7 +28,8 @@ enum {
#define GB_HALT(gb, unit) (gb)->unit##_cycles = GB_HALT_VALUE #define GB_HALT(gb, unit) (gb)->unit##_cycles = GB_HALT_VALUE
#define GB_STATE_MACHINE(gb, unit, cycles) \ #define GB_STATE_MACHINE(gb, unit, cycles, divisor) \
static const int __state_machine_divisor = divisor;\
(gb)->unit##_cycles += cycles; \ (gb)->unit##_cycles += cycles; \
if ((gb)->unit##_cycles <= 0 || (gb)->unit##_cycles == GB_HALT_VALUE) {\ if ((gb)->unit##_cycles <= 0 || (gb)->unit##_cycles == GB_HALT_VALUE) {\
return;\ return;\

View File

@ -1337,30 +1337,27 @@ static GB_opcode_t *opcodes[256] = {
}; };
void GB_cpu_run(GB_gameboy_t *gb) void GB_cpu_run(GB_gameboy_t *gb)
{ {
gb->vblank_just_occured = false;
if (gb->hdma_on) { if (gb->hdma_on) {
GB_advance_cycles(gb, 4); GB_advance_cycles(gb, 4);
return; return;
} }
if (gb->stopped) {
GB_advance_cycles(gb, 64);
return;
}
gb->vblank_just_occured = false;
if (gb->halted) { if (gb->halted) {
gb->display_hack = 1;
GB_advance_cycles(gb, 2); GB_advance_cycles(gb, 2);
} }
uint8_t interrupt_queue = gb->interrupt_enable & gb->io_registers[GB_IO_IF] & 0x1F; uint8_t interrupt_queue = gb->interrupt_enable & gb->io_registers[GB_IO_IF] & 0x1F;
if (gb->halted) { if (gb->halted) {
gb->display_hack = 2;
GB_advance_cycles(gb, 2); GB_advance_cycles(gb, 2);
gb->display_hack = 0;
} }
gb->io_registers[GB_IO_IF] |= gb->future_interrupts;
gb->future_interrupts = 0;
bool effecitve_ime = gb->ime; bool effecitve_ime = gb->ime;
if (gb->ime_toggle) { if (gb->ime_toggle) {
gb->ime = !gb->ime; gb->ime = !gb->ime;
@ -1381,9 +1378,7 @@ void GB_cpu_run(GB_gameboy_t *gb)
interrupt_queue = gb->interrupt_enable; interrupt_queue = gb->interrupt_enable;
GB_advance_cycles(gb, 4); GB_advance_cycles(gb, 4);
GB_write_memory(gb, gb->registers[GB_REGISTER_SP], (gb->pc) & 0xFF); GB_write_memory(gb, gb->registers[GB_REGISTER_SP], (gb->pc) & 0xFF);
interrupt_queue &= (gb->io_registers[GB_IO_IF] | gb->future_interrupts) & 0x1F; interrupt_queue &= (gb->io_registers[GB_IO_IF]) & 0x1F;
gb->io_registers[GB_IO_IF] |= gb->future_interrupts;
gb->future_interrupts = 0;
GB_advance_cycles(gb, 4); GB_advance_cycles(gb, 4);
if (interrupt_queue) { if (interrupt_queue) {
@ -1402,7 +1397,7 @@ void GB_cpu_run(GB_gameboy_t *gb)
GB_debugger_call_hook(gb, call_addr); GB_debugger_call_hook(gb, call_addr);
} }
/* Run mode */ /* Run mode */
else if(!gb->halted && !gb->stopped) { else if(!gb->halted) {
uint8_t opcode = GB_read_memory(gb, gb->pc++); uint8_t opcode = GB_read_memory(gb, gb->pc++);
if (gb->halt_bug) { if (gb->halt_bug) {
gb->pc--; gb->pc--;