From 299650159f32c72bb0fd706d1b7e680dce5909f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E7=9A=93?= Date: Thu, 10 Apr 2025 16:51:25 -0400 Subject: [PATCH] Implement changing the frame rate in libretro builds --- binding-sandbox/graphics-binding.h | 22 ++++++- src/core.cpp | 93 +++++++++++++++++++++++------- src/display/graphics.cpp | 2 +- 3 files changed, 94 insertions(+), 23 deletions(-) diff --git a/binding-sandbox/graphics-binding.h b/binding-sandbox/graphics-binding.h index 59faa917..ff78cf0e 100644 --- a/binding-sandbox/graphics-binding.h +++ b/binding-sandbox/graphics-binding.h @@ -196,7 +196,26 @@ namespace mkxp_sandbox { } static VALUE get_frame_rate(VALUE self) { - return sb()->bind()()(60.0); // TODO: use actual FPS + return sb()->bind()()(shState->graphics().getFrameRate()); + } + + static VALUE set_frame_rate(VALUE self, VALUE value) { + SANDBOX_COROUTINE(coro, + int frame_rate; + + VALUE operator()(VALUE self, VALUE value) { + BOOST_ASIO_CORO_REENTER (this) { + SANDBOX_AWAIT_AND_SET(frame_rate, rb_num2int, value); + GFX_LOCK; + shState->graphics().setFrameRate(frame_rate); + GFX_UNLOCK; + } + + return value; + } + ) + + return sb()->bind()()(self, value); } VALUE module; @@ -214,6 +233,7 @@ namespace mkxp_sandbox { SANDBOX_AWAIT(rb_define_module_function, module, "frame_count", (VALUE (*)(ANYARGS))get_frame_count, 0); SANDBOX_AWAIT(rb_define_module_function, module, "frame_count=", (VALUE (*)(ANYARGS))set_frame_count, 1); SANDBOX_AWAIT(rb_define_module_function, module, "frame_rate", (VALUE (*)(ANYARGS))get_frame_rate, 0); + SANDBOX_AWAIT(rb_define_module_function, module, "frame_rate=", (VALUE (*)(ANYARGS))set_frame_rate, 1); } } ) diff --git a/src/core.cpp b/src/core.cpp index 53747200..0bbd5ed4 100644 --- a/src/core.cpp +++ b/src/core.cpp @@ -36,12 +36,30 @@ #include "filesystem.h" #include "gl-fun.h" #include "glstate.h" +#include "sharedmidistate.h" using namespace mkxp_retro; using namespace mkxp_sandbox; static uint64_t frame_count; +extern const uint8_t mkxp_retro_dist_zip[]; +extern const size_t mkxp_retro_dist_zip_len; + +static bool initialized = false; +static ALCdevice *al_device = NULL; +static ALCcontext *al_context = NULL; +static LPALCRENDERSAMPLESSOFT alcRenderSamplesSOFT = NULL; +static LPALCLOOPBACKOPENDEVICESOFT alcLoopbackOpenDeviceSOFT = NULL; +static uint32_t frame_rate; +static uint32_t frame_rate_remainder; +static uint32_t samples_per_frame; +static uint32_t samples_per_frame_remainder; +static int16_t *sound_buf = NULL; +static bool retro_framebuffer_supported; +static bool shared_state_initialized; +static PHYSFS_File *rgssad = NULL; + namespace mkxp_retro { retro_log_printf_t log_printf; retro_video_refresh_t video_refresh; @@ -53,23 +71,10 @@ namespace mkxp_retro { retro_hw_render_callback hw_render; uint64_t get_ticks() noexcept { - return (frame_count * 1000) / 60; + return (frame_count * 1000) / shState->graphics().getFrameRate(); } } -extern const uint8_t mkxp_retro_dist_zip[]; -extern const size_t mkxp_retro_dist_zip_len; - -static bool initialized = false; -static ALCdevice *al_device = NULL; -static ALCcontext *al_context = NULL; -static LPALCRENDERSAMPLESSOFT alcRenderSamplesSOFT = NULL; -static LPALCLOOPBACKOPENDEVICESOFT alcLoopbackOpenDeviceSOFT = NULL; -static int16_t *sound_buf; -static bool retro_framebuffer_supported; -static bool shared_state_initialized; -static PHYSFS_File *rgssad = NULL; - static void fallback_log(enum retro_log_level level, const char *fmt, ...) { std::va_list va; va_start(va, fmt); @@ -142,6 +147,10 @@ SANDBOX_COROUTINE(main, ) static void deinit_sandbox() { + if (sound_buf != NULL) { + mkxp_aligned_free(sound_buf); + sound_buf = NULL; + } mkxp_retro::sandbox.reset(); audio.reset(); if (al_context != NULL) { @@ -230,7 +239,7 @@ static bool init_sandbox() { ALC_FORMAT_TYPE_SOFT, ALC_SHORT_SOFT, ALC_FREQUENCY, - 44100, + SYNTH_SAMPLERATE, 0, }; al_context = alcCreateContext(al_device, al_attrs); @@ -255,6 +264,9 @@ static bool init_sandbox() { return false; } + sound_buf = NULL; + frame_rate = 0; + frame_rate_remainder = 0; frame_count = 0; shared_state_initialized = false; @@ -313,11 +325,9 @@ extern "C" RETRO_API void retro_set_input_state(retro_input_state_t cb) { extern "C" RETRO_API void retro_init() { initialized = true; frame_buf = (uint32_t *)std::calloc(640 * 480, sizeof *frame_buf); - sound_buf = (int16_t *)mkxp_aligned_malloc(16, 735 * 2 * sizeof *sound_buf); } extern "C" RETRO_API void retro_deinit() { - mkxp_aligned_free(sound_buf); std::free(frame_buf); initialized = false; } @@ -338,8 +348,8 @@ extern "C" RETRO_API void retro_get_system_info(struct retro_system_info *info) extern "C" RETRO_API void retro_get_system_av_info(struct retro_system_av_info *info) { std::memset(info, 0, sizeof *info); info->timing = { - .fps = 60.0, - .sample_rate = 44100.0, + .fps = 40.0, + .sample_rate = (double)SYNTH_SAMPLERATE, }; info->geometry = { .base_width = 640, @@ -381,6 +391,39 @@ extern "C" RETRO_API void retro_run() { } } + if (mkxp_retro::sandbox.has_value()) { + // Update frame rate if needed + if (frame_rate != shState->graphics().getFrameRate()) { + frame_rate = shState->graphics().getFrameRate(); + frame_rate_remainder %= frame_rate; + samples_per_frame = SYNTH_SAMPLERATE / frame_rate; + samples_per_frame_remainder = SYNTH_SAMPLERATE % frame_rate; + + if (sound_buf != NULL) { + mkxp_aligned_free(sound_buf); + } + sound_buf = (int16_t *)mkxp_aligned_malloc(16, (samples_per_frame + !!samples_per_frame_remainder) * 2 * sizeof(int16_t)); + if (sound_buf == NULL) { + throw std::bad_alloc(); + } + + struct retro_system_av_info info; + std::memset(&info, 0, sizeof info); + info.timing = { + .fps = (double)frame_rate, + .sample_rate = (double)SYNTH_SAMPLERATE, + }; + info.geometry = { + .base_width = 640, + .base_height = 480, + .max_width = 640, + .max_height = 480, + .aspect_ratio = 640.0f / 480.0f, + }; + environment(RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO, &info); + } + } + void *fb; if (hw_render.context_type != RETRO_HW_CONTEXT_NONE) { gl.UseProgram(0); @@ -408,8 +451,16 @@ extern "C" RETRO_API void retro_run() { if (mkxp_retro::sandbox.has_value()) { audio->render(); - alcRenderSamplesSOFT(al_device, sound_buf, 735); - audio_sample_batch(sound_buf, 735); + + uint32_t samples = samples_per_frame; + frame_rate_remainder += samples_per_frame_remainder; + if (frame_rate_remainder >= frame_rate) { + ++samples; + frame_rate_remainder -= frame_rate; + } + + alcRenderSamplesSOFT(al_device, sound_buf, samples); + audio_sample_batch(sound_buf, samples); } ++frame_count; diff --git a/src/display/graphics.cpp b/src/display/graphics.cpp index f2aed478..25866e26 100644 --- a/src/display/graphics.cpp +++ b/src/display/graphics.cpp @@ -74,7 +74,7 @@ # define DEF_SCREEN_W 640 # define DEF_SCREEN_H 480 -# define DEF_FRAMERATE 60 +# define DEF_FRAMERATE 40 #else # define DEF_SCREEN_W (rgssVer == 1 ? 640 : 544) # define DEF_SCREEN_H (rgssVer == 1 ? 480 : 416)