Fix frame duping bugs in libretro builds

* Fixed a bug where frames are still duped when the frontend is
  fast-forwarding

* Fixed a bug where manual frame duping (without
  `RETRO_ENVIRONMENT_GET_CAN_DUPE`) causes screen flickering during a
  `Graphics.transition` call
This commit is contained in:
刘皓 2025-04-25 10:45:11 -04:00
parent 5c14f2352e
commit 2b3a97e83c
No known key found for this signature in database
GPG key ID: 7901753DB465B711
6 changed files with 48 additions and 14 deletions

View file

@ -65,6 +65,8 @@ namespace mkxp_sandbox {
duration = 8; duration = 8;
vague = 40; vague = 40;
sb().transitioning = true;
if (!shState->graphics().frozen()) { if (!shState->graphics().frozen()) {
return SANDBOX_NIL; return SANDBOX_NIL;
} }
@ -92,6 +94,8 @@ namespace mkxp_sandbox {
} }
~coro() { ~coro() {
sb().transitioning = false;
if (trans_map != NULL) { if (trans_map != NULL) {
delete trans_map; delete trans_map;
} }

View file

@ -57,7 +57,7 @@ void sandbox::sandbox_free(usize ptr) {
w2c_ruby_mkxp_sandbox_free(RB, ptr); w2c_ruby_mkxp_sandbox_free(RB, ptr);
} }
sandbox::sandbox() : ruby(new struct w2c_ruby), wasi(new wasi_t(ruby)), bindings(ruby), yielding(false) { sandbox::sandbox() : ruby(new struct w2c_ruby), wasi(new wasi_t(ruby)), bindings(ruby), yielding(false), transitioning(false) {
try { try {
// Initialize the sandbox // Initialize the sandbox
wasm2c_ruby_instantiate(RB, wasi.get()); wasm2c_ruby_instantiate(RB, wasi.get());

View file

@ -82,6 +82,7 @@ namespace mkxp_sandbox {
void sandbox_free(usize ptr); void sandbox_free(usize ptr);
public: public:
bool transitioning;
inline struct mkxp_sandbox::bindings &operator*() noexcept { return *bindings; } inline struct mkxp_sandbox::bindings &operator*() noexcept { return *bindings; }
inline struct mkxp_sandbox::bindings *operator->() noexcept { return &*bindings; } inline struct mkxp_sandbox::bindings *operator->() noexcept { return &*bindings; }
sandbox(); sandbox();

View file

@ -69,6 +69,7 @@ struct lock_guard {
}; };
static uint64_t frame_count; static uint64_t frame_count;
static uint64_t frame_time;
static uint64_t retro_run_count; static uint64_t retro_run_count;
extern const uint8_t mkxp_retro_dist_zip[]; extern const uint8_t mkxp_retro_dist_zip[];
@ -85,12 +86,15 @@ static bool dupe_supported;
static PHYSFS_File *rgssad = NULL; static PHYSFS_File *rgssad = NULL;
static retro_system_av_info av_info; static retro_system_av_info av_info;
static struct retro_audio_callback audio_callback; static struct retro_audio_callback audio_callback;
static struct retro_frame_time_callback frame_time_callback; static struct retro_frame_time_callback frame_time_callback = {
.callback = [](retro_usec_t delta) {
frame_time += delta;
},
};
static std::mutex threaded_audio_mutex; static std::mutex threaded_audio_mutex;
static bool threaded_audio_enabled = false; static bool threaded_audio_enabled = false;
static bool frame_time_callback_enabled = false; static bool frame_time_callback_enabled = false;
static std::atomic<bool> shared_state_initialized(false); static std::atomic<bool> shared_state_initialized(false);
static uint64_t frame_time;
namespace mkxp_retro { namespace mkxp_retro {
retro_log_printf_t log_printf; retro_log_printf_t log_printf;
@ -337,15 +341,16 @@ static bool init_sandbox() {
av_info.geometry.aspect_ratio = (float)av_info.geometry.base_width / (float)av_info.geometry.base_height; av_info.geometry.aspect_ratio = (float)av_info.geometry.base_width / (float)av_info.geometry.base_height;
av_info.timing.sample_rate = (double)SYNTH_SAMPLERATE; av_info.timing.sample_rate = (double)SYNTH_SAMPLERATE;
frame_time_callback.reference = 1000000 / (rgssVer == 1 ? 40 : 60); frame_time_callback.reference = 1000000 / (rgssVer == 1 ? 40 : 60);
frame_time_callback_enabled = environment(RETRO_ENVIRONMENT_SET_FRAME_TIME_CALLBACK, &frame_time_callback);
sound_buf = (int16_t *)mkxp_aligned_malloc(16, (threaded_audio_enabled ? THREADED_AUDIO_SAMPLES : (size_t)std::ceil((double)SYNTH_SAMPLERATE / av_info.timing.fps)) * 2 * sizeof(int16_t)); sound_buf = (int16_t *)mkxp_aligned_malloc(16, (threaded_audio_enabled ? THREADED_AUDIO_SAMPLES : (size_t)std::ceil((double)SYNTH_SAMPLERATE / av_info.timing.fps)) * 2 * sizeof(int16_t));
if (sound_buf == NULL) { if (sound_buf == NULL) {
throw std::bad_alloc(); throw std::bad_alloc();
} }
retro_run_count = 0;
frame_count = 0; frame_count = 0;
frame_time = 0; frame_time = 0;
retro_run_count = 0;
return true; return true;
} }
@ -435,7 +440,7 @@ extern "C" RETRO_API void retro_reset() {
} }
extern "C" RETRO_API void retro_run() { extern "C" RETRO_API void retro_run() {
bool should_render = mkxp_retro::sandbox.has_value() && (!frame_time_callback_enabled || frame_time >= frame_time_callback.reference); bool should_render = mkxp_retro::sandbox.has_value() && (frame_count == 0 || frame_time >= frame_time_callback.reference);
input_poll(); input_poll();
@ -443,7 +448,7 @@ extern "C" RETRO_API void retro_run() {
if (!shared_state_initialized.load(std::memory_order_relaxed)) { if (!shared_state_initialized.load(std::memory_order_relaxed)) {
SharedState::initInstance(&thread_data.get()); SharedState::initInstance(&thread_data.get());
shared_state_initialized.store(true, std::memory_order_seq_cst); shared_state_initialized.store(true, std::memory_order_seq_cst);
} else if (hw_render.context_type != RETRO_HW_CONTEXT_NONE) { } else if (hw_render.context_type != RETRO_HW_CONTEXT_NONE && (should_render || (!dupe_supported && mkxp_retro::sandbox.has_value()))) {
glState.reset(); glState.reset();
} }
@ -458,7 +463,7 @@ extern "C" RETRO_API void retro_run() {
deinit_sandbox(); deinit_sandbox();
} }
} else if (!dupe_supported && mkxp_retro::sandbox.has_value()) { } else if (!dupe_supported && mkxp_retro::sandbox.has_value()) {
shState->graphics().wait(1); shState->graphics().repaint(sb().transitioning);
} }
void *fb; void *fb;
@ -501,7 +506,15 @@ extern "C" RETRO_API void retro_run() {
++retro_run_count; ++retro_run_count;
if (mkxp_retro::sandbox.has_value()) { if (mkxp_retro::sandbox.has_value()) {
frame_time_callback.reference = 1000000 / shState->graphics().getFrameRate(); retro_usec_t new_reference = 1000000 / shState->graphics().getFrameRate();
if (new_reference != frame_time_callback.reference) {
frame_time_callback.reference = new_reference;
frame_time_callback_enabled = environment(RETRO_ENVIRONMENT_SET_FRAME_TIME_CALLBACK, &frame_time_callback);
}
}
if (!frame_time_callback_enabled) {
frame_time += frame_time_callback.reference;
} }
} }
@ -582,12 +595,6 @@ extern "C" RETRO_API bool retro_load_game(const struct retro_game_info *info) {
return false; return false;
} }
frame_time_callback.callback = [](retro_usec_t delta) {
frame_time += delta;
};
frame_time_callback.reference = 1000000 / 60;
frame_time_callback_enabled = environment(RETRO_ENVIRONMENT_SET_FRAME_TIME_CALLBACK, &frame_time_callback);
#ifdef MKXPZ_HAVE_THREADED_AUDIO #ifdef MKXPZ_HAVE_THREADED_AUDIO
audio_callback.callback = []() { audio_callback.callback = []() {
if (!shared_state_initialized.load(std::memory_order_seq_cst)) { if (!shared_state_initialized.load(std::memory_order_seq_cst)) {

View file

@ -1862,6 +1862,27 @@ void Graphics::setFrameskip(bool value) { p->useFrameSkip = value; }
Scene *Graphics::getScreen() const { return &p->screen; } Scene *Graphics::getScreen() const { return &p->screen; }
void Graphics::repaint(bool useBackBuffer) {
/* Repaint the screen with the last good frame we drew */
TEXFBO &lastFrame = useBackBuffer ? p->screen.getPP().backBuffer() : p->screen.getPP().frontBuffer();
int scaleIsSpecial = GLMeta::blitScaleIsSpecial(p->integerScaleBuffer, false, IntRect(0, 0, p->scSize.x, p->scSize.y), lastFrame, IntRect(0, 0, p->scRes.x, p->scRes.y));
GLMeta::blitBeginScreen(p->winSize, scaleIsSpecial);
GLMeta::blitSource(lastFrame, scaleIsSpecial);
FBO::clear();
p->metaBlitBufferFlippedScaled(scaleIsSpecial);
#ifndef MKXPZ_RETRO
SDL_GL_SwapWindow(p->threadData->window);
p->fpsLimiter.delay();
p->threadData->ethread->notifyFrame();
#endif // MKXPZ_RETRO
GLMeta::blitEnd();
}
void Graphics::repaintWait(const AtomicFlag &exitCond, bool checkReset) { void Graphics::repaintWait(const AtomicFlag &exitCond, bool checkReset) {
if (exitCond) if (exitCond)
return; return;

View file

@ -93,6 +93,7 @@ public:
/* <internal> */ /* <internal> */
Scene *getScreen() const; Scene *getScreen() const;
void repaint(bool useBackBuffer = false);
/* Repaint screen with static image until exitCond /* Repaint screen with static image until exitCond
* is set. Observes reset flag on top of shutdown * is set. Observes reset flag on top of shutdown
* if "checkReset" */ * if "checkReset" */