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:
parent
35db489f74
commit
a44530c98f
@ -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
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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];
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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!");
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user