diff --git a/common/supervision.h b/common/supervision.h index d7801bc..fce27dd 100644 --- a/common/supervision.h +++ b/common/supervision.h @@ -77,8 +77,8 @@ void supervision_done(void); * \return TRUE - success, FALSE - error */ BOOL supervision_load(const uint8 *rom, uint32 romSize); -void supervision_exec(uint16 *backbuffer); -void supervision_exec_ex(uint16 *backbuffer, int16 backbufferWidth); +void supervision_exec(uint16 *backbuffer, BOOL skipFrame); +void supervision_exec_ex(uint16 *backbuffer, int16 backbufferWidth, BOOL skipFrame); /*! * \param data Bits 0-7: Right, Left, Down, Up, B, A, Select, Start. diff --git a/common/watara.c b/common/watara.c index c662084..b91ff46 100644 --- a/common/watara.c +++ b/common/watara.c @@ -70,12 +70,12 @@ BOOL supervision_load(const uint8 *rom, uint32 romSize) return TRUE; } -void supervision_exec(uint16 *backbuffer) +void supervision_exec(uint16 *backbuffer, BOOL skipFrame) { - supervision_exec_ex(backbuffer, SV_W); + supervision_exec_ex(backbuffer, SV_W, skipFrame); } -void supervision_exec_ex(uint16 *backbuffer, int16 backbufferWidth) +void supervision_exec_ex(uint16 *backbuffer, int16 backbufferWidth, BOOL skipFrame) { uint32 i, scan; uint8 *regs = memorymap_getRegisters(); @@ -87,19 +87,21 @@ void supervision_exec_ex(uint16 *backbuffer, int16 backbufferWidth) timer_exec(m6502_registers.IPeriod); } - //if (!(regs[BANK] & 0x8)) { printf("LCD off\n"); } - scan = regs[XPOS] / 4 + regs[YPOS] * 0x30; - innerx = regs[XPOS] & 3; - size = regs[XSIZE]; // regs[XSIZE] <= SV_W - if (size > SV_W) - size = SV_W; // 192: Chimera, Matta Blatta, Tennis Pro '92 + if (!skipFrame) { + //if (!(regs[BANK] & 0x8)) { printf("LCD off\n"); } + scan = regs[XPOS] / 4 + regs[YPOS] * 0x30; + innerx = regs[XPOS] & 3; + size = regs[XSIZE]; // regs[XSIZE] <= SV_W + if (size > SV_W) + size = SV_W; // 192: Chimera, Matta Blatta, Tennis Pro '92 - for (i = 0; i < SV_H; i++) { - if (scan >= 0x1fe0) - scan -= 0x1fe0; // SSSnake - gpu_render_scanline(scan, backbuffer, innerx, size); - backbuffer += backbufferWidth; - scan += 0x30; + for (i = 0; i < SV_H; i++) { + if (scan >= 0x1fe0) + scan -= 0x1fe0; // SSSnake + gpu_render_scanline(scan, backbuffer, innerx, size); + backbuffer += backbufferWidth; + scan += 0x30; + } } if (Rd6502(0x2026) & 0x01) diff --git a/platform/GP2X/main.c b/platform/GP2X/main.c index 900bf55..605a4a6 100644 --- a/platform/GP2X/main.c +++ b/platform/GP2X/main.c @@ -169,14 +169,14 @@ int main(int argc, char *argv[]) switch(currentConfig.videoMode){ case 0: { int j; - supervision_exec(screenbuffer); + supervision_exec(screenbuffer, FALSE); for (j = 0; j < 160; j++) gp2x_memcpy(screen16+(80+(j+40)*320), screenbuffer+(j * 160), 160*2); } break; case 1: case 2: - supervision_exec(screen16); + supervision_exec(screen16, FALSE); break; default: break; diff --git a/platform/NDS/main.c b/platform/NDS/main.c index bfe7aad..56a87ba 100644 --- a/platform/NDS/main.c +++ b/platform/NDS/main.c @@ -135,7 +135,7 @@ int main() while (true) { CheckKeys(); - supervision_exec(screenBuffer); + supervision_exec(screenBuffer, FALSE); for (int j = 0; j < 160; j++) { // Copy frame buffer to screen diff --git a/platform/PSP/main.c b/platform/PSP/main.c index a1671a1..37df83f 100644 --- a/platform/PSP/main.c +++ b/platform/PSP/main.c @@ -284,7 +284,7 @@ int main(int argc, char* argv[]) memset(&oldPad, 0xFF, sizeof(SceCtrlData)); // Reset buttons } - supervision_exec_ex((uint16_t*)pixels, TEX_WIDTH); + supervision_exec_ex((uint16_t*)pixels, TEX_WIDTH, FALSE); sceKernelDcacheWritebackAll(); @@ -1022,4 +1022,4 @@ void DrawButton(int button, int x, int y) } sceGuCopyImage(PIX_FORMAT, 0, 0, 16, 16, 16, pixs[button], x, y, BUF_WIDTH, (void*)(((unsigned int)fbpc) + 0x4000000)); -} \ No newline at end of file +} diff --git a/platform/SDL/main.c b/platform/SDL/main.c index 51595fd..bb271fc 100644 --- a/platform/SDL/main.c +++ b/platform/SDL/main.c @@ -275,7 +275,7 @@ Increase Window Size: =\n\ while (!done) { PollEvents(); HandleInput(); - supervision_exec(screenBuffer); + supervision_exec(screenBuffer, FALSE); Draw(); Wait(); } diff --git a/platform/SDL2/main.c b/platform/SDL2/main.c index 63acd5c..7b2ddca 100644 --- a/platform/SDL2/main.c +++ b/platform/SDL2/main.c @@ -368,7 +368,7 @@ void Loop(void) switch (GetMenuState()) { case MENUSTATE_EMULATION: HandleInput(); - supervision_exec(screenBuffer); + supervision_exec(screenBuffer, FALSE); break; case MENUSTATE_DROP_ROM: DrawDropROM(); diff --git a/platform/WIN/main.c b/platform/WIN/main.c index 64cbb7c..cc08c31 100644 --- a/platform/WIN/main.c +++ b/platform/WIN/main.c @@ -140,7 +140,7 @@ DWORD WINAPI run(LPVOID lpParameter) supervision_set_input(controls_state); while (NeedUpdate()) { - supervision_exec(screenBuffer); + supervision_exec(screenBuffer, FALSE); #ifdef TERRIBLE_AUDIO_IMPLEMENTATION supervision_update_sound(audioBuffer, BUFFER_SIZE / 2); diff --git a/platform/libretro/libretro.c b/platform/libretro/libretro.c index 5c3fc38..5d1be0f 100644 --- a/platform/libretro/libretro.c +++ b/platform/libretro/libretro.c @@ -35,9 +35,10 @@ static retro_audio_sample_batch_t audio_batch_cb; static bool libretro_supports_bitmasks = false; -#define SV_W 160 -#define SV_H 160 -#define AUDIO_BUFFER_SIZE ((SV_SAMPLE_RATE / 60) << 1) +#define SV_FPS 60 +#define SV_W 160 +#define SV_H 160 +#define AUDIO_BUFFER_SIZE ((SV_SAMPLE_RATE / SV_FPS) << 1) static enum SV_COLOR color_scheme = SV_COLOR_SCHEME_DEFAULT; static int ghosting_frames = 0; @@ -87,6 +88,73 @@ struct sv_color_scheme sv_color_schemes[] = { { NULL, 0 }, }; +/************************************ + * Frameskipping Support + ************************************/ + +static unsigned frameskip_type = 0; +static unsigned frameskip_threshold = 0; +static uint16_t frameskip_counter = 0; + +static bool retro_audio_buff_active = false; +static unsigned retro_audio_buff_occupancy = 0; +static bool retro_audio_buff_underrun = false; +/* Maximum number of consecutive frames that + * can be skipped */ +#define FRAMESKIP_MAX 60 + +static unsigned audio_latency = 0; +static bool update_audio_latency = false; + +static void retro_audio_buff_status_cb( + bool active, unsigned occupancy, bool underrun_likely) +{ + retro_audio_buff_active = active; + retro_audio_buff_occupancy = occupancy; + retro_audio_buff_underrun = underrun_likely; +} + +static void init_frameskip(void) +{ + if (frameskip_type > 0) + { + struct retro_audio_buffer_status_callback buf_status_cb; + + buf_status_cb.callback = retro_audio_buff_status_cb; + if (!environ_cb(RETRO_ENVIRONMENT_SET_AUDIO_BUFFER_STATUS_CALLBACK, + &buf_status_cb)) + { + if (log_cb) + log_cb(RETRO_LOG_WARN, "Frameskip disabled - frontend does not support audio buffer status monitoring.\n"); + + retro_audio_buff_active = false; + retro_audio_buff_occupancy = 0; + retro_audio_buff_underrun = false; + audio_latency = 0; + } + else + { + /* Frameskip is enabled - increase frontend + * audio latency to minimise potential + * buffer underruns */ + float frame_time_msec = 1000.0f / (float)SV_FPS; + + /* Set latency to 6x current frame time... */ + audio_latency = (unsigned)((6.0f * frame_time_msec) + 0.5f); + + /* ...then round up to nearest multiple of 32 */ + audio_latency = (audio_latency + 0x1F) & ~0x1F; + } + } + else + { + environ_cb(RETRO_ENVIRONMENT_SET_AUDIO_BUFFER_STATUS_CALLBACK, NULL); + audio_latency = 0; + } + + update_audio_latency = true; +} + /************************************ * Auxiliary functions ************************************/ @@ -118,6 +186,7 @@ static void check_variables(bool startup) struct retro_variable var = {0}; enum SV_COLOR color_scheme_last; int ghosting_frames_last; + unsigned frameskip_type_last; /* Internal Palette */ color_scheme_last = color_scheme; @@ -142,6 +211,31 @@ static void check_variables(bool startup) if (startup || (ghosting_frames != ghosting_frames_last)) supervision_set_ghosting(ghosting_frames); + + /* Frameskip */ + frameskip_type_last = frameskip_type; + frameskip_type = 0; + var.key = "potator_frameskip"; + var.value = NULL; + + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) + { + if (!strcmp(var.value, "auto")) + frameskip_type = 1; + else if (!strcmp(var.value, "manual")) + frameskip_type = 2; + } + + if (startup || (frameskip_type != frameskip_type_last)) + init_frameskip(); + + /* Frameskip Threshold (%) */ + frameskip_threshold = 33; + var.key = "potator_frameskip_threshold"; + var.value = NULL; + + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) + frameskip_threshold = strtol(var.value, NULL, 10); } static void update_input(void) @@ -253,8 +347,8 @@ void retro_get_system_info(struct retro_system_info *info) void retro_get_system_av_info(struct retro_system_av_info *info) { memset(info, 0, sizeof(*info)); - info->timing.fps = 60; - info->timing.sample_rate = SV_SAMPLE_RATE; + info->timing.fps = (double)SV_FPS; + info->timing.sample_rate = (double)SV_SAMPLE_RATE; info->geometry.base_width = SV_W; info->geometry.base_height = SV_H; info->geometry.max_width = SV_W; @@ -440,6 +534,15 @@ void retro_init(void) * of 128, to avoid potential overflows */ audio_samples_buffer = (uint8*)malloc(((AUDIO_BUFFER_SIZE + 0x7F) & ~0x7F) * sizeof(uint8)); audio_out_buffer = (int16_t*)malloc(AUDIO_BUFFER_SIZE * sizeof(int16_t)); + + frameskip_type = 0; + frameskip_threshold = 0; + frameskip_counter = 0; + retro_audio_buff_active = false; + retro_audio_buff_occupancy = 0; + retro_audio_buff_underrun = false; + audio_latency = 0; + update_audio_latency = false; } void retro_deinit(void) @@ -479,7 +582,8 @@ void retro_reset(void) void retro_run(void) { - bool options_updated = false; + bool options_updated = false; + BOOL skip_frame = FALSE; /* Core options */ if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &options_updated) && options_updated) @@ -488,11 +592,46 @@ void retro_run(void) /* Update input */ update_input(); + /* Check whether current frame should be skipped */ + if ((frameskip_type > 0) && retro_audio_buff_active) + { + switch (frameskip_type) + { + case 1: /* auto */ + skip_frame = retro_audio_buff_underrun ? TRUE : FALSE; + break; + case 2: /* manual */ + skip_frame = (retro_audio_buff_occupancy < frameskip_threshold) ? TRUE : FALSE; + break; + default: + skip_frame = 0; + break; + } + + if (!skip_frame || (frameskip_counter >= FRAMESKIP_MAX)) + { + skip_frame = 0; + frameskip_counter = 0; + } + else + frameskip_counter++; + } + + /* If frameskip settings have changed, update + * frontend audio latency */ + if (update_audio_latency) + { + environ_cb(RETRO_ENVIRONMENT_SET_MINIMUM_AUDIO_LATENCY, + &audio_latency); + update_audio_latency = false; + } + /* Run emulator */ - supervision_exec(video_buffer); + supervision_exec(video_buffer, skip_frame); /* Output video */ - video_cb(video_buffer, SV_W, SV_H, SV_W << 1); + video_cb((bool)skip_frame ? NULL : video_buffer, + SV_W, SV_H, SV_W << 1); /* Output audio */ update_audio(); diff --git a/platform/libretro/libretro_core_options.h b/platform/libretro/libretro_core_options.h index 286f767..4480a10 100644 --- a/platform/libretro/libretro_core_options.h +++ b/platform/libretro/libretro_core_options.h @@ -105,6 +105,43 @@ struct retro_core_option_definition option_defs_us[] = { }, "0" }, + { + "potator_frameskip", + "Frameskip", + "Skip frames to avoid audio buffer under-run (crackling). Improves performance at the expense of visual smoothness. 'Auto' skips frames when advised by the frontend. 'Manual' utilises the 'Frameskip Threshold (%)' setting.", + { + { "disabled", NULL }, + { "auto", "Auto" }, + { "manual", "Manual" }, + { NULL, NULL }, + }, + "disabled" + }, + { + "potator_frameskip_threshold", + "Frameskip Threshold (%)", + "When 'Frameskip' is set to 'Manual', specifies the audio buffer occupancy threshold (percentage) below which frames will be skipped. Higher values reduce the risk of crackling by causing frames to be dropped more frequently.", + { + { "15", NULL }, + { "18", NULL }, + { "21", NULL }, + { "24", NULL }, + { "27", NULL }, + { "30", NULL }, + { "33", NULL }, + { "36", NULL }, + { "39", NULL }, + { "42", NULL }, + { "45", NULL }, + { "48", NULL }, + { "51", NULL }, + { "54", NULL }, + { "57", NULL }, + { "60", NULL }, + { NULL, NULL }, + }, + "33" + }, { NULL, NULL, NULL, {{0}}, NULL }, }; diff --git a/platform/opendingux/main_od.c b/platform/opendingux/main_od.c index ec3e40d..91f28e6 100644 --- a/platform/opendingux/main_od.c +++ b/platform/opendingux/main_od.c @@ -269,7 +269,7 @@ int main(int argc, char *argv[]) { supervision_set_input(controls_state); // Update emulation - supervision_exec(XBuf); + supervision_exec(XBuf, FALSE); graphics_paint(); nextTick += interval;