rg_display+rg_system: Removed update mode setting and improved frame pacing

Now that partial update is no longer glitchy, there is no reason to ever disable it.

However, the `Update` setting was also indirectly doing one more thing: It imposed a frameskip of 1 when `Full` was selected.

This did prevent stuttering in some games.

But that's an indirect effect, and even I keep forgetting that it's the reason why it can counter-intuitively improve performance.

So I think it's better to handle the frameskip directly instead.
This commit is contained in:
Alex Duchesne 2024-02-22 00:04:03 -05:00
parent 35db489f74
commit a44530c98f
15 changed files with 219 additions and 236 deletions

View File

@ -139,10 +139,6 @@ which allows high quality audio through headphones. You can switch to it in the
Retro-Go typically detects and resolves application crashes and freezes automatically. However, if you do
get stuck in a boot loop, you can hold `DOWN` while powering up the device to return to the launcher.
### Artifacts or tearing
Retro-Go uses partial screen updating to achieve a higher framerate and reduce tearing. This method isn't
perfect however, if you notice display issues or stuttering you can try changing the `Update` option.
### Sound quality
The volume isn't correctly attenuated on the GO, resulting in upper volume levels that are too loud and
lower levels that are distorted due to DAC resolution. A quick way to improve the audio is to cut one

View File

@ -31,7 +31,6 @@ static const char *SETTING_BACKLIGHT = "DispBacklight";
static const char *SETTING_SCALING = "DispScaling";
static const char *SETTING_FILTER = "DispFilter";
static const char *SETTING_ROTATION = "DispRotation";
static const char *SETTING_UPDATE = "DispUpdate";
static const char *SETTING_BORDER = "DispBorder";
static const char *SETTING_CUSTOM_WIDTH = "DispCustomWidth";
static const char *SETTING_CUSTOM_HEIGHT = "DispCustomHeight";
@ -315,7 +314,7 @@ static inline unsigned blend_pixels(unsigned a, unsigned b)
static inline void write_update(const rg_video_update_t *update)
{
int64_t time_start = rg_system_timer();
const int64_t time_start = rg_system_timer();
const int screen_width = display.screen.width;
const int screen_height = display.screen.height;
@ -340,7 +339,7 @@ static inline void write_update(const rg_video_update_t *update)
union {const uint8_t *u8; const uint16_t *u16; } buffer = {update->buffer + display.source.offset};
const uint16_t *palette = update->palette;
bool partial = config.update_mode == RG_DISPLAY_UPDATE_PARTIAL;
bool partial = true; // config.update_mode == RG_DISPLAY_UPDATE_PARTIAL;
int lines_per_buffer = LCD_BUFFER_LENGTH / draw_width;
int lines_remaining = draw_height;
@ -482,8 +481,7 @@ static inline void write_update(const rg_video_update_t *update)
lines_remaining -= lines_to_copy;
}
counters.lastFullFrame = lines_updated > display.screen.height * 0.75;
if (counters.lastFullFrame)
if (lines_updated > display.screen.height * 0.80)
counters.fullFrames++;
counters.totalFrames++;
counters.busyTime += rg_system_timer() - time_start;
@ -644,21 +642,9 @@ const rg_display_t *rg_display_get_info(void)
return &display;
}
const rg_display_counters_t *rg_display_get_counters(void)
rg_display_counters_t rg_display_get_counters(void)
{
return &counters;
}
void rg_display_set_update_mode(display_update_t update_mode)
{
config.update_mode = RG_MIN(RG_MAX(0, update_mode), RG_DISPLAY_UPDATE_COUNT - 1);
rg_settings_set_number(NS_APP, SETTING_UPDATE, config.update_mode);
display.changed = true;
}
display_update_t rg_display_get_update_mode(void)
{
return config.update_mode;
return counters;
}
void rg_display_set_scaling(display_scaling_t scaling)
@ -940,7 +926,6 @@ void rg_display_init(void)
.scaling = rg_settings_get_number(NS_APP, SETTING_SCALING, RG_DISPLAY_SCALING_FULL),
.filter = rg_settings_get_number(NS_APP, SETTING_FILTER, RG_DISPLAY_FILTER_BOTH),
.rotation = rg_settings_get_number(NS_APP, SETTING_ROTATION, RG_DISPLAY_ROTATION_AUTO),
.update_mode = rg_settings_get_number(NS_APP, SETTING_UPDATE, RG_DISPLAY_UPDATE_PARTIAL),
.border_file = rg_settings_get_string(NS_APP, SETTING_BORDER, NULL),
.custom_width = rg_settings_get_number(NS_APP, SETTING_CUSTOM_WIDTH, 240),
.custom_height = rg_settings_get_number(NS_APP, SETTING_CUSTOM_HEIGHT, 240),

View File

