[Scummvm-git-logs] scummvm master -> 292e409938384ac0b3819a336c61fbb71dcbb9c3
spleen1981
noreply at scummvm.org
Thu Aug 20 08:42:47 UTC 2026
This automated email contains information about 6 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
bd1474923b LIBRETRO: Fix emulation thread shutdown and state race in threaded path
536840e96e LIBRETRO: Hand the frontend GL context over at thread switches
802c06ce05 LIBRETRO: Order the MIDI ring cursors against their payload
5f69c64dcd LIBRETRO: Give the cothread real stack headroom, and warn before it runs out
8911889af6 LIBRETRO: Resolve WGL entry points at runtime in the GL context handoff
292e409938 LIBRETRO: Defer context_reset's GL work to the emulation thread
Commit: bd1474923be4616e7788ad83a3b361c1720ffd44
https://github.com/scummvm/scummvm/commit/bd1474923be4616e7788ad83a3b361c1720ffd44
Author: Giovanni Cascione (ing.cascione at gmail.com)
Date: 2026-08-20T10:40:28+02:00
Commit Message:
LIBRETRO: Fix emulation thread shutdown and state race in threaded path
The threaded (non-libco) path parked the emulation thread on a condition variable
that was then destroyed with it still waiting, and never joined the thread.
The shared status byte was also updated under two different locks.
Split the two implementations, use a single lock with an explicit turn variable,
and join the thread before teardown.
Changed paths:
backends/platform/libretro/src/libretro-threads.cpp
diff --git a/backends/platform/libretro/src/libretro-threads.cpp b/backends/platform/libretro/src/libretro-threads.cpp
index e0bd61b2821..28427f699bb 100644
--- a/backends/platform/libretro/src/libretro-threads.cpp
+++ b/backends/platform/libretro/src/libretro-threads.cpp
@@ -19,47 +19,10 @@
#include "base/main.h"
#include "backends/platform/libretro/include/libretro-threads.h"
-#define EMU_WAITING (1 << 0)
-#define MAIN_WAITING (1 << 1)
-#define EMU_STARTED (1 << 2)
-#define EMU_EXITED (1 << 3)
-static uint8 status = EMU_WAITING | MAIN_WAITING;
-static int scummvm_res = -1;
-
-#ifdef USE_LIBCO
-#include <libco.h>
-static cothread_t main_thread;
-static cothread_t emu_thread;
-#else
-#include <rthreads/rthreads.h>
-static uintptr_t main_thread_id;
-static sthread_t *emu_thread;
-static slock_t *emu_lock;
-static slock_t *main_lock;
-static scond_t *emu_cond;
-static scond_t *main_cond;
-#endif
-
extern char cmd_params[20][200];
extern char cmd_params_num;
-static void retro_exit_to_main_thread() {
-#ifdef USE_LIBCO
- co_switch(main_thread);
-#else
- slock_lock(main_lock);
- status &= ~MAIN_WAITING;
- slock_unlock(main_lock);
- slock_lock(emu_lock);
- scond_signal(main_cond);
-
- status |= EMU_WAITING;
- while (status & EMU_WAITING) {
- scond_wait(emu_cond, emu_lock);
- }
- slock_unlock(emu_lock);
-#endif
-}
+static int scummvm_res = -1;
static int retro_run_emulator(void) {
static const char *argv[20] = {0};
@@ -69,8 +32,28 @@ static int retro_run_emulator(void) {
return scummvm_main(cmd_params_num, argv);
}
-static void retro_wrap_emulator(void) {
+int retro_get_scummvm_res() {
+ return scummvm_res;
+}
+
+#ifdef USE_LIBCO
+
+#include <libco.h>
+
+#define EMU_WAITING (1 << 0)
+#define MAIN_WAITING (1 << 1)
+#define EMU_STARTED (1 << 2)
+#define EMU_EXITED (1 << 3)
+static uint8 status = EMU_WAITING | MAIN_WAITING;
+static cothread_t main_thread;
+static cothread_t emu_thread;
+
+static void retro_exit_to_main_thread(void) {
+ co_switch(main_thread);
+}
+
+static void retro_wrap_emulator(void) {
status &= ~EMU_EXITED;
status |= EMU_STARTED;
scummvm_res = retro_run_emulator();
@@ -79,98 +62,188 @@ static void retro_wrap_emulator(void) {
retro_exit_to_main_thread();
}
-#ifndef USE_LIBCO
-static void retro_wrap_emulator(void *args) {
- retro_wrap_emulator();
-}
-#endif
-
-static void retro_free_emu_thread() {
-#ifdef USE_LIBCO
+static void retro_free_emu_thread(void) {
if (emu_thread)
co_delete(emu_thread);
-#else
- if (main_lock)
- slock_free(main_lock);
- if (emu_lock)
- slock_free(emu_lock);
- if (main_cond)
- scond_free(main_cond);
- if (emu_cond)
- scond_free(emu_cond);
-#endif
emu_thread = NULL;
}
-void retro_switch_to_emu_thread() {
+void retro_switch_to_emu_thread(void) {
if (retro_emu_thread_exited() || !retro_emu_thread_initialized())
return;
-#ifdef USE_LIBCO
co_switch(emu_thread);
-#else
- slock_lock(emu_lock);
- status &= ~EMU_WAITING;
- slock_unlock(emu_lock);
- slock_lock(main_lock);
- scond_signal(emu_cond);
-
- status |= MAIN_WAITING;
- while (status & MAIN_WAITING) {
- scond_wait(main_cond, main_lock);
- }
- slock_unlock(main_lock);
-#endif
}
-void retro_switch_to_main_thread() {
+void retro_switch_to_main_thread(void) {
retro_exit_to_main_thread();
}
-bool retro_emu_thread_initialized() {
+bool retro_emu_thread_initialized(void) {
return (bool)emu_thread;
}
-bool retro_emu_thread_exited() {
+bool retro_emu_thread_exited(void) {
return (bool)(status & EMU_EXITED);
}
+bool retro_emu_thread_started(void) {
+ return (bool)(status & EMU_STARTED);
+}
+
bool retro_init_emu_thread(void) {
if (retro_emu_thread_initialized())
return true;
- bool success = true;
-#ifdef USE_LIBCO
+
main_thread = co_active();
emu_thread = co_create(65536 * sizeof(void *), retro_wrap_emulator);
- if (!emu_thread)
-#else
- main_thread_id = sthread_get_current_thread_id();
- main_lock = slock_new();
- emu_lock = slock_new();
- main_cond = scond_new();
- emu_cond = scond_new();
- emu_thread = sthread_create(retro_wrap_emulator, NULL);
-
- if (!main_lock || !emu_lock || !main_cond || !emu_cond || !emu_thread)
-#endif
- success = false;
-
- if (!success)
+ if (!emu_thread) {
retro_free_emu_thread();
- else
- status &= ~(EMU_EXITED | EMU_STARTED);
+ return false;
+ }
- return success;
+ status &= ~(EMU_EXITED | EMU_STARTED);
+ return true;
}
-void retro_deinit_emu_thread() {
+void retro_deinit_emu_thread(void) {
if (retro_emu_thread_initialized())
retro_free_emu_thread();
}
-int retro_get_scummvm_res() {
- return scummvm_res;
+#else /* !USE_LIBCO */
+
+#include <rthreads/rthreads.h>
+
+#define TURN_MAIN 0
+#define TURN_EMU 1
+
+static sthread_t *emu_thread = NULL;
+static slock_t *state_lock = NULL;
+static scond_t *main_cond = NULL;
+static scond_t *emu_cond = NULL;
+
+/* Everything below is guarded by state_lock. The two threads never run
+ * concurrently - the handshake hands control back and forth - but they are
+ * distinct OS threads, so the flags still need a lock rather than the single
+ * unsynchronised byte this used to share with the libco path. */
+static uint8 turn = TURN_MAIN;
+static bool emu_started = false;
+static bool emu_exited = false;
+
+static void retro_wrap_emulator(void *args) {
+ slock_lock(state_lock);
+ while (turn != TURN_EMU)
+ scond_wait(emu_cond, state_lock);
+ slock_unlock(state_lock);
+
+ scummvm_res = retro_run_emulator();
+
+ /* Hand control back and return, rather than parking on emu_cond: the
+ * thread has to actually exit so that retro_free_emu_thread() can join
+ * it before the lock and condition variables are destroyed. */
+ slock_lock(state_lock);
+ emu_exited = true;
+ emu_started = false;
+ turn = TURN_MAIN;
+ scond_signal(main_cond);
+ slock_unlock(state_lock);
+}
+
+static void retro_free_emu_thread(void) {
+ if (emu_thread) {
+ sthread_join(emu_thread);
+ emu_thread = NULL;
+ }
+ if (main_cond) {
+ scond_free(main_cond);
+ main_cond = NULL;
+ }
+ if (emu_cond) {
+ scond_free(emu_cond);
+ emu_cond = NULL;
+ }
+ if (state_lock) {
+ slock_free(state_lock);
+ state_lock = NULL;
+ }
+ emu_started = false;
+}
+
+void retro_switch_to_emu_thread(void) {
+ if (retro_emu_thread_exited() || !retro_emu_thread_initialized())
+ return;
+
+ slock_lock(state_lock);
+ turn = TURN_EMU;
+ scond_signal(emu_cond);
+ while (turn != TURN_MAIN)
+ scond_wait(main_cond, state_lock);
+ slock_unlock(state_lock);
+}
+
+void retro_switch_to_main_thread(void) {
+ slock_lock(state_lock);
+ turn = TURN_MAIN;
+ scond_signal(main_cond);
+ while (turn != TURN_EMU)
+ scond_wait(emu_cond, state_lock);
+ slock_unlock(state_lock);
+}
+
+bool retro_emu_thread_initialized(void) {
+ return emu_thread != NULL;
+}
+
+bool retro_emu_thread_exited(void) {
+ bool ret;
+ if (!state_lock)
+ return false;
+ slock_lock(state_lock);
+ ret = emu_exited;
+ slock_unlock(state_lock);
+ return ret;
}
bool retro_emu_thread_started(void) {
- return (bool)(status & EMU_STARTED);
+ bool ret;
+ if (!state_lock)
+ return false;
+ slock_lock(state_lock);
+ ret = emu_started;
+ slock_unlock(state_lock);
+ return ret;
+}
+
+bool retro_init_emu_thread(void) {
+ if (retro_emu_thread_initialized())
+ return true;
+
+ state_lock = slock_new();
+ main_cond = scond_new();
+ emu_cond = scond_new();
+
+ if (!state_lock || !main_cond || !emu_cond) {
+ retro_free_emu_thread();
+ return false;
+ }
+
+ turn = TURN_MAIN;
+ emu_exited = false;
+ emu_started = true;
+
+ emu_thread = sthread_create(retro_wrap_emulator, NULL);
+ if (!emu_thread) {
+ emu_started = false;
+ retro_free_emu_thread();
+ return false;
+ }
+
+ return true;
}
+
+void retro_deinit_emu_thread(void) {
+ if (retro_emu_thread_initialized())
+ retro_free_emu_thread();
+}
+
+#endif /* USE_LIBCO */
Commit: 536840e96e94ac9e1a3e66ec8d35aad3f7e66f60
https://github.com/scummvm/scummvm/commit/536840e96e94ac9e1a3e66ec8d35aad3f7e66f60
Author: Giovanni Cascione (ing.cascione at gmail.com)
Date: 2026-08-20T10:40:46+02:00
Commit Message:
LIBRETRO: Hand the frontend GL context over at thread switches
ScummVM issues its GL calls from the emulation thread, which only works under libco
because both halves share one OS thread. On a real thread there is no current context there.
Move the context with control: release it before yielding, reacquire it after resuming.
No-op under libco or when no context is current.
Changed paths:
A backends/platform/libretro/include/libretro-gl-context-handoff.h
A backends/platform/libretro/src/libretro-gl-context-handoff.cpp
backends/platform/libretro/Makefile.common
backends/platform/libretro/src/libretro-core.cpp
backends/platform/libretro/src/libretro-threads.cpp
diff --git a/backends/platform/libretro/Makefile.common b/backends/platform/libretro/Makefile.common
index 1213a80a829..8370b1e38d7 100644
--- a/backends/platform/libretro/Makefile.common
+++ b/backends/platform/libretro/Makefile.common
@@ -287,6 +287,8 @@ ifeq ($(or $(HAVE_OPENGL), $(HAVE_OPENGLES2)), 1)
LIBRETRO_OBJS += $(CORE_PATH)/libretro-graphics-opengl.o
endif
+LIBRETRO_OBJS += $(CORE_PATH)/libretro-gl-context-handoff.o
+
OBJS += $(LIBRETRO_OBJS)
######################################################################
diff --git a/backends/platform/libretro/include/libretro-gl-context-handoff.h b/backends/platform/libretro/include/libretro-gl-context-handoff.h
new file mode 100644
index 00000000000..78cada690cc
--- /dev/null
+++ b/backends/platform/libretro/include/libretro-gl-context-handoff.h
@@ -0,0 +1,52 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef LIBRETRO_GL_CONTEXT_HANDOFF_H
+#define LIBRETRO_GL_CONTEXT_HANDOFF_H
+
+/* The frontend makes its rendering context current on the thread that drives
+ * retro_run(). ScummVM issues its GL calls from inside scummvm_main(), which
+ * runs on the emulation thread, so the context has to travel with control at
+ * every handoff.
+ *
+ * The two threads never run concurrently, so no reference counting is needed:
+ * whichever thread is about to block releases the context, and whichever
+ * thread is about to run acquires it. Visibility of the saved handles between
+ * the two threads is provided by the lock in libretro-threads.cpp.
+ *
+ * These are no-ops when no context is current (software rendering) and when
+ * the core is built with libco, where both halves share one OS thread.
+ */
+
+/* Unbind the context from the calling thread, remembering what was bound.
+ * Call immediately before handing control to the other thread. */
+void retro_gl_context_release(void);
+
+/* Rebind whatever the last retro_gl_context_release() unbound. Call
+ * immediately after regaining control. */
+void retro_gl_context_acquire(void);
+
+/* Whether a context handoff backend is available on this platform. Requesting
+ * hardware rendering without one would leave the emulation thread issuing GL
+ * calls against no current context, so the caller falls back to software. */
+bool retro_gl_context_handoff_available(void);
+
+#endif
diff --git a/backends/platform/libretro/src/libretro-core.cpp b/backends/platform/libretro/src/libretro-core.cpp
index 56fc7048724..c60cd499919 100644
--- a/backends/platform/libretro/src/libretro-core.cpp
+++ b/backends/platform/libretro/src/libretro-core.cpp
@@ -53,6 +53,7 @@
#include "backends/platform/libretro/include/libretro-defs.h"
#include "backends/platform/libretro/include/libretro-core.h"
+#include "backends/platform/libretro/include/libretro-gl-context-handoff.h"
#include "backends/platform/libretro/include/libretro-threads.h"
#include "backends/platform/libretro/include/libretro-core-options.h"
#include "backends/platform/libretro/include/libretro-os.h"
@@ -150,6 +151,17 @@ static void setup_hw_rendering(void) {
enum retro_pixel_format pixel_fmt;
#ifdef USE_OPENGL
+ /* ScummVM issues its GL calls from the emulation thread, so the frontend's
+ context has to travel with control at every thread switch. Without a
+ backend for that handoff those calls would land on a thread with no
+ current context, so stay on the software renderer instead. */
+ if ((video_hw_mode & VIDEO_GRAPHIC_MODE_REQUEST_HW) && !retro_gl_context_handoff_available()) {
+ if (retro_log_cb)
+ retro_log_cb(RETRO_LOG_WARN, "No GL context handoff backend available, falling back to software.\n");
+ retro_osd_notification("HW rendering unavailable on this platform.");
+ video_hw_mode = VIDEO_GRAPHIC_MODE_REQUEST_SW;
+ }
+
if (video_hw_mode & VIDEO_GRAPHIC_MODE_REQUEST_HW) {
pixel_fmt = RETRO_PIXEL_FORMAT_XRGB8888;
if (!environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &pixel_fmt) && retro_log_cb)
diff --git a/backends/platform/libretro/src/libretro-gl-context-handoff.cpp b/backends/platform/libretro/src/libretro-gl-context-handoff.cpp
new file mode 100644
index 00000000000..f7a4b2ce04d
--- /dev/null
+++ b/backends/platform/libretro/src/libretro-gl-context-handoff.cpp
@@ -0,0 +1,272 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "common/scummsys.h"
+#include "backends/platform/libretro/include/libretro-gl-context-handoff.h"
+
+#if defined(USE_LIBCO) || !defined(USE_OPENGL)
+
+/* Both halves share an OS thread, or there is no GL at all: nothing to move. */
+void retro_gl_context_release(void) {}
+void retro_gl_context_acquire(void) {}
+bool retro_gl_context_handoff_available(void) {
+ return true;
+}
+
+#else
+
+#include <stddef.h>
+
+#if defined(_WIN32)
+#define CONTEXT_BACKEND_WGL
+#include <windows.h>
+#elif defined(__APPLE__)
+#define CONTEXT_BACKEND_CGL
+#include <dlfcn.h>
+#elif defined(HAVE_DLFCN) || defined(__linux__) || defined(__ANDROID__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__HAIKU__)
+#define CONTEXT_BACKEND_DL
+#include <dlfcn.h>
+#endif
+
+/* Resolved lazily on first use and then latched, so a platform with no
+ * backend costs one failed lookup rather than one per frame. */
+enum ContextBackend {
+ kBackendUnprobed = 0,
+ kBackendNone,
+ kBackendEGL,
+ kBackendGLX,
+ kBackendWGL,
+ kBackendCGL
+};
+
+static ContextBackend _backend = kBackendUnprobed;
+static bool _held = false;
+
+#if defined(CONTEXT_BACKEND_DL)
+
+/* EGL. Declared locally rather than pulling in EGL/egl.h, which is not
+ * available on every target that can dlopen libEGL. */
+#define LOCAL_EGL_DRAW 0x3059
+#define LOCAL_EGL_READ 0x305A
+
+typedef void *(*eglGetCurrentDisplay_t)(void);
+typedef void *(*eglGetCurrentContext_t)(void);
+typedef void *(*eglGetCurrentSurface_t)(int readdraw);
+typedef unsigned (*eglMakeCurrent_t)(void *dpy, void *draw, void *read, void *ctx);
+
+static eglGetCurrentDisplay_t _eglGetCurrentDisplay = NULL;
+static eglGetCurrentContext_t _eglGetCurrentContext = NULL;
+static eglGetCurrentSurface_t _eglGetCurrentSurface = NULL;
+static eglMakeCurrent_t _eglMakeCurrent = NULL;
+
+static void *_egl_dpy = NULL;
+static void *_egl_ctx = NULL;
+static void *_egl_draw = NULL;
+static void *_egl_read = NULL;
+
+/* GLX. Same reasoning; GLXDrawable is an XID, i.e. unsigned long. */
+typedef void *(*glXGetCurrentDisplay_t)(void);
+typedef void *(*glXGetCurrentContext_t)(void);
+typedef unsigned long (*glXGetCurrentDrawable_t)(void);
+typedef unsigned long (*glXGetCurrentReadDrawable_t)(void);
+typedef int (*glXMakeContextCurrent_t)(void *dpy, unsigned long draw, unsigned long read, void *ctx);
+
+static glXGetCurrentDisplay_t _glXGetCurrentDisplay = NULL;
+static glXGetCurrentContext_t _glXGetCurrentContext = NULL;
+static glXGetCurrentDrawable_t _glXGetCurrentDrawable = NULL;
+static glXGetCurrentReadDrawable_t _glXGetCurrentReadDrawable = NULL;
+static glXMakeContextCurrent_t _glXMakeContextCurrent = NULL;
+
+static void *_glx_dpy = NULL;
+static void *_glx_ctx = NULL;
+static unsigned long _glx_draw = 0;
+static unsigned long _glx_read = 0;
+
+static void *openLibrary(const char *const *names) {
+ for (int i = 0; names[i]; i++) {
+ void *handle = dlopen(names[i], RTLD_LAZY | RTLD_LOCAL);
+ if (handle)
+ return handle;
+ }
+ return NULL;
+}
+
+static bool probeEGL(void) {
+ static const char *const names[] = {"libEGL.so.1", "libEGL.so", NULL};
+ void *lib = openLibrary(names);
+ if (!lib)
+ return false;
+
+ _eglGetCurrentDisplay = (eglGetCurrentDisplay_t)dlsym(lib, "eglGetCurrentDisplay");
+ _eglGetCurrentContext = (eglGetCurrentContext_t)dlsym(lib, "eglGetCurrentContext");
+ _eglGetCurrentSurface = (eglGetCurrentSurface_t)dlsym(lib, "eglGetCurrentSurface");
+ _eglMakeCurrent = (eglMakeCurrent_t)dlsym(lib, "eglMakeCurrent");
+
+ return _eglGetCurrentDisplay && _eglGetCurrentContext && _eglGetCurrentSurface && _eglMakeCurrent;
+}
+
+static bool probeGLX(void) {
+ static const char *const names[] = {"libGL.so.1", "libGL.so", NULL};
+ void *lib = openLibrary(names);
+ if (!lib)
+ return false;
+
+ _glXGetCurrentDisplay = (glXGetCurrentDisplay_t)dlsym(lib, "glXGetCurrentDisplay");
+ _glXGetCurrentContext = (glXGetCurrentContext_t)dlsym(lib, "glXGetCurrentContext");
+ _glXGetCurrentDrawable = (glXGetCurrentDrawable_t)dlsym(lib, "glXGetCurrentDrawable");
+ _glXGetCurrentReadDrawable = (glXGetCurrentReadDrawable_t)dlsym(lib, "glXGetCurrentReadDrawable");
+ _glXMakeContextCurrent = (glXMakeContextCurrent_t)dlsym(lib, "glXMakeContextCurrent");
+
+ return _glXGetCurrentDisplay && _glXGetCurrentContext && _glXGetCurrentDrawable && _glXGetCurrentReadDrawable && _glXMakeContextCurrent;
+}
+
+#elif defined(CONTEXT_BACKEND_CGL)
+
+typedef void *(*CGLGetCurrentContext_t)(void);
+typedef int (*CGLSetCurrentContext_t)(void *ctx);
+
+static CGLGetCurrentContext_t _CGLGetCurrentContext = NULL;
+static CGLSetCurrentContext_t _CGLSetCurrentContext = NULL;
+static void *_cgl_ctx = NULL;
+
+static bool probeCGL(void) {
+ void *lib = dlopen("/System/Library/Frameworks/OpenGL.framework/OpenGL", RTLD_LAZY | RTLD_LOCAL);
+ if (!lib)
+ return false;
+
+ _CGLGetCurrentContext = (CGLGetCurrentContext_t)dlsym(lib, "CGLGetCurrentContext");
+ _CGLSetCurrentContext = (CGLSetCurrentContext_t)dlsym(lib, "CGLSetCurrentContext");
+
+ return _CGLGetCurrentContext && _CGLSetCurrentContext;
+}
+
+#elif defined(CONTEXT_BACKEND_WGL)
+
+static HDC _wgl_dc = NULL;
+static HGLRC _wgl_ctx = NULL;
+
+#endif
+
+static ContextBackend probeBackend(void) {
+#if defined(CONTEXT_BACKEND_DL)
+ if (probeEGL())
+ return kBackendEGL;
+ if (probeGLX())
+ return kBackendGLX;
+ return kBackendNone;
+#elif defined(CONTEXT_BACKEND_CGL)
+ return probeCGL() ? kBackendCGL : kBackendNone;
+#elif defined(CONTEXT_BACKEND_WGL)
+ return kBackendWGL;
+#else
+ return kBackendNone;
+#endif
+}
+
+bool retro_gl_context_handoff_available(void) {
+ if (_backend == kBackendUnprobed)
+ _backend = probeBackend();
+
+ return _backend != kBackendNone;
+}
+
+void retro_gl_context_release(void) {
+ if (!retro_gl_context_handoff_available())
+ return;
+
+ _held = false;
+
+ switch (_backend) {
+#if defined(CONTEXT_BACKEND_DL)
+ case kBackendEGL:
+ _egl_dpy = _eglGetCurrentDisplay();
+ _egl_ctx = _eglGetCurrentContext();
+ if (!_egl_dpy || !_egl_ctx)
+ return;
+ _egl_draw = _eglGetCurrentSurface(LOCAL_EGL_DRAW);
+ _egl_read = _eglGetCurrentSurface(LOCAL_EGL_READ);
+ _eglMakeCurrent(_egl_dpy, NULL, NULL, NULL);
+ _held = true;
+ return;
+
+ case kBackendGLX:
+ _glx_dpy = _glXGetCurrentDisplay();
+ _glx_ctx = _glXGetCurrentContext();
+ if (!_glx_dpy || !_glx_ctx)
+ return;
+ _glx_draw = _glXGetCurrentDrawable();
+ _glx_read = _glXGetCurrentReadDrawable();
+ _glXMakeContextCurrent(_glx_dpy, 0, 0, NULL);
+ _held = true;
+ return;
+#elif defined(CONTEXT_BACKEND_CGL)
+ case kBackendCGL:
+ _cgl_ctx = _CGLGetCurrentContext();
+ if (!_cgl_ctx)
+ return;
+ _CGLSetCurrentContext(NULL);
+ _held = true;
+ return;
+#elif defined(CONTEXT_BACKEND_WGL)
+ case kBackendWGL:
+ _wgl_dc = wglGetCurrentDC();
+ _wgl_ctx = wglGetCurrentContext();
+ if (!_wgl_dc || !_wgl_ctx)
+ return;
+ wglMakeCurrent(NULL, NULL);
+ _held = true;
+ return;
+#endif
+ default:
+ return;
+ }
+}
+
+void retro_gl_context_acquire(void) {
+ if (!_held)
+ return;
+
+ _held = false;
+
+ switch (_backend) {
+#if defined(CONTEXT_BACKEND_DL)
+ case kBackendEGL:
+ _eglMakeCurrent(_egl_dpy, _egl_draw, _egl_read, _egl_ctx);
+ return;
+
+ case kBackendGLX:
+ _glXMakeContextCurrent(_glx_dpy, _glx_draw, _glx_read, _glx_ctx);
+ return;
+#elif defined(CONTEXT_BACKEND_CGL)
+ case kBackendCGL:
+ _CGLSetCurrentContext(_cgl_ctx);
+ return;
+#elif defined(CONTEXT_BACKEND_WGL)
+ case kBackendWGL:
+ wglMakeCurrent(_wgl_dc, _wgl_ctx);
+ return;
+#endif
+ default:
+ return;
+ }
+}
+
+#endif /* USE_LIBCO || !USE_OPENGL */
diff --git a/backends/platform/libretro/src/libretro-threads.cpp b/backends/platform/libretro/src/libretro-threads.cpp
index 28427f699bb..f6e689d5bd9 100644
--- a/backends/platform/libretro/src/libretro-threads.cpp
+++ b/backends/platform/libretro/src/libretro-threads.cpp
@@ -17,6 +17,8 @@
#include <stdio.h>
#include <libretro.h>
#include "base/main.h"
+#include "backends/platform/libretro/include/libretro-core.h"
+#include "backends/platform/libretro/include/libretro-gl-context-handoff.h"
#include "backends/platform/libretro/include/libretro-threads.h"
extern char cmd_params[20][200];
@@ -135,9 +137,12 @@ static void retro_wrap_emulator(void *args) {
while (turn != TURN_EMU)
scond_wait(emu_cond, state_lock);
slock_unlock(state_lock);
+ retro_gl_context_acquire();
scummvm_res = retro_run_emulator();
+ retro_gl_context_release();
+
/* Hand control back and return, rather than parking on emu_cond: the
* thread has to actually exit so that retro_free_emu_thread() can join
* it before the lock and condition variables are destroyed. */
@@ -173,21 +178,25 @@ void retro_switch_to_emu_thread(void) {
if (retro_emu_thread_exited() || !retro_emu_thread_initialized())
return;
+ retro_gl_context_release();
slock_lock(state_lock);
turn = TURN_EMU;
scond_signal(emu_cond);
while (turn != TURN_MAIN)
scond_wait(main_cond, state_lock);
slock_unlock(state_lock);
+ retro_gl_context_acquire();
}
void retro_switch_to_main_thread(void) {
+ retro_gl_context_release();
slock_lock(state_lock);
turn = TURN_MAIN;
scond_signal(main_cond);
while (turn != TURN_EMU)
scond_wait(emu_cond, state_lock);
slock_unlock(state_lock);
+ retro_gl_context_acquire();
}
bool retro_emu_thread_initialized(void) {
Commit: 802c06ce0517c16fb237647b02fd461d064d815a
https://github.com/scummvm/scummvm/commit/802c06ce0517c16fb237647b02fd461d064d815a
Author: Giovanni Cascione (ing.cascione at gmail.com)
Date: 2026-08-20T10:41:10+02:00
Commit Message:
LIBRETRO: Order the MIDI ring cursors against their payload
Changed paths:
backends/platform/libretro/src/libretro-core.cpp
diff --git a/backends/platform/libretro/src/libretro-core.cpp b/backends/platform/libretro/src/libretro-core.cpp
index c60cd499919..a9fc5101c29 100644
--- a/backends/platform/libretro/src/libretro-core.cpp
+++ b/backends/platform/libretro/src/libretro-core.cpp
@@ -41,6 +41,7 @@
#endif
#include <features/features_cpu.h> // cpu_features_get_time_usec()
+#include <retro_atomic.h>
/**
* Include base/internal_version.h to allow access to SCUMMVM_VERSION.
@@ -143,9 +144,15 @@ static void retro_gui_res_reset() {
}
#endif
+/* Single-producer / single-consumer ring. The producer is ScummVM's MIDI
+ driver, the consumer is retro_midi_queue_drain() in retro_run(). With
+ USE_LIBCO those are the same OS thread and the fences below cost nothing;
+ without it they are two threads, and 'volatile' does not order the payload
+ stores against the cursor publish - the consumer could see an index before
+ the event it points at. */
static retro_midi_event_t midi_queue[MIDI_QUEUE_SIZE];
-static volatile uint32 midi_head = 0; /* producer writes */
-static volatile uint32 midi_tail = 0; /* consumer writes */
+static retro_atomic_int_t midi_head = RETRO_ATOMIC_INT_INITIALIZER(0); /* published by producer */
+static retro_atomic_int_t midi_tail = RETRO_ATOMIC_INT_INITIALIZER(0); /* published by consumer */
static void setup_hw_rendering(void) {
@@ -922,16 +929,22 @@ const char *retro_get_playlist_dir(void) {
}
void retro_midi_queue_push(uint8 byte, uint32 delta_us) {
- uint32 next = (midi_head + 1) & (MIDI_QUEUE_SIZE - 1);
+ /* The producer owns head, so it can read it plainly; tail needs an
+ acquire load to pair with the consumer's release below. */
+ int head = retro_atomic_load_acquire_int(&midi_head);
+ int next = (head + 1) & (MIDI_QUEUE_SIZE - 1);
- if (next == midi_tail) {
+ if (next == retro_atomic_load_acquire_int(&midi_tail)) {
/* Queue full â drop event (acceptable for MIDI) */
return;
}
- midi_queue[midi_head].byte = byte;
- midi_queue[midi_head].delta_us = delta_us;
- midi_head = next;
+ midi_queue[head].byte = byte;
+ midi_queue[head].delta_us = delta_us;
+
+ /* Release: the two stores above are visible to any thread that
+ acquire-loads this index. */
+ retro_atomic_store_release_int(&midi_head, next);
}
static void retro_midi_queue_drain(void) {
@@ -943,17 +956,23 @@ static void retro_midi_queue_drain(void) {
return;
bool did_write = false;
+ int tail = retro_atomic_load_acquire_int(&midi_tail);
+ int head = retro_atomic_load_acquire_int(&midi_head);
- while (midi_tail != midi_head) {
- retro_midi_event_t ev = midi_queue[midi_tail];
- midi_tail = (midi_tail + 1) & (MIDI_QUEUE_SIZE - 1);
+ while (tail != head) {
+ retro_midi_event_t ev = midi_queue[tail];
+ tail = (tail + 1) & (MIDI_QUEUE_SIZE - 1);
retro_midi_interface->write(ev.byte, ev.delta_us);
did_write = true;
}
- if (did_write)
+ if (did_write) {
+ /* Publish once: the producer only needs to know the slots are
+ free, not how far along the drain got. */
+ retro_atomic_store_release_int(&midi_tail, tail);
retro_midi_interface->flush();
+ }
}
void retro_init(void) {
Commit: 5f69c64dcdf49900cc4ece1b1e0047f3993145d7
https://github.com/scummvm/scummvm/commit/5f69c64dcdf49900cc4ece1b1e0047f3993145d7
Author: Giovanni Cascione (ing.cascione at gmail.com)
Date: 2026-08-20T10:41:29+02:00
Commit Message:
LIBRETRO: Give the cothread real stack headroom, and warn before it runs out
Changed paths:
backends/platform/libretro/src/libretro-threads.cpp
diff --git a/backends/platform/libretro/src/libretro-threads.cpp b/backends/platform/libretro/src/libretro-threads.cpp
index f6e689d5bd9..c81ecd33c91 100644
--- a/backends/platform/libretro/src/libretro-threads.cpp
+++ b/backends/platform/libretro/src/libretro-threads.cpp
@@ -42,6 +42,31 @@ int retro_get_scummvm_res() {
#include <libco.h>
+#ifndef EMU_THREAD_STACK_SIZE
+#define EMU_THREAD_STACK_SIZE (1024 * 1024)
+#endif
+
+/* Since there is no guard page, the only warning available is a watermark. */
+static const char *stack_anchor = NULL;
+static bool stack_warned = false;
+
+static void check_stack_headroom(void) {
+ char probe;
+ size_t used;
+
+ if (!stack_anchor || stack_warned)
+ return;
+
+ used = (size_t)(stack_anchor - &probe);
+ if (used < (EMU_THREAD_STACK_SIZE / 4) * 3)
+ return;
+
+ stack_warned = true;
+ if (retro_log_cb)
+ retro_log_cb(RETRO_LOG_WARN, "[scummvm] Emulation stack at %u KB of %u KB; libco stacks have no guard page.\n",
+ (unsigned)(used / 1024), (unsigned)(EMU_THREAD_STACK_SIZE / 1024));
+}
+
#define EMU_WAITING (1 << 0)
#define MAIN_WAITING (1 << 1)
#define EMU_STARTED (1 << 2)
@@ -52,10 +77,14 @@ static cothread_t main_thread;
static cothread_t emu_thread;
static void retro_exit_to_main_thread(void) {
+ check_stack_headroom();
co_switch(main_thread);
}
static void retro_wrap_emulator(void) {
+ char anchor;
+ stack_anchor = &anchor;
+
status &= ~EMU_EXITED;
status |= EMU_STARTED;
scummvm_res = retro_run_emulator();
@@ -97,7 +126,7 @@ bool retro_init_emu_thread(void) {
return true;
main_thread = co_active();
- emu_thread = co_create(65536 * sizeof(void *), retro_wrap_emulator);
+ emu_thread = co_create(EMU_THREAD_STACK_SIZE, retro_wrap_emulator);
if (!emu_thread) {
retro_free_emu_thread();
return false;
Commit: 8911889af68acc1e089859abcec9fa356ec50fc6
https://github.com/scummvm/scummvm/commit/8911889af68acc1e089859abcec9fa356ec50fc6
Author: Giovanni Cascione (ing.cascione at gmail.com)
Date: 2026-08-20T10:41:50+02:00
Commit Message:
LIBRETRO: Resolve WGL entry points at runtime in the GL context handoff
Changed paths:
backends/platform/libretro/src/libretro-gl-context-handoff.cpp
diff --git a/backends/platform/libretro/src/libretro-gl-context-handoff.cpp b/backends/platform/libretro/src/libretro-gl-context-handoff.cpp
index f7a4b2ce04d..18002b6b135 100644
--- a/backends/platform/libretro/src/libretro-gl-context-handoff.cpp
+++ b/backends/platform/libretro/src/libretro-gl-context-handoff.cpp
@@ -160,9 +160,33 @@ static bool probeCGL(void) {
#elif defined(CONTEXT_BACKEND_WGL)
-static HDC _wgl_dc = NULL;
+/* Resolved from opengl32.dll at runtime, like the dlopen backends above, so
+ * the core does not need to be linked against opengl32. */
+typedef HDC (WINAPI *wglGetCurrentDC_t)(void);
+typedef HGLRC (WINAPI *wglGetCurrentContext_t)(void);
+typedef BOOL (WINAPI *wglMakeCurrent_t)(HDC, HGLRC);
+
+static wglGetCurrentDC_t _wglGetCurrentDC = NULL;
+static wglGetCurrentContext_t _wglGetCurrentContext = NULL;
+static wglMakeCurrent_t _wglMakeCurrent = NULL;
+
+static HDC _wgl_dc = NULL;
static HGLRC _wgl_ctx = NULL;
+static bool probeWGL(void) {
+ HMODULE lib = GetModuleHandleA("opengl32.dll");
+ if (!lib)
+ lib = LoadLibraryA("opengl32.dll");
+ if (!lib)
+ return false;
+
+ _wglGetCurrentDC = (wglGetCurrentDC_t)(void *)GetProcAddress(lib, "wglGetCurrentDC");
+ _wglGetCurrentContext = (wglGetCurrentContext_t)(void *)GetProcAddress(lib, "wglGetCurrentContext");
+ _wglMakeCurrent = (wglMakeCurrent_t)(void *)GetProcAddress(lib, "wglMakeCurrent");
+
+ return _wglGetCurrentDC && _wglGetCurrentContext && _wglMakeCurrent;
+}
+
#endif
static ContextBackend probeBackend(void) {
@@ -175,7 +199,7 @@ static ContextBackend probeBackend(void) {
#elif defined(CONTEXT_BACKEND_CGL)
return probeCGL() ? kBackendCGL : kBackendNone;
#elif defined(CONTEXT_BACKEND_WGL)
- return kBackendWGL;
+ return probeWGL() ? kBackendWGL : kBackendNone;
#else
return kBackendNone;
#endif
@@ -227,11 +251,11 @@ void retro_gl_context_release(void) {
return;
#elif defined(CONTEXT_BACKEND_WGL)
case kBackendWGL:
- _wgl_dc = wglGetCurrentDC();
- _wgl_ctx = wglGetCurrentContext();
+ _wgl_dc = _wglGetCurrentDC();
+ _wgl_ctx = _wglGetCurrentContext();
if (!_wgl_dc || !_wgl_ctx)
return;
- wglMakeCurrent(NULL, NULL);
+ _wglMakeCurrent(NULL, NULL);
_held = true;
return;
#endif
@@ -261,7 +285,7 @@ void retro_gl_context_acquire(void) {
return;
#elif defined(CONTEXT_BACKEND_WGL)
case kBackendWGL:
- wglMakeCurrent(_wgl_dc, _wgl_ctx);
+ _wglMakeCurrent(_wgl_dc, _wgl_ctx);
return;
#endif
default:
Commit: 292e409938384ac0b3819a336c61fbb71dcbb9c3
https://github.com/scummvm/scummvm/commit/292e409938384ac0b3819a336c61fbb71dcbb9c3
Author: Giovanni Cascione (ing.cascione at gmail.com)
Date: 2026-08-20T10:42:11+02:00
Commit Message:
LIBRETRO: Defer context_reset's GL work to the emulation thread
Changed paths:
backends/platform/libretro/include/libretro-threads.h
backends/platform/libretro/src/libretro-core.cpp
backends/platform/libretro/src/libretro-timer.cpp
diff --git a/backends/platform/libretro/include/libretro-threads.h b/backends/platform/libretro/include/libretro-threads.h
index 8dfbfe61a28..637fae6722f 100644
--- a/backends/platform/libretro/include/libretro-threads.h
+++ b/backends/platform/libretro/include/libretro-threads.h
@@ -63,5 +63,12 @@ int retro_get_scummvm_res(void);
*/
bool retro_emu_thread_started(void);
+
+/* Set from the frontend's context_reset (main thread); consumed on the
+ * emulation thread, where the GL context is current, to run the actual
+ * graphics-context reset. */
+void retro_set_context_reset_pending(void);
+bool retro_consume_context_reset(void);
+
#endif
diff --git a/backends/platform/libretro/src/libretro-core.cpp b/backends/platform/libretro/src/libretro-core.cpp
index a9fc5101c29..7848191c733 100644
--- a/backends/platform/libretro/src/libretro-core.cpp
+++ b/backends/platform/libretro/src/libretro-core.cpp
@@ -115,10 +115,25 @@ static bool updating_variables = false;
#ifdef USE_OPENGL
static struct retro_hw_render_callback hw_render;
+static bool context_reset_pending = false;
+
+void retro_set_context_reset_pending(void) {
+ context_reset_pending = true;
+}
+
+bool retro_consume_context_reset(void) {
+ bool pending = context_reset_pending;
+ context_reset_pending = false;
+ return pending;
+}
+
static void context_reset(void) {
retro_log_cb(RETRO_LOG_DEBUG, "HW context reset\n");
+ /* The reset re-creates the GL context and reloads all GL entry points,
+ which must happen on the emulation thread where the context is current.
+ Defer it instead of calling it here on the frontend thread. */
if (retro_emu_thread_started())
- LIBRETRO_G_SYSTEM->resetGraphicsContext();
+ retro_set_context_reset_pending();
}
static void context_destroy(void) {
diff --git a/backends/platform/libretro/src/libretro-timer.cpp b/backends/platform/libretro/src/libretro-timer.cpp
index d2a6f2e03bc..e8c2a1a58d5 100644
--- a/backends/platform/libretro/src/libretro-timer.cpp
+++ b/backends/platform/libretro/src/libretro-timer.cpp
@@ -31,6 +31,10 @@ LibretroTimerManager::LibretroTimerManager(uint32 refresh_rate) {
void LibretroTimerManager::switchThread(uint8 caller) {
_spentOnMainThread = g_system->getMillis();
_threadSwitchCaller = caller;
+#ifdef USE_OPENGL
+ if (retro_consume_context_reset())
+ LIBRETRO_G_SYSTEM->resetGraphicsContext();
+#endif
LIBRETRO_G_SYSTEM->refreshScreen();
retro_switch_to_main_thread();
_spentOnMainThread = g_system->getMillis() - _spentOnMainThread;
More information about the Scummvm-git-logs
mailing list