More accurate FPS capping that tracks time correctly even when the screen is off. Should also support restarting the LCD during blank to increase FPS to 63.

This commit is contained in:
Lior Halphon 2017-04-21 16:00:53 +03:00
parent fb55c35f87
commit c766704267
6 changed files with 87 additions and 57 deletions

View File

@ -1,14 +1,9 @@
#include <stdbool.h>
#include <unistd.h>
#include <sys/time.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include "gb.h"
#ifdef _WIN32
#define _WIN32_WINNT 0x0500
#include <Windows.h>
#endif
/*
Each line is 456 cycles, approximately:
@ -198,46 +193,12 @@ static uint32_t get_pixel(GB_gameboy_t *gb, uint8_t x, uint8_t y)
return gb->background_palettes_rgb[(attributes & 7) * 4 + background_pixel];
}
static int64_t get_nanoseconds(void)
static void display_vblank(GB_gameboy_t *gb)
{
#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
}
// Todo: FPS capping should not be related to vblank, as the display is not always on, and this causes "jumps"
// when switching the display on and off.
void display_vblank(GB_gameboy_t *gb)
{
/* Called every Gameboy vblank. Does FPS-capping and calls user's vblank callback if Turbo Mode allows. */
if (gb->turbo && !gb->turbo_dont_skip) {
int64_t nanoseconds = get_nanoseconds();
if (nanoseconds <= gb->last_vblank + FRAME_LENGTH) {
if (gb->turbo) {
if (GB_timing_sync_turbo(gb)) {
return;
}
gb->last_vblank = nanoseconds;
}
if (!gb->disable_rendering && (!(gb->io_registers[GB_IO_LCDC] & 0x80) || gb->stopped)) {
@ -247,16 +208,7 @@ void display_vblank(GB_gameboy_t *gb)
}
gb->vblank_callback(gb);
if (!gb->turbo) {
int64_t nanoseconds = get_nanoseconds();
if (labs((signed long)(nanoseconds - gb->last_vblank)) < FRAME_LENGTH ) {
nsleep(FRAME_LENGTH + gb->last_vblank - nanoseconds);
gb->last_vblank += FRAME_LENGTH;
}
else {
gb->last_vblank = nanoseconds;
}
}
GB_timing_sync(gb);
gb->vblank_just_occured = true;
}
@ -352,7 +304,6 @@ static void update_display_state(GB_gameboy_t *gb, uint8_t cycles)
/* Reset window rendering state */
gb->effective_window_enabled = false;
gb->effective_window_y = 0xFF;
display_vblank(gb);
}
/* Entered VBlank state, update STAT and IF */
@ -365,6 +316,7 @@ static void update_display_state(GB_gameboy_t *gb, uint8_t cycles)
if (gb->io_registers[GB_IO_STAT] & 0x20) {
gb->stat_interrupt_line = true;
}
display_vblank(gb);
}
/* Handle STAT changes for lines 0-143 */

View File

@ -574,7 +574,6 @@ void GB_reset(GB_gameboy_t *gb)
gb->mbc_rom_bank = 1;
gb->last_rtc_second = time(NULL);
gb->last_vblank = clock();
gb->cgb_ram_bank = 1;
gb->io_registers[GB_IO_JOYP] = 0xF;
gb->io_registers[GB_IO_OBP0] = gb->io_registers[GB_IO_OBP1] = 0xFF;

View File

@ -325,7 +325,7 @@ struct GB_gameboy_internal_s {
/* Timing */
GB_SECTION(timing,
int64_t last_vblank;
GB_PADDING(int64_t, last_vblank);
uint32_t display_cycles;
uint32_t div_cycles;
GB_PADDING(uint32_t, tima_cycles);
@ -394,12 +394,16 @@ struct GB_gameboy_internal_s {
uint32_t *screen;
GB_sample_t *audio_buffer;
bool keys[GB_KEY_MAX];
/* Timing */
uint64_t last_sync;
uint64_t cycles_since_last_sync;
/* Audio Specific */
/* Audio */
unsigned int buffer_size;
unsigned int sample_rate;
unsigned int audio_position;
bool audio_stream_started; // detects first copy request to minimize lag
bool audio_stream_started; /* detects first copy request to minimize lag */
volatile bool audio_copy_in_progress;
volatile bool apu_lock;
@ -416,6 +420,7 @@ struct GB_gameboy_internal_s {
GB_rumble_callback_t rumble_callback;
GB_serial_transfer_start_callback_t serial_transfer_start_callback;
GB_serial_transfer_end_callback_t serial_transfer_end_callback;
/* IR */
long cycles_since_ir_change;
long cycles_since_input_ir_change;

View File

@ -440,6 +440,10 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
/* Todo: verify this. */
gb->display_cycles = gb->cgb_double_speed? -2 : -4;
}
else if (!(value & 0x80) && (gb->io_registers[GB_IO_LCDC] & 0x80)) {
/* Sync after turning off LCD */
GB_timing_sync(gb);
}
gb->io_registers[GB_IO_LCDC] = value;
return;

View File

@ -1,4 +1,70 @@
#include "gb.h"
#ifdef _WIN32
#define _WIN32_WINNT 0x0500
#include <Windows.h>
#else
#include <sys/time.h>
#endif
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 + FRAME_LENGTH) {
return true;
}
gb->last_sync = nanoseconds;
}
return false;
}
void GB_timing_sync(GB_gameboy_t *gb)
{
if (gb->turbo) return;
/* Prevent syncing if not enough time has passed.*/
if (gb->cycles_since_last_sync < LCDC_PERIOD / 4) return;
uint64_t target_nanoseconds = gb->cycles_since_last_sync * FRAME_LENGTH / LCDC_PERIOD;
int64_t nanoseconds = get_nanoseconds();
if (labs((signed long)(nanoseconds - gb->last_sync)) < target_nanoseconds ) {
nsleep(target_nanoseconds + gb->last_sync - nanoseconds);
gb->last_sync += target_nanoseconds;
}
else {
gb->last_sync = nanoseconds;
}
gb->cycles_since_last_sync = 0;
}
static void GB_ir_run(GB_gameboy_t *gb)
{
@ -69,6 +135,7 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles)
gb->apu.apu_cycles += cycles;
gb->cycles_since_ir_change += cycles;
gb->cycles_since_input_ir_change += cycles;
gb->cycles_since_last_sync += cycles;
GB_dma_run(gb);
GB_hdma_run(gb);
GB_apu_run(gb);

View File

@ -7,6 +7,9 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles);
void GB_set_internal_div_counter(GB_gameboy_t *gb, uint32_t value);
void GB_rtc_run(GB_gameboy_t *gb);
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,