@ -3,15 +3,6 @@
#include <stdbool.h>
#include <stdint.h>
typedef enum
{
RG_DISPLAY_UPDATE_PARTIAL = 0,
RG_DISPLAY_UPDATE_FULL,
// RG_DISPLAY_UPDATE_INTERLACE,
// RG_DISPLAY_UPDATE_SMART,
RG_DISPLAY_UPDATE_COUNT,
} display_update_t;
typedef enum
{
RG_DISPLAY_SCALING_OFF = 0, // No scaling, center image on screen
@ -73,7 +64,6 @@ typedef struct
display_rotation_t rotation;
display_scaling_t scaling;
display_filter_t filter;
display_update_t update_mode;
display_backlight_t backlight;
char *border_file;
int custom_width, custom_height;
@ -82,10 +72,8 @@ typedef struct
typedef struct
{
int32_t totalFrames;
int32_t partFrames;
int32_t fullFrames;
int64_t busyTime;
bool lastFullFrame;
} rg_display_counters_t;
typedef struct
@ -144,7 +132,7 @@ bool rg_display_save_frame(const char *filename, const rg_video_update_t *frame,
void rg_display_set_source_format(int width, int height, int crop_h, int crop_v, int stride, rg_pixel_flags_t format);
void rg_display_submit(const rg_video_update_t *update, uint32_t flags);
const rg_display_counters_t *rg_display_get_counters(void);
rg_display_counters_t rg_display_get_counters(void);
const rg_display_t *rg_display_get_info(void);
void rg_display_set_scaling(display_scaling_t scaling);
@ -155,8 +143,6 @@ void rg_display_set_rotation(display_rotation_t rotation);
display_rotation_t rg_display_get_rotation(void);
void rg_display_set_backlight(display_backlight_t percent);
display_backlight_t rg_display_get_backlight(void);
void rg_display_set_update_mode(display_update_t update);
display_update_t rg_display_get_update_mode(void);
void rg_display_set_border(const char *filename);
char *rg_display_get_border(void);
void rg_display_set_custom_width(int width);

View File

@ -588,10 +588,10 @@ void rg_gui_draw_status_bars(void)
if (!app->initialized || app->isLauncher)
return;
snprintf(header, max_len, "SPEED: %d%% (%d/%d) / BUSY: %d%%",
(int)round(stats.totalFPS / app->refreshRate * 100.f),
(int)round(stats.totalFPS - stats.skippedFPS),
(int)round(stats.totalFPS),
snprintf(header, max_len, "SPEED: %d%% (%d:%d) / BUSY: %d%%",
(int)round(stats.totalFPS / app->tickRate * 100.f),
(int)app->frameskip,
(int)round(stats.skippedFPS),
(int)round(stats.busyPercent));
if (app->romPath && strlen(app->romPath) > max_len - 1)
@ -1215,29 +1215,6 @@ static rg_gui_event_t custom_height_cb(rg_gui_option_t *option, rg_gui_event_t e
return RG_DIALOG_VOID;
}
static rg_gui_event_t update_mode_update_cb(rg_gui_option_t *option, rg_gui_event_t event)
{
int max = RG_DISPLAY_UPDATE_COUNT - 1;
int mode = rg_display_get_update_mode();
int prev_mode = mode;
if (event == RG_DIALOG_PREV && --mode < 0)
mode = max; // 0;
if (event == RG_DIALOG_NEXT && ++mode > max)
mode = 0; // max;
if (mode != prev_mode)
rg_display_set_update_mode(mode);
if (mode == RG_DISPLAY_UPDATE_PARTIAL)
strcpy(option->value, "Partial");
if (mode == RG_DISPLAY_UPDATE_FULL)
strcpy(option->value, "Full ");
// if (mode == RG_DISPLAY_UPDATE_INTERLACE) strcpy(option->value, "Interlace");
return RG_DIALOG_VOID;
}
static rg_gui_event_t speedup_update_cb(rg_gui_option_t *option, rg_gui_event_t event)
{
rg_app_t *app = rg_system_get_app();
@ -1347,13 +1324,12 @@ void rg_gui_options_menu(void)
// App settings that are shown only inside a game
else
{
*opt++ = (rg_gui_option_t){0, "Scaling", "-", RG_DIALOG_FLAG_NORMAL, &scaling_update_cb};
*opt++ = (rg_gui_option_t){0, " Width", "-", RG_DIALOG_FLAG_HIDDEN, &custom_width_cb};
*opt++ = (rg_gui_option_t){0, " Height", "-", RG_DIALOG_FLAG_HIDDEN, &custom_height_cb};
*opt++ = (rg_gui_option_t){0, "Filter ", "-", RG_DIALOG_FLAG_NORMAL, &filter_update_cb};
*opt++ = (rg_gui_option_t){0, "Update ", "-", RG_DIALOG_FLAG_NORMAL, &update_mode_update_cb};
*opt++ = (rg_gui_option_t){0, "Border ", "-", RG_DIALOG_FLAG_NORMAL, &border_update_cb};
*opt++ = (rg_gui_option_t){0, "Speed ", "-", RG_DIALOG_FLAG_NORMAL, &speedup_update_cb};
*opt++ = (rg_gui_option_t){0, "Scaling", "-", RG_DIALOG_FLAG_NORMAL, &scaling_update_cb};
*opt++ = (rg_gui_option_t){0, " Width", "-", RG_DIALOG_FLAG_HIDDEN, &custom_width_cb};
*opt++ = (rg_gui_option_t){0, " Height", "-", RG_DIALOG_FLAG_HIDDEN, &custom_height_cb};
*opt++ = (rg_gui_option_t){0, "Filter", "-", RG_DIALOG_FLAG_NORMAL, &filter_update_cb};
*opt++ = (rg_gui_option_t){0, "Border", "-", RG_DIALOG_FLAG_NORMAL, &border_update_cb};
*opt++ = (rg_gui_option_t){0, "Speed", "-", RG_DIALOG_FLAG_NORMAL, &speedup_update_cb};
}
size_t extra_options = get_dialog_items_count(app->options);

View File

@ -81,7 +81,7 @@ static rg_app_t app;
static logbuf_t logbuf;
static rg_task_t tasks[8];
static int ledValue = -1;
static int wdtCounter = 0;
static int64_t watchdogTimer = INT64_MAX;
static bool exitCalled = false;
static const char *SETTING_BOOT_NAME = "BootName";
@ -90,7 +90,7 @@ static const char *SETTING_BOOT_FLAGS = "BootFlags";
static const char *SETTING_TIMEZONE = "Timezone";
#define WDT_TIMEOUT 8000000
#define WDT_RELOAD(val) wdtCounter = (val)
#define WDT_RELOAD(val) watchdogTimer = rg_system_timer() + (val)
#define logbuf_putc(buf, c) (buf)->buffer[(buf)->cursor++] = c, (buf)->cursor %= RG_LOGBUF_SIZE;
#define logbuf_puts(buf, str) for (const char *ptr = str; *ptr; ptr++) logbuf_putc(buf, *ptr);
@ -138,8 +138,8 @@ static void update_statistics(void)
static counters_t counters = {0};
const counters_t previous = counters;
rg_display_counters_t display = *rg_display_get_counters();
// rg_audio_counters_t audio = *rg_audio_get_counters();
rg_display_counters_t display = rg_display_get_counters();
// rg_audio_counters_t audio = rg_audio_get_counters();
counters.totalFrames = display.totalFrames;
counters.fullFrames = display.fullFrames;
@ -147,33 +147,34 @@ static void update_statistics(void)
counters.ticks = statistics.ticks;
counters.updateTime = rg_system_timer();
float elapsedTime = (counters.updateTime - previous.updateTime) / 1000000.f;
statistics.busyPercent = RG_MIN((counters.busyTime - previous.busyTime) / (elapsedTime * 1000000.f) * 100.f, 100.f);
statistics.totalFPS = (counters.ticks - previous.ticks) / elapsedTime;
statistics.skippedFPS = statistics.totalFPS - ((counters.totalFrames - previous.totalFrames) / elapsedTime);
statistics.fullFPS = (counters.fullFrames - previous.fullFrames) / elapsedTime;
if (counters.ticks && previous.ticks)
{
float totalTime = counters.updateTime - previous.updateTime;
float totalTimeSecs = totalTime / 1000000.f;
float busyTime = counters.busyTime - previous.busyTime;
float ticks = counters.ticks - previous.ticks;
// Since we sample semi-randomly, display and ticks could be out of sync.
// RG_MIN won't prevent bogus skippedFPS=1, but at least no -1...
float fullFrames = RG_MIN(counters.fullFrames - previous.fullFrames, ticks);
float frames = RG_MIN(counters.totalFrames - previous.totalFrames, ticks);
statistics.busyPercent = busyTime / totalTime * 100.f;
statistics.totalFPS = ticks / totalTimeSecs;
statistics.skippedFPS = (ticks - frames) / totalTimeSecs;
statistics.fullFPS = fullFrames / totalTimeSecs;
}
update_memory_statistics();
}
static void system_monitor_task(void *arg)
{
int64_t lastLoop = rg_system_timer();
int32_t numLoop = 0;
float batteryPercent = 0.f;
bool ledState = false;
// Give the app a few seconds to start before monitoring
rg_task_delay(2500);
WDT_RELOAD(WDT_TIMEOUT);
while (!exitCalled)
{
int loopTime_us = rg_system_timer() - lastLoop;
lastLoop = rg_system_timer();
rtcValue = time(NULL);
// Maybe we should *try* to wait for vsync before updating?
update_statistics();
if (rg_input_read_battery(&batteryPercent, NULL))
@ -198,30 +199,35 @@ static void system_monitor_task(void *arg)
(int)(statistics.fullFPS + 0.5f),
(int)(batteryPercent + 0.5f));
if ((wdtCounter -= loopTime_us) <= 0)
if (watchdogTimer <= rg_system_timer())
{
if ((lastLoop - statistics.lastTick) > WDT_TIMEOUT)
{
if (app.watchdog)
RG_PANIC("Application unresponsive!");
else
RG_LOGW("Application unresponsive!");
}
if (app.watchdog)
RG_PANIC("Application unresponsive!");
else
RG_LOGW("Application unresponsive!");
WDT_RELOAD(WDT_TIMEOUT);
}
#ifdef RG_ENABLE_PROFILING
if ((numLoop % 10) == 0)
rg_system_dump_profile();
#endif
// if ((numLoop % 300) == 299)
// {
// RG_LOGI("Saving system time");
// rg_system_save_time();
// }
// Auto frameskip
if (statistics.ticks > app.tickRate)
{
float speed = ((float)statistics.totalFPS / app.tickRate) * 100.f / app.speed;
// We don't fully go back to 0 frameskip because if we dip below 95% once, we're clearly
// borderline in power and going back to 0 is just asking for stuttering...
if (speed > 99.f && statistics.busyPercent < 90.f && app.frameskip > 1)
{
app.frameskip--;
RG_LOGI("Reduced frameskip to %d", app.frameskip);
}
else if (speed < 95.f && statistics.busyPercent > 90.f && app.frameskip < 4)
{
app.frameskip++;
RG_LOGI("Raised frameskip to %d", app.frameskip);
}
}
rg_task_delay(1000);
rtcValue = time(NULL);
numLoop++;
}
}
@ -302,8 +308,9 @@ rg_app_t *rg_system_init(int sampleRate, const rg_handlers_t *handlers, const rg
.bootFlags = 0,
.bootType = RG_RST_POWERON,
.speed = 1.f,
.refreshRate = 60,
.sampleRate = sampleRate,
.tickRate = 60,
.frameskip = 0,
.overclock = 0,
.watchdog = 1,
.logLevel = RG_LOG_INFO,
@ -562,7 +569,7 @@ void rg_system_tick(int busyTime)
statistics.lastTick = rg_system_timer();
statistics.busyTime += busyTime;
statistics.ticks++;
// WDT_RELOAD(WDT_TIMEOUT);
WDT_RELOAD(WDT_TIMEOUT);
}
IRAM_ATTR int64_t rg_system_timer(void)

View File

@ -4,6 +4,7 @@
extern "C" {
#endif
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
@ -155,8 +156,9 @@ typedef struct
uint32_t bootFlags;
uint32_t bootType;
float speed;
int refreshRate;
int sampleRate;
int tickRate;
int frameskip;
int overclock;
int watchdog;
int logLevel;
@ -243,6 +245,7 @@ rg_emu_state_t *rg_emu_get_states(const char *romPath, size_t slots);
#define RG_LOGE(x, ...) rg_system_log(RG_LOG_ERROR, RG_LOG_TAG, x, ## __VA_ARGS__)
#define RG_LOGW(x, ...) rg_system_log(RG_LOG_WARN, RG_LOG_TAG, x, ## __VA_ARGS__)
#define RG_LOGI(x, ...) rg_system_log(RG_LOG_INFO, RG_LOG_TAG, x, ## __VA_ARGS__)
#define RG_LOGU(x, ...) rg_system_log(RG_LOG_USER, RG_LOG_TAG, x, ## __VA_ARGS__)
#define RG_LOGD(x, ...) rg_system_log(RG_LOG_DEBUG, RG_LOG_TAG, x, ## __VA_ARGS__)
void __cyg_profile_func_enter(void *this_fn, void *call_site);

View File

@ -35,7 +35,6 @@ static rg_app_t *app;
static bool yfm_enabled = true;
static bool z80_enabled = true;
static bool sn76489_enabled = true;
static int frameskip = 3;
static FILE *savestate_fp = NULL;
static int savestate_errors = 0;
@ -43,7 +42,6 @@ static int savestate_errors = 0;
static const char *SETTING_YFM_EMULATION = "yfm_enable";
static const char *SETTING_Z80_EMULATION = "z80_enable";
static const char *SETTING_SN76489_EMULATION = "sn_enable";
static const char *SETTING_FRAMESKIP = "frameskip";
// --- MAIN
@ -156,20 +154,6 @@ static rg_gui_event_t z80_update_cb(rg_gui_option_t *option, rg_gui_event_t even
return RG_DIALOG_VOID;
}
static rg_gui_event_t frameskip_cb(rg_gui_option_t *option, rg_gui_event_t event)
{
if (event == RG_DIALOG_PREV || event == RG_DIALOG_NEXT)
{
frameskip += (event == RG_DIALOG_PREV) ? -1 : 1;
frameskip = RG_MAX(frameskip, 1);
rg_settings_set_number(NS_APP, SETTING_FRAMESKIP, frameskip);
}
sprintf(option->value, "%d", frameskip);
return RG_DIALOG_VOID;
}
static bool screenshot_handler(const char *filename, int width, int height)
{
return rg_display_save_frame(filename, currentUpdate, width, height);
@ -228,7 +212,6 @@ void app_main(void)
{1, "YFM emulation", "On", 1, &yfm_update_cb},
{1, "SN76489 emulation", "On", 1, &sn76489_update_cb},
{3, "Z80 emulation", "On", 1, &z80_update_cb},
{2, "Frameskip", "", 1, &frameskip_cb},
RG_DIALOG_END
};
@ -237,7 +220,6 @@ void app_main(void)
yfm_enabled = rg_settings_get_number(NS_APP, SETTING_YFM_EMULATION, 1);
sn76489_enabled = rg_settings_get_number(NS_APP, SETTING_SN76489_EMULATION, 0);
z80_enabled = rg_settings_get_number(NS_APP, SETTING_Z80_EMULATION, 1);
frameskip = rg_settings_get_number(NS_APP, SETTING_FRAMESKIP, frameskip);
updates[0].buffer = rg_alloc(320 * 240 + 64, MEM_FAST) + 32;
// updates[1].buffer = rg_alloc(320 * 240 + 64, MEM_FAST) + 32;
@ -271,6 +253,9 @@ void app_main(void)
rg_emu_load_state(app->saveSlot);
}
app->tickRate = 60;
app->frameskip = 3;
extern unsigned char gwenesis_vdp_regs[0x20];
extern unsigned int gwenesis_vdp_status;
extern unsigned short CRAM565[256];
@ -281,10 +266,11 @@ void app_main(void)
uint32_t keymap[8] = {RG_KEY_UP, RG_KEY_DOWN, RG_KEY_LEFT, RG_KEY_RIGHT, RG_KEY_A, RG_KEY_B, RG_KEY_SELECT, RG_KEY_START};
uint32_t joystick = 0, joystick_old;
uint32_t frames = 0;
RG_LOGI("rg_display_set_source_format()\n");
int skipFrames = 0;
RG_LOGI("emulation loop\n");
while (true)
{
@ -310,7 +296,8 @@ void app_main(void)
}
int64_t startTime = rg_system_timer();
bool drawFrame = (frames++ % frameskip) == 0;
bool drawFrame = skipFrames == 0;
bool slowFrame = false;
int lines_per_frame = REG1_PAL ? LINES_PER_FRAME_PAL : LINES_PER_FRAME_NTSC;
int hint_counter = gwenesis_vdp_regs[10];
@ -411,15 +398,33 @@ void app_main(void)
{
for (int i = 0; i < 256; ++i)
currentUpdate->palette[i] = (CRAM565[i] << 8) | (CRAM565[i] >> 8);
slowFrame = !rg_display_sync(false);
rg_display_submit(currentUpdate, 0);
}
int elapsed = rg_system_timer() - startTime;
rg_system_tick(elapsed);
rg_system_tick(rg_system_timer() - startTime);
if (yfm_enabled || z80_enabled) {
// TODO: Mix in gwenesis_sn76489_buffer
rg_audio_submit((void *)gwenesis_ym2612_buffer, AUDIO_BUFFER_LENGTH >> 1);
}
if (skipFrames == 0)
{
int frameTime = 1000000 / (app->tickRate * app->speed);
int elapsed = rg_system_timer() - startTime;
if (app->frameskip > 0)
skipFrames = app->frameskip;
else if (elapsed > frameTime + 1500) // Allow some jitter
skipFrames = (elapsed + frameTime / 2) / frameTime;
else if (drawFrame && slowFrame)
skipFrames = 1;
if (app->speed > 1.f)
skipFrames += 2;
}
else if (skipFrames > 0)
{
skipFrames--;
}
}
}

