diff --git a/src/engine/platform/sound/gb/gb.h b/src/engine/platform/sound/gb/gb.h index eb29a02f8..0a2baa6ae 100644 --- a/src/engine/platform/sound/gb/gb.h +++ b/src/engine/platform/sound/gb/gb.h @@ -9,6 +9,7 @@ #include "gb_struct_def.h" #include "apu.h" +#include "timing.h" #define GB_STRUCT_VERSION 13 diff --git a/src/engine/platform/sound/gb/timing.c b/src/engine/platform/sound/gb/timing.c new file mode 100644 index 000000000..7b79b7259 --- /dev/null +++ b/src/engine/platform/sound/gb/timing.c @@ -0,0 +1,407 @@ +#include "gb.h" +#ifdef _WIN32 +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0500 +#endif +#include +#else +#include +#endif + +static const unsigned GB_TAC_TRIGGER_BITS[] = {512, 8, 32, 128}; + +#ifndef GB_DISABLE_TIMEKEEPING +static int64_t get_nanoseconds(void) +{ +#ifndef _WIN32 + struct timeval now; + gettimeofday(&now, NULL); + return (now.tv_usec) * 1000 + now.tv_sec * 1000000000L; +#else + FILETIME time; + GetSystemTimeAsFileTime(&time); + return (((int64_t)time.dwHighDateTime << 32) | time.dwLowDateTime) * 100L; +#endif +} + +static void nsleep(uint64_t nanoseconds) +{ +#ifndef _WIN32 + struct timespec sleep = {0, nanoseconds}; + nanosleep(&sleep, NULL); +#else + HANDLE timer; + LARGE_INTEGER time; + timer = CreateWaitableTimer(NULL, true, NULL); + time.QuadPart = -(nanoseconds / 100L); + SetWaitableTimer(timer, &time, 0, NULL, NULL, false); + WaitForSingleObject(timer, INFINITE); + CloseHandle(timer); +#endif +} + +bool GB_timing_sync_turbo(GB_gameboy_t *gb) +{ + if (!gb->turbo_dont_skip) { + int64_t nanoseconds = get_nanoseconds(); + if (nanoseconds <= gb->last_sync + (1000000000LL * LCDC_PERIOD / GB_get_clock_rate(gb))) { + return true; + } + gb->last_sync = nanoseconds; + } + return false; +} + +void GB_timing_sync(GB_gameboy_t *gb) +{ + if (gb->turbo) { + gb->cycles_since_last_sync = 0; + return; + } + /* Prevent syncing if not enough time has passed.*/ + if (gb->cycles_since_last_sync < LCDC_PERIOD / 3) return; + + 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 time_to_sleep = target_nanoseconds + gb->last_sync - nanoseconds; + if (time_to_sleep > 0 && time_to_sleep < LCDC_PERIOD * 1200000000LL / GB_get_clock_rate(gb)) { // +20% to be more forgiving + nsleep(time_to_sleep); + gb->last_sync += target_nanoseconds; + } + else { + if (time_to_sleep < 0 && -time_to_sleep < LCDC_PERIOD * 1200000000LL / GB_get_clock_rate(gb)) { + // We're running a bit too slow, but the difference is small enough, + // just skip this sync and let it even out + return; + } + gb->last_sync = nanoseconds; + } + + gb->cycles_since_last_sync = 0; + if (gb->update_input_hint_callback) { + gb->update_input_hint_callback(gb); + } +} +#else + +bool GB_timing_sync_turbo(GB_gameboy_t *gb) +{ + return false; +} + +void GB_timing_sync(GB_gameboy_t *gb) +{ +} + +#endif + +#define IR_DECAY 31500 +#define IR_THRESHOLD 19900 +#define IR_MAX IR_THRESHOLD * 2 + IR_DECAY + +static void GB_ir_run(GB_gameboy_t *gb, uint32_t cycles) +{ + if (gb->model == GB_MODEL_AGB) return; + if (gb->infrared_input || gb->cart_ir || (gb->io_registers[GB_IO_RP] & 1)) { + gb->ir_sensor += cycles; + if (gb->ir_sensor > IR_MAX) { + gb->ir_sensor = IR_MAX; + } + + gb->effective_ir_input = gb->ir_sensor >= IR_THRESHOLD && gb->ir_sensor <= IR_THRESHOLD + IR_DECAY; + } + else { + if (gb->ir_sensor <= cycles) { + gb->ir_sensor = 0; + } + else { + gb->ir_sensor -= cycles; + } + gb->effective_ir_input = false; + } + +} + +static void advance_tima_state_machine(GB_gameboy_t *gb) +{ + if (gb->tima_reload_state == GB_TIMA_RELOADED) { + gb->tima_reload_state = GB_TIMA_RUNNING; + } + else if (gb->tima_reload_state == GB_TIMA_RELOADING) { + gb->io_registers[GB_IO_IF] |= 4; + gb->tima_reload_state = GB_TIMA_RELOADED; + } +} + +static void increase_tima(GB_gameboy_t *gb) +{ + gb->io_registers[GB_IO_TIMA]++; + if (gb->io_registers[GB_IO_TIMA] == 0) { + gb->io_registers[GB_IO_TIMA] = gb->io_registers[GB_IO_TMA]; + gb->tima_reload_state = GB_TIMA_RELOADING; + } +} + +static void GB_set_internal_div_counter(GB_gameboy_t *gb, uint16_t value) +{ + /* TIMA increases when a specific high-bit becomes a low-bit. */ + value &= INTERNAL_DIV_CYCLES - 1; + uint16_t triggers = gb->div_counter & ~value; + if ((gb->io_registers[GB_IO_TAC] & 4) && (triggers & GB_TAC_TRIGGER_BITS[gb->io_registers[GB_IO_TAC] & 3])) { + increase_tima(gb); + } + + /* TODO: Can switching to double speed mode trigger an event? */ + uint16_t apu_bit = gb->cgb_double_speed? 0x2000 : 0x1000; + if (triggers & apu_bit) { + GB_apu_run(gb); + GB_apu_div_event(gb); + } + else { + uint16_t secondary_triggers = ~gb->div_counter & value; + if (secondary_triggers & apu_bit) { + GB_apu_run(gb); + GB_apu_div_secondary_event(gb); + } + } + gb->div_counter = value; +} + +static void GB_timers_run(GB_gameboy_t *gb, uint8_t cycles) +{ + if (gb->stopped) { + if (GB_is_cgb(gb)) { + 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); + GB_STATE(gb, div, 3); + } + + GB_set_internal_div_counter(gb, 0); +main: + GB_SLEEP(gb, div, 1, 3); + while (true) { + advance_tima_state_machine(gb); + GB_set_internal_div_counter(gb, gb->div_counter + 4); + gb->apu.apu_cycles += 4 << !gb->cgb_double_speed; + GB_SLEEP(gb, div, 2, 4); + } + + /* Todo: This is ugly to allow compatibility with 0.11 save states. Fix me when breaking save compatibility */ + { + div3: + /* Compensate for lack of prefetch emulation, as well as DIV's internal initial value */ + GB_set_internal_div_counter(gb, 8); + goto main; + } +} + +static void advance_serial(GB_gameboy_t *gb, uint8_t cycles) +{ + if (gb->printer.command_state || gb->printer.bits_received) { + gb->printer.idle_time += cycles; + } + if (gb->serial_length == 0) { + gb->serial_cycles += cycles; + return; + } + + while (cycles > gb->serial_length) { + advance_serial(gb, gb->serial_length); + cycles -= gb->serial_length; + } + + uint16_t previous_serial_cycles = gb->serial_cycles; + gb->serial_cycles += cycles; + if ((gb->serial_cycles & gb->serial_length) != (previous_serial_cycles & gb->serial_length)) { + gb->serial_count++; + if (gb->serial_count == 8) { + gb->serial_length = 0; + gb->serial_count = 0; + gb->io_registers[GB_IO_SC] &= ~0x80; + gb->io_registers[GB_IO_IF] |= 8; + } + + gb->io_registers[GB_IO_SB] <<= 1; + + if (gb->serial_transfer_bit_end_callback) { + gb->io_registers[GB_IO_SB] |= gb->serial_transfer_bit_end_callback(gb); + } + else { + gb->io_registers[GB_IO_SB] |= 1; + } + + if (gb->serial_length) { + /* Still more bits to send */ + if (gb->serial_transfer_bit_start_callback) { + gb->serial_transfer_bit_start_callback(gb, gb->io_registers[GB_IO_SB] & 0x80); + } + } + + } + return; + +} + +static void GB_rtc_run(GB_gameboy_t *gb, uint8_t cycles) +{ + if (gb->cartridge_type->mbc_type != GB_HUC3 && !gb->cartridge_type->has_rtc) return; + gb->rtc_cycles += cycles; + time_t current_time = 0; + + switch (gb->rtc_mode) { + case GB_RTC_MODE_SYNC_TO_HOST: + // Sync in a 1/32s resolution + if (gb->rtc_cycles < GB_get_unmultiplied_clock_rate(gb) / 16) return; + gb->rtc_cycles -= GB_get_unmultiplied_clock_rate(gb) / 16; + current_time = time(NULL); + break; + case GB_RTC_MODE_ACCURATE: + if (gb->cartridge_type->mbc_type != GB_HUC3 && (gb->rtc_real.high & 0x40)) { + gb->rtc_cycles -= cycles; + return; + } + if (gb->rtc_cycles < GB_get_unmultiplied_clock_rate(gb) * 2) return; + gb->rtc_cycles -= GB_get_unmultiplied_clock_rate(gb) * 2; + current_time = gb->last_rtc_second + 1; + break; + } + + if (gb->cartridge_type->mbc_type == GB_HUC3) { + while (gb->last_rtc_second / 60 < current_time / 60) { + gb->last_rtc_second += 60; + gb->huc3_minutes++; + if (gb->huc3_minutes == 60 * 24) { + gb->huc3_days++; + gb->huc3_minutes = 0; + } + } + return; + } + bool running = false; + if (gb->cartridge_type->mbc_type == GB_TPP1) { + running = gb->tpp1_mr4 & 0x4; + } + else { + running = (gb->rtc_real.high & 0x40) == 0; + } + + if (running) { /* is timer running? */ + while (gb->last_rtc_second + 60 * 60 * 24 < current_time) { + gb->last_rtc_second += 60 * 60 * 24; + if (gb->cartridge_type->mbc_type == GB_TPP1) { + if (++gb->rtc_real.tpp1.weekday == 7) { + gb->rtc_real.tpp1.weekday = 0; + if (++gb->rtc_real.tpp1.weeks == 0) { + gb->tpp1_mr4 |= 8; /* Overflow bit */ + } + } + } + else if (++gb->rtc_real.days == 0) { + if (gb->rtc_real.high & 1) { /* Bit 8 of days*/ + gb->rtc_real.high |= 0x80; /* Overflow bit */ + } + + gb->rtc_real.high ^= 1; + } + } + + while (gb->last_rtc_second < current_time) { + gb->last_rtc_second++; + if (++gb->rtc_real.seconds != 60) continue; + gb->rtc_real.seconds = 0; + + if (++gb->rtc_real.minutes != 60) continue; + gb->rtc_real.minutes = 0; + + if (gb->cartridge_type->mbc_type == GB_TPP1) { + if (++gb->rtc_real.tpp1.hours != 24) continue; + gb->rtc_real.tpp1.hours = 0; + if (++gb->rtc_real.tpp1.weekday != 7) continue; + gb->rtc_real.tpp1.weekday = 0; + if (++gb->rtc_real.tpp1.weeks == 0) { + gb->tpp1_mr4 |= 8; /* Overflow bit */ + } + } + else { + if (++gb->rtc_real.hours != 24) continue; + gb->rtc_real.hours = 0; + + if (++gb->rtc_real.days != 0) continue; + + if (gb->rtc_real.high & 1) { /* Bit 8 of days*/ + gb->rtc_real.high |= 0x80; /* Overflow bit */ + } + + gb->rtc_real.high ^= 1; + } + } + } +} + + +void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) +{ + gb->apu.pcm_mask[0] = gb->apu.pcm_mask[1] = 0xFF; // Sort of hacky, but too many cross-component interactions to do it right + // Affected by speed boost + gb->dma_cycles += cycles; + + GB_timers_run(gb, cycles); + if (!gb->stopped) { + advance_serial(gb, cycles); // TODO: Verify what happens in STOP mode + } + + gb->debugger_ticks += cycles; + + if (!gb->cgb_double_speed) { + cycles <<= 1; + } + + // Not affected by speed boost + if (gb->io_registers[GB_IO_LCDC] & 0x80) { + gb->double_speed_alignment += cycles; + } + gb->hdma_cycles += cycles; + gb->apu_output.sample_cycles += cycles; + gb->cycles_since_last_sync += cycles; + gb->cycles_since_run += cycles; + + gb->rumble_on_cycles += gb->rumble_strength & 3; + gb->rumble_off_cycles += (gb->rumble_strength & 3) ^ 3; + + if (!gb->stopped) { // TODO: Verify what happens in STOP mode + GB_dma_run(gb); + GB_hdma_run(gb); + } + GB_apu_run(gb); + GB_display_run(gb, cycles); + GB_ir_run(gb, cycles); + GB_rtc_run(gb, cycles); +} + +/* + This glitch is based on the expected results of mooneye-gb rapid_toggle test. + This glitch happens because how TIMA is increased, see GB_set_internal_div_counter. + According to GiiBiiAdvance, GBC's behavior is different, but this was not tested or implemented. +*/ +void GB_emulate_timer_glitch(GB_gameboy_t *gb, uint8_t old_tac, uint8_t new_tac) +{ + /* Glitch only happens when old_tac is enabled. */ + if (!(old_tac & 4)) return; + + unsigned old_clocks = GB_TAC_TRIGGER_BITS[old_tac & 3]; + unsigned new_clocks = GB_TAC_TRIGGER_BITS[new_tac & 3]; + + /* The bit used for overflow testing must have been 1 */ + if (gb->div_counter & old_clocks) { + /* And now either the timer must be disabled, or the new bit used for overflow testing be 0. */ + if (!(new_tac & 4) || gb->div_counter & new_clocks) { + increase_tima(gb); + } + } +} diff --git a/src/engine/platform/sound/gb/timing.h b/src/engine/platform/sound/gb/timing.h new file mode 100644 index 000000000..07e04734b --- /dev/null +++ b/src/engine/platform/sound/gb/timing.h @@ -0,0 +1,40 @@ +#ifndef timing_h +#define timing_h +#include "gb_struct_def.h" + +#ifdef GB_INTERNAL +void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles); +void GB_emulate_timer_glitch(GB_gameboy_t *gb, uint8_t old_tac, uint8_t new_tac); +bool GB_timing_sync_turbo(GB_gameboy_t *gb); /* Returns true if should skip frame */ +void GB_timing_sync(GB_gameboy_t *gb); + +enum { + GB_TIMA_RUNNING = 0, + GB_TIMA_RELOADING = 1, + GB_TIMA_RELOADED = 2 +}; + + +#define GB_SLEEP(gb, unit, state, cycles) do {\ + (gb)->unit##_cycles -= (cycles) * __state_machine_divisor; \ + if ((gb)->unit##_cycles <= 0) {\ + (gb)->unit##_state = state;\ + return;\ + unit##state:; \ + }\ +} while (0) + +#define GB_STATE_MACHINE(gb, unit, cycles, divisor) \ +static const int __state_machine_divisor = divisor;\ +(gb)->unit##_cycles += cycles; \ +if ((gb)->unit##_cycles <= 0) {\ + return;\ +}\ +switch ((gb)->unit##_state) +#endif + +#define GB_STATE(gb, unit, state) case state: goto unit##state + +#define GB_UNIT(unit) int32_t unit##_cycles, unit##_state + +#endif /* timing_h */