From a34d3d77eb5b0dc92a5a8ea261d3ba0e1d69614a Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Sun, 30 Nov 2025 09:52:28 -0800 Subject: [PATCH 1/4] Clarify which thread is the main thread If video is initialized, the main thread is the one that initialized video, otherwise if events are initialized, the main thread is the thread that initialized events, otherwise the main thread is the one that called the main function. Fixes https://github.com/libsdl-org/SDL/issues/14511 --- src/SDL.c | 36 ++++++++++++++++++------ src/SDL_internal.h | 1 + src/events/SDL_events.c | 3 ++ src/main/SDL_main_callbacks.c | 12 ++++++-- src/main/SDL_main_callbacks.h | 7 ++--- src/main/SDL_runapp.c | 3 +- src/main/emscripten/SDL_sysmain_runapp.c | 4 +-- src/main/gdk/SDL_sysmain_runapp.cpp | 5 ++-- src/main/n3ds/SDL_sysmain_runapp.c | 5 +--- src/main/ps2/SDL_sysmain_runapp.c | 10 ++----- src/main/psp/SDL_sysmain_runapp.c | 6 +--- src/main/windows/SDL_sysmain_runapp.c | 4 +-- src/video/uikit/SDL_uikitappdelegate.m | 6 ++-- 13 files changed, 56 insertions(+), 46 deletions(-) diff --git a/src/SDL.c b/src/SDL.c index 4a879893f2c83..f9eaa0f1c312d 100644 --- a/src/SDL.c +++ b/src/SDL.c @@ -189,6 +189,8 @@ static bool SDL_MainIsReady = false; static bool SDL_MainIsReady = true; #endif static SDL_ThreadID SDL_MainThreadID = 0; +static SDL_ThreadID SDL_EventsThreadID = 0; +static SDL_ThreadID SDL_VideoThreadID = 0; static bool SDL_bInMainQuit = false; static Uint8 SDL_SubsystemRefCount[32]; @@ -266,14 +268,16 @@ void SDL_SetMainReady(void) bool SDL_IsMainThread(void) { - if (SDL_MainThreadID == 0) { - // Not initialized yet? - return true; + if (SDL_VideoThreadID) { + return (SDL_GetCurrentThreadID() == SDL_VideoThreadID); } - if (SDL_MainThreadID == SDL_GetCurrentThreadID()) { - return true; + if (SDL_EventsThreadID) { + return (SDL_GetCurrentThreadID() == SDL_EventsThreadID); } - return false; + if (SDL_MainThreadID) { + return (SDL_GetCurrentThreadID() == SDL_MainThreadID); + } + return true; } // Initialize all the subsystems that require initialization before threads start @@ -281,6 +285,11 @@ void SDL_InitMainThread(void) { static bool done_info = false; + // If we haven't done it by now, mark this as the main thread + if (SDL_MainThreadID == 0) { + SDL_MainThreadID = SDL_GetCurrentThreadID(); + } + SDL_InitTLSData(); SDL_InitEnvironment(); SDL_InitTicks(); @@ -335,6 +344,11 @@ bool SDL_InitSubSystem(SDL_InitFlags flags) if (flags & SDL_INIT_EVENTS) { if (SDL_ShouldInitSubsystem(SDL_INIT_EVENTS)) { SDL_IncrementSubsystemRefCount(SDL_INIT_EVENTS); + + // Note which thread initialized events + // This is the thread which should be pumping events + SDL_EventsThreadID = SDL_GetCurrentThreadID(); + if (!SDL_InitEvents()) { SDL_DecrementSubsystemRefCount(SDL_INIT_EVENTS); goto quit_and_error; @@ -354,12 +368,16 @@ bool SDL_InitSubSystem(SDL_InitFlags flags) goto quit_and_error; } + SDL_IncrementSubsystemRefCount(SDL_INIT_VIDEO); + // We initialize video on the main thread // On Apple platforms this is a requirement. // On other platforms, this is the definition. - SDL_MainThreadID = SDL_GetCurrentThreadID(); + SDL_VideoThreadID = SDL_GetCurrentThreadID(); +#ifdef SDL_PLATFORM_APPLE + SDL_assert(SDL_VideoThreadID == SDL_MainThreadID); +#endif - SDL_IncrementSubsystemRefCount(SDL_INIT_VIDEO); if (!SDL_VideoInit(NULL)) { SDL_DecrementSubsystemRefCount(SDL_INIT_VIDEO); SDL_PushError(); @@ -609,6 +627,7 @@ void SDL_QuitSubSystem(SDL_InitFlags flags) if (SDL_ShouldQuitSubsystem(SDL_INIT_VIDEO)) { SDL_QuitRender(); SDL_VideoQuit(); + SDL_VideoThreadID = 0; // video implies events SDL_QuitSubSystem(SDL_INIT_EVENTS); } @@ -619,6 +638,7 @@ void SDL_QuitSubSystem(SDL_InitFlags flags) if (flags & SDL_INIT_EVENTS) { if (SDL_ShouldQuitSubsystem(SDL_INIT_EVENTS)) { SDL_QuitEvents(); + SDL_EventsThreadID = 0; } SDL_DecrementSubsystemRefCount(SDL_INIT_EVENTS); } diff --git a/src/SDL_internal.h b/src/SDL_internal.h index 7f8439e159efb..13d431e10333b 100644 --- a/src/SDL_internal.h +++ b/src/SDL_internal.h @@ -265,6 +265,7 @@ extern "C" { #include "SDL_utils_c.h" #include "SDL_hashtable.h" + /* SDL_ExitProcess is not declared in any public header, although it is shared between some parts of SDL, because we don't want anything calling it without an extremely good reason. */ diff --git a/src/events/SDL_events.c b/src/events/SDL_events.c index 7551e4a6493b9..5560ecb74b216 100644 --- a/src/events/SDL_events.c +++ b/src/events/SDL_events.c @@ -1494,6 +1494,9 @@ void SDL_PumpEventMaintenance(void) // Run the system dependent event loops static void SDL_PumpEventsInternal(bool push_sentinel) { + // This should only be called on the main thread, check in debug builds + SDL_assert(SDL_IsMainThread()); + // Free any temporary memory from old events SDL_FreeTemporaryMemory(); diff --git a/src/main/SDL_main_callbacks.c b/src/main/SDL_main_callbacks.c index 6c4a67acd7b68..9bed41379ec5f 100644 --- a/src/main/SDL_main_callbacks.c +++ b/src/main/SDL_main_callbacks.c @@ -147,13 +147,19 @@ void SDL_QuitMainCallbacks(SDL_AppResult result) SDL_Quit(); } -void SDL_CheckDefaultArgcArgv(int *argc, char ***argv) +static void SDL_CheckDefaultArgcArgv(int *argc, char ***argv) { - if (!argv) - { + if (!*argv) { static char dummyargv0[] = { 'S', 'D', 'L', '_', 'a', 'p', 'p', '\0' }; static char *argvdummy[2] = { dummyargv0, NULL }; *argc = 1; *argv = argvdummy; } } + +int SDL_CallMainFunction(int argc, char *argv[], SDL_main_func mainFunction) +{ + SDL_CheckDefaultArgcArgv(&argc, &argv); + SDL_SetMainReady(); + return mainFunction(argc, argv); +} diff --git a/src/main/SDL_main_callbacks.h b/src/main/SDL_main_callbacks.h index 2408414909a9a..58962a849e133 100644 --- a/src/main/SDL_main_callbacks.h +++ b/src/main/SDL_main_callbacks.h @@ -27,10 +27,7 @@ SDL_AppResult SDL_InitMainCallbacks(int argc, char *argv[], SDL_AppInit_func app SDL_AppResult SDL_IterateMainCallbacks(bool pump_events); void SDL_QuitMainCallbacks(SDL_AppResult result); -// (not a callback thing, but convenient to stick this in here.) -// If *_argv is NULL, update *_argc and *_argv to point at a static array of { "SDL_app", NULL }. -void SDL_CheckDefaultArgcArgv(int *_argc, char ***_argv); +// Check args and call the main function +extern int SDL_CallMainFunction(int argc, char *argv[], SDL_main_func mainFunction); #endif // SDL_main_callbacks_h_ - - diff --git a/src/main/SDL_runapp.c b/src/main/SDL_runapp.c index 83ac590cb5efc..df0750529d6a8 100644 --- a/src/main/SDL_runapp.c +++ b/src/main/SDL_runapp.c @@ -28,8 +28,7 @@ int SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void * reserved) { (void)reserved; - SDL_CheckDefaultArgcArgv(&argc, &argv); - return mainFunction(argc, argv); + return SDL_CallMainFunction(argc, argv, mainFunction); } #endif diff --git a/src/main/emscripten/SDL_sysmain_runapp.c b/src/main/emscripten/SDL_sysmain_runapp.c index 04a8a91de46fc..74e4a0e62802b 100644 --- a/src/main/emscripten/SDL_sysmain_runapp.c +++ b/src/main/emscripten/SDL_sysmain_runapp.c @@ -37,8 +37,6 @@ int SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void * reserv { (void)reserved; - SDL_CheckDefaultArgcArgv(&argc, &argv); - // Move any URL params that start with "SDL_" over to environment // variables, so the hint system can pick them up, etc, much like a user // can set them from a shell prompt on a desktop machine. Ignore all @@ -59,7 +57,7 @@ int SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void * reserv } }, SDL_setenv_unsafe); - return mainFunction(argc, argv); + return SDL_CallMainFunction(argc, argv, mainFunction); } #endif diff --git a/src/main/gdk/SDL_sysmain_runapp.cpp b/src/main/gdk/SDL_sysmain_runapp.cpp index 3eeac97dfb6cb..5c8ca466c4c9a 100644 --- a/src/main/gdk/SDL_sysmain_runapp.cpp +++ b/src/main/gdk/SDL_sysmain_runapp.cpp @@ -24,6 +24,7 @@ extern "C" { #include "../../core/gdk/SDL_gdk.h" #include "../../core/windows/SDL_windows.h" #include "../../events/SDL_events_c.h" +#include "../SDL_main_callbacks.h" } #include #include @@ -65,14 +66,12 @@ int SDL_RunApp(int argc, char **argv, SDL_main_func mainFunction, void *reserved SDL_SetError("[GDK] Unable to get titleid. Will not call XblInitialize. Check MicrosoftGame.config!"); } - SDL_SetMainReady(); - if (!GDK_RegisterChangeNotifications()) { return -1; } // Run the application main() code - result = mainFunction(argc, argv); + result = SDL_CallMainFunction(argc, argv, mainFunction); GDK_UnregisterChangeNotifications(); diff --git a/src/main/n3ds/SDL_sysmain_runapp.c b/src/main/n3ds/SDL_sysmain_runapp.c index 4cb6adeb9560b..d6842b7d3b325 100644 --- a/src/main/n3ds/SDL_sysmain_runapp.c +++ b/src/main/n3ds/SDL_sysmain_runapp.c @@ -31,14 +31,11 @@ int SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void * reserv { int result; - SDL_CheckDefaultArgcArgv(&argc, &argv); - // init osSetSpeedupEnable(true); romfsInit(); - SDL_SetMainReady(); - result = mainFunction(argc, argv); + result = SDL_CallMainFunction(argc, argv, mainFunction); // quit romfsExit(); diff --git a/src/main/ps2/SDL_sysmain_runapp.c b/src/main/ps2/SDL_sysmain_runapp.c index e92bc702413ee..ce97ad0c0c2e6 100644 --- a/src/main/ps2/SDL_sysmain_runapp.c +++ b/src/main/ps2/SDL_sysmain_runapp.c @@ -68,21 +68,17 @@ static void deinit_drivers(void) int SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void * reserved) { - int res; + int result; (void)reserved; - SDL_CheckDefaultArgcArgv(&argc, &argv); - prepare_IOP(); init_drivers(); - SDL_SetMainReady(); - - res = mainFunction(argc, argv); + result = SDL_CallMainFunction(argc, argv, mainFunction); deinit_drivers(); - return res; + return result; } #endif // SDL_PLATFORM_PS2 diff --git a/src/main/psp/SDL_sysmain_runapp.c b/src/main/psp/SDL_sysmain_runapp.c index 3b4eb0f8b15d2..dabf7ac77fa4c 100644 --- a/src/main/psp/SDL_sysmain_runapp.c +++ b/src/main/psp/SDL_sysmain_runapp.c @@ -74,13 +74,9 @@ int SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void * reserv { (void)reserved; - SDL_CheckDefaultArgcArgv(&argc, &argv); - sdl_psp_setup_callbacks(); - SDL_SetMainReady(); - - return mainFunction(argc, argv); + return SDL_CallMainFunction(argc, argv, mainFunction); } #endif // SDL_PLATFORM_PSP diff --git a/src/main/windows/SDL_sysmain_runapp.c b/src/main/windows/SDL_sysmain_runapp.c index a53042049bc8a..39b3820c8f37c 100644 --- a/src/main/windows/SDL_sysmain_runapp.c +++ b/src/main/windows/SDL_sysmain_runapp.c @@ -23,6 +23,7 @@ #ifdef SDL_PLATFORM_WIN32 #include "../../core/windows/SDL_windows.h" +#include "../SDL_main_callbacks.h" /* Win32-specific SDL_RunApp(), which does most of the SDL_main work, based on SDL_windows_main.c, placed in the public domain by Sam Lantinga 4/13/98 */ @@ -37,8 +38,7 @@ int MINGW32_FORCEALIGN SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunc if (args_error) { SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal Error", args_error, NULL); } else { - SDL_SetMainReady(); - result = mainFunction(argc, argv); + result = SDL_CallMainFunction(argc, argv, mainFunction); if (heap_allocated) { HeapFree(GetProcessHeap(), 0, heap_allocated); } diff --git a/src/video/uikit/SDL_uikitappdelegate.m b/src/video/uikit/SDL_uikitappdelegate.m index 9b8b3bcbe5fdc..e2c304b9caea6 100644 --- a/src/video/uikit/SDL_uikitappdelegate.m +++ b/src/video/uikit/SDL_uikitappdelegate.m @@ -42,8 +42,6 @@ int SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void *reserved) { - SDL_CheckDefaultArgcArgv(&argc, &argv); - // store arguments forward_main = mainFunction; forward_argc = argc; @@ -483,7 +481,7 @@ - (void)postFinishLaunch [self performSelector:@selector(hideLaunchScreen) withObject:nil afterDelay:0.0]; SDL_SetiOSEventPump(true); - exit_status = forward_main(forward_argc, forward_argv); + exit_status = SDL_CallMainFunction(forward_argc, forward_argv, forward_main); SDL_SetiOSEventPump(false); if (launchWindow) { @@ -553,7 +551,7 @@ - (void)postFinishLaunch // run the user's application, passing argc and argv SDL_SetiOSEventPump(true); - exit_status = forward_main(forward_argc, forward_argv); + exit_status = SDL_CallMainFunction(forward_argc, forward_argv, forward_main); SDL_SetiOSEventPump(false); if (launchWindow) { From ed10abf9b70bc282af0eb6d4de924d58e10ec275 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Sun, 30 Nov 2025 10:23:48 -0800 Subject: [PATCH 2/4] Set SDL_MAIN_AVAILABLE for all platforms --- include/SDL3/SDL_main.h | 17 ++++++++++++----- src/main/SDL_runapp.c | 12 +++++++++--- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/include/SDL3/SDL_main.h b/include/SDL3/SDL_main.h index 009cd4ef1a13e..81a601df8f0f5 100644 --- a/include/SDL3/SDL_main.h +++ b/include/SDL3/SDL_main.h @@ -167,12 +167,10 @@ */ #define SDL_MAIN_NEEDED - #elif defined(SDL_PLATFORM_IOS) - /* On iOS SDL provides a main function that creates an application delegate - and starts the iOS application run loop. + #elif defined(SDL_PLATFORM_IOS) || defined(SDL_PLATFORM_TVOS) + /* On iOS and tvOS SDL provides a main function that creates an application delegate and starts the application run loop. - To use it, just #include SDL_main.h in the source file that contains your - main() function. + To use it, just #include in the source file that contains your main() function. See src/video/uikit/SDL_uikitappdelegate.m for more details. */ @@ -230,6 +228,15 @@ */ #define SDL_MAIN_AVAILABLE + #else + /* + This platform SDL provides a main function that sets up the main + thread and calls your main function. + + If you provide this yourself, you may define SDL_MAIN_HANDLED + */ + #define SDL_MAIN_AVAILABLE + #endif #endif /* SDL_MAIN_HANDLED */ diff --git a/src/main/SDL_runapp.c b/src/main/SDL_runapp.c index df0750529d6a8..09c6b003c116f 100644 --- a/src/main/SDL_runapp.c +++ b/src/main/SDL_runapp.c @@ -21,9 +21,15 @@ #include "SDL_internal.h" #include "SDL_main_callbacks.h" -/* Most platforms that use/need SDL_main have their own SDL_RunApp() implementation. - * If not, you can special case it here by appending || defined(__YOUR_PLATFORM__) */ -#if ( !defined(SDL_MAIN_NEEDED) && !defined(SDL_MAIN_AVAILABLE) ) || defined(SDL_PLATFORM_ANDROID) +// Add your platform here if you define a custom SDL_RunApp() implementation +#if !defined(SDL_PLATFORM_WIN32) && \ + !defined(SDL_PLATFORM_GDK) && \ + !defined(SDL_PLATFORM_IOS) && \ + !defined(SDL_PLATFORM_TVOS) && \ + !defined(SDL_PLATFORM_EMSCRIPTEN) && \ + !defined(SDL_PLATFORM_PSP) && \ + !defined(SDL_PLATFORM_PS2) && \ + !defined(SDL_PLATFORM_3DS) int SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void * reserved) { From cb0eeced01e1c2379954cf1e91e204413c34cbc8 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Sun, 30 Nov 2025 10:37:20 -0800 Subject: [PATCH 3/4] testthread: SDL performs thread initialization automatically --- test/testthread.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/test/testthread.c b/test/testthread.c index 70710eacbcfb0..ea56af95b9580 100644 --- a/test/testthread.c +++ b/test/testthread.c @@ -118,12 +118,6 @@ int main(int argc, char *argv[]) i += consumed; } - /* Load the SDL library */ - if (!SDL_Init(0)) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't initialize SDL: %s", SDL_GetError()); - return 1; - } - if (SDL_GetEnvironmentVariable(SDL_GetEnvironment(), "SDL_TESTS_QUICK") != NULL) { SDL_Log("Not running slower tests"); SDL_Quit(); From 40b49882171678c9c7a0576147e7b7d8addf47c6 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Sun, 30 Nov 2025 10:51:19 -0800 Subject: [PATCH 4/4] testthread: verify that child threads aren't SDL_IsMainThread() --- test/testthread.c | 37 +++++++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/test/testthread.c b/test/testthread.c index ea56af95b9580..48f39ee1c7cbe 100644 --- a/test/testthread.c +++ b/test/testthread.c @@ -54,8 +54,14 @@ getprioritystr(SDL_ThreadPriority priority) return "???"; } -static int SDLCALL -ThreadFunc(void *data) +static int SDLCALL CheckMainThread(void *data) +{ + bool *thread_is_main = (bool *)data; + *thread_is_main = SDL_IsMainThread(); + return 0; +} + +static int SDLCALL ThreadFunc(void *data) { SDL_ThreadPriority prio = SDL_THREAD_PRIORITY_NORMAL; @@ -91,6 +97,7 @@ killed(int sig) int main(int argc, char *argv[]) { int i; + bool child_is_main = true; /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, 0); @@ -112,15 +119,33 @@ int main(int argc, char *argv[]) if (consumed <= 0) { static const char *options[] = { "[--prio]", NULL }; SDLTest_CommonLogUsage(state, argv[0], options); - exit(1); + quit(1); } i += consumed; } + /* Check main thread */ + if (!SDL_IsMainThread()) { + SDL_Log("SDL_IsMainThread() returned false for the main thread"); + quit(1); + } + + thread = SDL_CreateThread(CheckMainThread, "CheckMainThread", &child_is_main); + if (!thread) { + SDL_Log("Couldn't create thread: %s", SDL_GetError()); + quit(1); + } + SDL_WaitThread(thread, NULL); + + if (child_is_main) { + SDL_Log("SDL_IsMainThread() returned true for a child thread"); + quit(1); + } + if (SDL_GetEnvironmentVariable(SDL_GetEnvironment(), "SDL_TESTS_QUICK") != NULL) { SDL_Log("Not running slower tests"); - SDL_Quit(); + quit(0); return 0; } @@ -130,7 +155,7 @@ int main(int argc, char *argv[]) SDL_SetAtomicInt(&alive, 1); thread = SDL_CreateThread(ThreadFunc, "One", "#1"); if (!thread) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create thread: %s", SDL_GetError()); + SDL_Log("Couldn't create thread: %s", SDL_GetError()); quit(1); } SDL_Delay(5 * 1000); @@ -144,7 +169,7 @@ int main(int argc, char *argv[]) (void)signal(SIGTERM, killed); thread = SDL_CreateThread(ThreadFunc, "Two", "#2"); if (!thread) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create thread: %s", SDL_GetError()); + SDL_Log("Couldn't create thread: %s", SDL_GetError()); quit(1); } (void)raise(SIGTERM);