View File

@ -533,7 +533,7 @@ void app_main()
};
app = rg_system_init(AUDIO_SAMPLE_RATE, &handlers, options);
app->refreshRate = TICRATE;
app->tickRate = TICRATE;
update.buffer = rg_alloc(SCREENHEIGHT*SCREENWIDTH, MEM_FAST);

View File

@ -4,6 +4,7 @@
#include <gnuboy.h>
static int skipFrames = 20; // The 20 is to hide startup flicker in some games
static bool slowFrame = false;
static int video_time;
static int audio_time;
@ -195,6 +196,7 @@ static rg_gui_event_t rtc_update_cb(rg_gui_option_t *option, rg_gui_event_t even
static void video_callback(void *buffer)
{
int64_t startTime = rg_system_timer();
slowFrame = !rg_display_sync(false);
rg_display_submit(currentUpdate, 0);
video_time += rg_system_timer() - startTime;
}
@ -273,8 +275,8 @@ void gbc_main(void)
// Ready!
int joystick_old = -1;
int joystick = 0;
uint32_t joystick_old = -1;
uint32_t joystick = 0;
while (true)
{
@ -334,26 +336,25 @@ void gbc_main(void)
}
}
int elapsed = rg_system_timer() - startTime;
elapsed -= audio_time;
// Tick before submitting audio/syncing
rg_system_tick(rg_system_timer() - startTime - audio_time);
if (skipFrames == 0)
{
// This is all nonsense, we should sample lag on a per second basis or something...
int frameTime = 1000000 / (60 * app->speed);
if (elapsed > frameTime - 2000) // It takes about 2ms to copy the audio buffer
int frameTime = 1000000 / (app->tickRate * app->speed);
int elapsed = rg_system_timer() - startTime;
if (app->frameskip > 0)
skipFrames = app->frameskip;
else if (elapsed > frameTime + 1500) // Allow some jitter
skipFrames = (elapsed + frameTime / 2) / frameTime;
else if (drawFrame && rg_display_get_counters()->lastFullFrame) // This could be avoided when scaling != full
else if (drawFrame && slowFrame)
skipFrames = 1;
if (app->speed > 1.f) // This is a hack until we account for audio speed...
skipFrames += (int)app->speed;
if (app->speed > 1.f)
skipFrames += 2;
}
else if (skipFrames > 0)
{
skipFrames--;
}
// Tick before submitting audio/syncing
rg_system_tick(elapsed);
}
}

