Clover’s Toy Box development: improving SDL support and adding SDL 3.
Overview
SDL (Simple Directmedia Layer) is a library used by Clover’s Toy Box for creating the application window with an OpenGL context, playing audio, and receiving input from the keyboard, mouse, and game controllers. An OpenGL (Open Graphics Library) context is used for 3D graphics rendering.
Toy Box is now compatible with all SDL versions with OpenGL support. This allows Toy Box to use SDL 3 for the best modern operating system compatibility and new game controller support while I still continue to tinker with platforms or operating system versions that are only supported by SDL 2 and now potentially SDL 1.2 as well.
However this doesn’t make Toy Box effortlessly run on everything that SDL has been ported to. There is platform specific code, it requires figuring out building for the platform, and possibly requires implementing 3D rendering.
I use the HIDAPI library for implementing my own support for the Official Nintendo USB GameCube Controller Adapter. I had to jump through some hoops to support the HIDAPI library embedded in SDL 2, SDL 3, or (for SDL 1 or SDL 2 before 2.0.18) a standalone HIDAPI library.
(SDL 2.0.12+ and SDL 3 also supports this device but I prefer my own preexisting support that is essentially identical to my support for GameCube controllers on a real Wii or GameCube using libogc.)
All-in-One
Toy Box now supports every stable version of the SDL API since support was added for OpenGL (SDL 1.1.0+, 1.2.0+, 2.0.0+, 3.1.3+).
Toy Box also supports all of those in a single build of the application. There isn’t separate source code control branches to maintain or separate builds that need to be verified that they can compile. (Though I do retain the ability to use traditional linking to a specific SDL version.)
Unfortunately macOS and Android need separate application builds for SDL 2 and 3 to compile against different SDKs to allow targeting older operating system versions supported by SDL 2.
Additional SDL versions
Toy Box previously only supported SDL 2 and required pretty much whatever SDL 2.x.x version it was compiled against due to making use of new features. There was many compile-time version checks but it didn’t cover everything. Now Toy Box handles all SDL 2.x.x versions by using ~70 compile-time and run-time version checks.
SDL 3 is not the most exciting upgrade for the needs of a resizable OpenGL window with a game controller, mouse, and keyboard. SDL 3 is necessary to keep up with the latest development and platform compatibility as not everything is backported to SDL 2.
New support for SDL 3 adds new keyboard keys, game controller devices and buttons, and exposes game controller battery power as a percentage instead of low, medium, full.
Currently there is only one SDL 3 version check. I only use functionality of the first stable ABI 3.1.3 preview release.
New support for SDL 1 opens the door to supporting Windows 9x/ME/2000 and older versions of Mac OS X but I haven’t looked into those yet.
Building and running SDL 1.1.0 from the year 2000 on modern Linux was kind of amazing. It works! Though it did unfortunately crash in Xorg at window shutdown (in GNOME Wayland session).
Toy Box also uses version checks to support all SDL 1.1.x and 1.2.x versions; a whole 5 version checks for the utilized functionality.
I already had a window/audio/input abstraction layer over SDL 2 and libogc. The only required extension for adding SDL 1 and 3 was exposing the parts of HIDAPI that are used for GameCube controller adapter support.
Dynamically Load SDL
Manually dynamically loading SDL with dlopen() or LoadLibrary() is required for supporting SDL 1, 2, and 3 in the same executable. However I began considering this with only SDL 2 in Spearmint. It solves issues I encountered on Windows, macOS, and Linux.
Toy Box chooses the newest SDL library; on Linux it uses either the library installed at the system level or included with the application. If SDL 3 is missing or fails to load, Toy Box will fall back to SDL 2 and then SDL 1. It’s also possible to specify which SDL version to use.
Dynamically loading SDL avoids needing to create a custom SDL build for Windows to rename the SDL.dll for 64-bit (SDL64.dll) to not conflict with the 32-bit SDL.dll.
Dynamically loading SDL and supporting any SDL version at run-time means the application can compile against the same latest SDL headers that are included in the source tree regardless of the actual library version. This avoids the issue of needing separate SDL headers for each macOS architecture’s final SDL version like in Spearmint with four sets of SDL 2 header files.
Dynamically loading SDL avoids the problem of “rpath $ORIGIN” not being supported on older Linux distributions which breaks finding libraries included with the application.
Supporting system libraries and libraries included with the application fixes the problem of Spearmint 1.0.0 bundling SDL 2.0.8 but seven years later users would be better off using the newer system library on Linux. (Technically Windows and macOS would also benefit from newer SDL and I should probably update SDL in the application more frequently.)
Supporting fallback to older SDL system libraries also means Toy Box (without bundling SDL) would not need to statically link to SDL 3 or wait for Linux distributions to provide it. It would simply continue using SDL 2.
OpenGL support
SDL 1.2
I added SDL 1.2 support and I got a black screen. SDL 1.2 didn’t enable double buffering by default. I needed to manually call glFlush() to tell the OpenGL driver to do the rendering.
I enabled double buffering for SDL 1.2 (using SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 )). I don’t expect to actually need single-buffered rendering but I added support anyway. Let’s avoid black screen problems if we can.
SDL 3
I added SDL 3 support and I got a black screen. Swapping buffers for double-buffered rendering was failing. I made my OpenGL renderer automatically switch to single-buffered rendering in this situation. This fixed rendering. (This isn’t supported by OpenGL ES or WebGL.)
For some reason SDL 3 was requiring a call to SDL_GL_MakeCurrent() before calling SDL_GL_SwapWindow(). Calling it after creating the window wasn’t good enough but it worked directly before swap buffers. It didn’t make sense why I would need to do that.
I started thinking I should make a small test case for reporting it. Though it occurred to me I was on a random development revision of SDL 3 (May 5, 2025; git commit 29d211649). Using the latest SDL 3 code solved the issue. It also worked on the initial stable version 3.2.0 and (at time of writing) newest stable 3.2.24. But sure, now my renderer handles swapping buffers failing in case it breaks. Let’s avoid black screen problems if we can.
Swap Buffers
This is more or less what I do now; it use to just call swap window before SDL 1.2 and SDL 3 randomly broke rendering. (The fallback for swapping buffers failing is only implemented for SDL 3. Prior versions do not indicate if swapping buffers fails.)
// check if double buffer is enabled at initialization
int doubleBuffer = true;
if ( Desktop_OpenGL ) {
glGetIntegerv( GL_DOUBLEBUFFER, &doubleBuffer );
}
// ... at end of frame swap buffers or flush OpenGL commands
if ( doubleBuffer ) {
if ( !SDL_GL_SwapWindow( window ) ) {
if ( Desktop_OpenGL ) {
printf( "WARNING: Swap buffers failed. Disabling double-buffered rendering.\n" );
doubleBuffer = false;
glDrawBuffer( GL_FRONT );
glFlush();
} else {
// This should probably throw a fatal error on OpenGL ES and WebGL.
}
}
} else {
glFlush();
}
SDL doesn’t solve everything
There is a port of some version of SDL to run on most computer devices. I think supporting all versions of SDL in Clover’s Toy Box is exciting! Unfortunately this doesn’t instantly port my software to everything.
To port to a new platform, I would need to adapt the platform specific code in Toy Box for file access, time, networking, window/audio/input support, potentially loading dynamic libraries, and potentially support a new 3D graphics API or alter the OpenGL support. I would also need to install the build environment and set up the the correct build commands. I would need to figure out how test it and there is a decent chance of running into unique issues.
Most of this would need to be done even if not using SDL. My SDL usage only handles window/audio/input support. This is a small part of the porting process for older limited platforms. My Wii port without SDL handles window/audio/input in ~2,000 lines with an additional ~1,500 for the 3D graphics backend and ~50 lines for networking.
No version of SDL has a 3D graphics abstraction that would support the Wii or other older (pre-Vulkan) hardware. The SDL 1.2 Wii port doesn’t scale the joystick axis to the full range expected by applications. So if I used SDL, I would still be on the hook to do most of the porting work and have to step into maintaining the SDL Wii port or try to workaround the issues. It’s simpler to only deal with my own code base.
SDL 1.2 is also under a different license (LGPL license) than SDL 2+ (zlib license). It requires ability for the user to replace the LGPL code. For closed source software such as Toy Box, SDL 1.2 is difficult or impossible to use outside typical desktop platforms like Windows, Linux, and macOS. However there isn’t a strong reason to port SDL 2 or 3 to older platforms as they don’t offer very much of the new SDL functionality.
SDL offers a lot of functionality on desktop platforms. SDL is particularly useful for supporting things that are outside my setup and testing environment. I think using SDL gives better compatibility on Linux than I could do otherwise as I don’t plan to test every X11 window manager and Wayland compositor. SDL also contains many game controller mappings (so it works without user configuration) and contains game controller drivers that provide support for more hardware than the native platform APIs.
This is to say, SDL is one tool available for adding support for a platform. It may be the best tool for the job depending on your goals. However you still have to port the application yourself to each platform and it may be simpler and/or better to just directly support a platform. So support for all SDL versions when most devices have a port of SDL is not as exciting as it sounds.
Conclusion
Clover’s Toy Box overcomes several issues with Spearmint’s platform support and transition to new SDL versions.
These changes were developed in October through December 2025 but posting about it was delayed as I had not added support for SDL 3 on Android or tested macOS yet. I still haven’t done those as of September 2026.