View File

@ -136,7 +136,7 @@ void gw_main(void)
};
app = rg_system_reinit(AUDIO_SAMPLE_RATE, &handlers, options);
app->refreshRate = GW_REFRESH_RATE;
app->tickRate = GW_REFRESH_RATE;
updates[0].buffer = malloc(GW_SCREEN_WIDTH * GW_SCREEN_HEIGHT * 2);
@ -260,17 +260,17 @@ void gw_main(void)
// to execute on the emulated device
gw_system_run(GW_SYSTEM_CYCLES);
/* update the screen only if there is no pending frame to render */
if (rg_display_sync(0) && drawFrame)
// Our refresh rate is 128Hz, which is way too fast for our display
// so make sure the previous frame is done sending before queuing a new one
if (rg_display_sync(false) && drawFrame)
{
gw_system_blit(currentUpdate->buffer);
rg_display_submit(currentUpdate, 0);
}
/****************************************************************************/
int elapsed = rg_system_timer() - startTime;
rg_system_tick(elapsed);
// Tick before submitting audio/syncing
rg_system_tick(rg_system_timer() - startTime);
/* copy audio samples for DMA */
rg_audio_sample_t mixbuffer[GW_AUDIO_BUFFER_LENGTH];

View File

@ -178,7 +178,7 @@ extern "C" void lynx_main(void)
updates[1].buffer = (void*)rg_alloc(HANDY_SCREEN_WIDTH * HANDY_SCREEN_WIDTH * 2, MEM_FAST);
// The Lynx has a variable framerate but 60 is typical
app->refreshRate = 60;
app->tickRate = 60;
// Init emulator
lynx = new CSystem(app->romPath, MIKIE_PIXEL_FORMAT_16BPP_565_BE, app->sampleRate);
@ -201,6 +201,7 @@ extern "C" void lynx_main(void)
float sampleTime = 1000000.f / app->sampleRate;
long skipFrames = 0;
bool slowFrame = false;
// Start emulation
while (1)
@ -231,37 +232,40 @@ extern "C" void lynx_main(void)
if (joystick & RG_KEY_SELECT) buttons |= BUTTON_OPT1;
lynx->SetButtonData(buttons);
lynx->UpdateFrame(drawFrame);
if (drawFrame)
{
slowFrame = !rg_display_sync(false);
rg_display_submit(currentUpdate, 0);
currentUpdate = &updates[currentUpdate == &updates[0]];
gPrimaryFrameBuffer = (UBYTE*)currentUpdate->buffer;
}
int elapsed = rg_system_timer() - startTime;
app->tickRate = AUDIO_SAMPLE_RATE / (gAudioBufferPointer / 2);
rg_system_tick(rg_system_timer() - startTime);
rg_audio_submit(audioBuffer, gAudioBufferPointer >> 1);
// See if we need to skip a frame to keep up
if (skipFrames == 0)
{
if (app->speed > 1.f)
skipFrames += (int)app->speed * 2;
int frameTime = ((gAudioBufferPointer / 2) * sampleTime);
int elapsed = rg_system_timer() - startTime;
if (app->frameskip > 0)
skipFrames = app->frameskip;
// The Lynx uses a variable framerate so we use the count of generated audio samples as reference instead
else if (elapsed > ((gAudioBufferPointer / 2) * sampleTime))
skipFrames += 1;
else if (drawFrame && rg_display_get_counters()->lastFullFrame) // This could be avoided when scaling != full
skipFrames += 1;
else if (elapsed > frameTime + 1500)
skipFrames = 1;
else if (drawFrame && slowFrame)
skipFrames = 1;
if (app->speed > 1.f)
skipFrames += 2;
}
else if (skipFrames > 0)
{
skipFrames--;
}
rg_system_tick(elapsed);
rg_audio_submit(audioBuffer, gAudioBufferPointer >> 1);
gAudioBufferPointer = 0;
}
}

View File

@ -7,6 +7,7 @@ static int overscan = true;
static int autocrop = 0;
static int palette = 0;
static int crop_h, crop_v;
static bool slowFrame = false;
static nes_t *nes;
static const char *SETTING_AUTOCROP = "autocrop";
@ -161,6 +162,7 @@ static void blit_screen(uint8 *bmp)
// A rolling average should be used for autocrop == 1, it causes jitter in some games...
// int crop_h = (autocrop == 2) || (autocrop == 1 && nes->ppu->left_bg_counter > 210) ? 8 : 0;
currentUpdate->buffer = NES_SCREEN_GETPTR(bmp, crop_h, crop_v);
slowFrame = !rg_display_sync(false);
rg_display_submit(currentUpdate, 0);
}
@ -220,7 +222,7 @@ void nes_main(void)
else if (ret < 0)
RG_PANIC("Unsupported ROM.");
app->refreshRate = nes->refresh_rate;
app->tickRate = nes->refresh_rate;
nes->blit_func = blit_screen;
ppu_setopt(PPU_LIMIT_SPRITES, rg_settings_get_number(NS_APP, SETTING_SPRITELIMIT, 1));
@ -277,31 +279,31 @@ void nes_main(void)
input_update(0, buttons);
nes_emulate(drawFrame);
int elapsed = rg_system_timer() - startTime;
// Tick before submitting audio/syncing
rg_system_tick(rg_system_timer() - startTime);
// Audio is used to pace emulation :)
rg_audio_submit((void*)nes->apu->buffer, nes->apu->samples_per_frame);
if (skipFrames == 0)
{
int frameTime = 1000000 / (nes->refresh_rate * app->speed);
if (elapsed > frameTime - 2000) // It takes about 2ms to copy the audio buffer
skipFrames = (elapsed + frameTime / 2) / frameTime;
else if (drawFrame && rg_display_get_counters()->lastFullFrame)
skipFrames = 1;
else if (nsfPlayer)
int elapsed = rg_system_timer() - startTime;
if (nsfPlayer)
skipFrames = 10, nsf_draw_overlay();
if (app->speed > 1.f) // This is a hack until we account for audio speed...
skipFrames += (int)app->speed;
else if (app->frameskip > 0)
skipFrames = app->frameskip;
else if (elapsed > frameTime + 1500) // Allow some jitter
skipFrames = (elapsed + frameTime / 2) / frameTime;
else if (drawFrame && slowFrame)
skipFrames = 1;
if (app->speed > 1.f)
skipFrames += 2;
}
else if (skipFrames > 0)
{
skipFrames--;
}
// Tick before submitting audio/syncing
rg_system_tick(elapsed);
// Audio is used to pace emulation :)
rg_audio_submit((void*)nes->apu->buffer, nes->apu->samples_per_frame);
}
RG_PANIC("Nofrendo died!");

View File

@ -17,6 +17,8 @@ static int current_height = 0;
static int current_width = 0;
static int overscan = false;
static int skipFrames = 0;
static bool drawFrame = true;
static bool slowFrame = false;
static uint8_t *framebuffers[2];
static const char *SETTING_OVERSCAN = "overscan";
@ -54,15 +56,16 @@ uint8_t *osd_gfx_framebuffer(int width, int height)
current_width = width;
current_height = height;
}
return skipFrames ? NULL : currentUpdate->buffer;
return drawFrame ? currentUpdate->buffer : NULL;
}
void osd_vsync(void)
{
static int64_t lasttime, prevtime;
if (skipFrames == 0)
if (drawFrame)
{
slowFrame = !rg_display_sync(false);
rg_display_submit(currentUpdate, 0);
currentUpdate = &updates[currentUpdate == &updates[0]];
}
@ -70,10 +73,12 @@ void osd_vsync(void)
// See if we need to skip a frame to keep up
if (skipFrames == 0)
{
if (app->speed > 1.f)
skipFrames = app->speed * 2.5f;
else
if (app->frameskip > 0)
skipFrames = app->frameskip;
else if (drawFrame && slowFrame)
skipFrames = 1;
if (app->speed > 1.f)
skipFrames += 2;
}
else if (skipFrames > 0)
{
@ -81,7 +86,7 @@ void osd_vsync(void)
}
int64_t curtime = rg_system_timer();
int frameTime = 1000000 / 60 / app->speed;
int frameTime = 1000000 / (app->tickRate * app->speed);
int sleep = frameTime - (curtime - lasttime);
if (sleep > frameTime)
@ -104,6 +109,8 @@ void osd_vsync(void)
if ((lasttime + frameTime) < prevtime)
lasttime = prevtime;
drawFrame = (skipFrames == 0);
}
void osd_input_read(uint8_t joypads[8])
@ -227,6 +234,9 @@ void pce_main(void)
rg_emu_load_state(app->saveSlot);
}
app->tickRate = 60;
app->frameskip = 1;
emulationPaused = false;
RunPCE();

View File

@ -167,7 +167,7 @@ void sms_main(void)
system_poweron();
app->refreshRate = (sms.display == DISPLAY_NTSC) ? FPS_NTSC : FPS_PAL;
app->tickRate = (sms.display == DISPLAY_NTSC) ? FPS_NTSC : FPS_PAL;
updates[0].buffer += bitmap.viewport.x;
updates[1].buffer += bitmap.viewport.x;
@ -196,6 +196,7 @@ void sms_main(void)
int64_t startTime = rg_system_timer();
bool drawFrame = !skipFrames;
bool slowFrame = false;
input.pad[0] = 0x00;
input.pad[1] = 0x00;
@ -310,32 +311,12 @@ void sms_main(void)
{
if (render_copy_palette(currentUpdate->palette))
memcpy(&updates[currentUpdate == &updates[0]].palette, currentUpdate->palette, 512);
slowFrame = !rg_display_sync(false);
rg_display_submit(currentUpdate, 0);
currentUpdate = &updates[currentUpdate == &updates[0]]; // Swap
bitmap.data = currentUpdate->buffer - bitmap.viewport.x;
}
int elapsed = rg_system_timer() - startTime;
// See if we need to skip a frame to keep up
if (skipFrames == 0)
{
int frameTime = 1000000 / (app->refreshRate * app->speed);
if (elapsed > frameTime - 2000) // It takes about 2ms to copy the audio buffer
skipFrames = (elapsed + frameTime / 2) / frameTime;
else if (drawFrame && rg_display_get_counters()->lastFullFrame)
skipFrames = 1;
if (app->speed > 1.f) // This is a hack until we account for audio speed...
skipFrames += (int)app->speed;
}
else if (skipFrames > 0)
{
skipFrames--;
}
// Tick before submitting audio/syncing
rg_system_tick(elapsed);
// The emulator's sound buffer isn't in a very convenient format, we must remix it.
size_t sample_count = snd.sample_count;
rg_audio_sample_t mixbuffer[sample_count];
@ -345,7 +326,29 @@ void sms_main(void)
mixbuffer[i].right = snd.stream[1][i] * 2.75f;
}
// Tick before submitting audio/syncing
rg_system_tick(rg_system_timer() - startTime);
// Audio is used to pace emulation :)
rg_audio_submit(mixbuffer, sample_count);
// See if we need to skip a frame to keep up
if (skipFrames == 0)
{
int frameTime = 1000000 / (app->tickRate * app->speed);
int elapsed = rg_system_timer() - startTime;
if (app->frameskip > 0)
skipFrames = app->frameskip;
else if (elapsed > frameTime + 1500) // Allow some jitter
skipFrames = (elapsed + frameTime / 2) / frameTime;
else if (drawFrame && slowFrame)
skipFrames = 1;
if (app->speed > 1.f)
skipFrames += 2;
}
else if (skipFrames > 0)
{
skipFrames--;
}
}
}

View File

@ -27,7 +27,6 @@ static rg_app_t *app;
static bool apu_enabled = true;
static bool lowpass_filter = false;
static int frameskip = 4;
bool overclock_cycles = false;
int one_c = 4, slow_one_c = 5, two_c = 6;
@ -35,7 +34,6 @@ int one_c = 4, slow_one_c = 5, two_c = 6;
static int keymap_id = 0;
static keymap_t keymap;
static const char *SETTING_FRAMESKIP = "frameskip";
static const char *SETTING_KEYMAP = "keymap";
static const char *SETTING_APU_EMULATION = "apu";
// --- MAIN
@ -88,20 +86,6 @@ static rg_gui_event_t apu_toggle_cb(rg_gui_option_t *option, rg_gui_event_t even
return RG_DIALOG_VOID;
}
static rg_gui_event_t frameskip_cb(rg_gui_option_t *option, rg_gui_event_t event)
{
if (event == RG_DIALOG_PREV || event == RG_DIALOG_NEXT)
{
frameskip += (event == RG_DIALOG_PREV) ? -1 : 1;
frameskip = RG_MAX(frameskip, 1);
rg_settings_set_number(NS_APP, SETTING_FRAMESKIP, frameskip);
}
sprintf(option->value, "%d", frameskip);
return RG_DIALOG_VOID;
}
static rg_gui_event_t lowpass_filter_cb(rg_gui_option_t *option, rg_gui_event_t event)
{
if (event == RG_DIALOG_PREV || event == RG_DIALOG_NEXT)
@ -279,13 +263,11 @@ void app_main(void)
const rg_gui_option_t options[] = {
{2, "Audio enable", (char *)"", 1, &apu_toggle_cb},
{2, "Audio filter", (char*)"", 1, &lowpass_filter_cb},
{2, "Frameskip", (char *)"", 1, &frameskip_cb},
{2, "Controls", (char *)"", 1, &menu_keymap_cb},
RG_DIALOG_END,
};
app = rg_system_init(AUDIO_SAMPLE_RATE, &handlers, options);
frameskip = rg_settings_get_number(NS_APP, SETTING_FRAMESKIP, frameskip);
apu_enabled = rg_settings_get_number(NS_APP, SETTING_APU_EMULATION, 1);
updates[0].buffer = malloc(SNES_WIDTH * SNES_HEIGHT_EXTENDED * 2);
@ -337,11 +319,12 @@ void app_main(void)
rg_emu_load_state(app->saveSlot);
}
app->refreshRate = Memory.ROMFramesPerSecond;
app->tickRate = Memory.ROMFramesPerSecond;
app->frameskip = 3;
bool menuCancelled = false;
bool menuPressed = false;
int frames = 0;
int skipFrames = 0;
while (1)
{
@ -371,13 +354,19 @@ void app_main(void)
}
int64_t startTime = rg_system_timer();
bool drawFrame = (skipFrames == 0);
bool slowFrame = false;
IPPU.RenderThisFrame = (frames++ % frameskip) == 0;
IPPU.RenderThisFrame = drawFrame;
GFX.Screen = currentUpdate->buffer;
S9xMainLoop();
if (IPPU.RenderThisFrame)
if (drawFrame)
{
slowFrame = !rg_display_sync(false);
rg_display_submit(currentUpdate, 0);
}
#ifndef USE_BLARGG_APU
if (apu_enabled && lowpass_filter)
@ -386,13 +375,29 @@ void app_main(void)
S9xMixSamples((void *)mixbuffer, AUDIO_BUFFER_LENGTH << 1);
#endif
int elapsed = rg_system_timer() - startTime;
rg_system_tick(rg_system_timer() - startTime);
#ifndef USE_BLARGG_APU
if (apu_enabled)
rg_audio_submit(mixbuffer, AUDIO_BUFFER_LENGTH);
#endif
rg_system_tick(elapsed);
if (skipFrames == 0)
{
int frameTime = 1000000 / (app->tickRate * app->speed);
int elapsed = rg_system_timer() - startTime;
if (app->frameskip > 0)
skipFrames = app->frameskip;
else if (elapsed > frameTime + 1500) // Allow some jitter
skipFrames = (elapsed + frameTime / 2) / frameTime;
else if (drawFrame && slowFrame)
skipFrames = 1;
if (app->speed > 1.f)
skipFrames += 2;
}
else if (skipFrames > 0)
{
skipFrames--;
}
}
}