Hi, I’m having trouble finding any resources / tutorials on porting SDL to a new platform.
Specifically I’m interested in information on porting the SDL system to the BIOS environment (running it as an EFI application).
If anyone knows any tutorials, forum threads, etc… I would be very grateful, as I’m having trouble turning anything up myself.
Having a resource like this would be both great and probably occasionally obsolete, but I don’t think one exists at the moment.
But having done this a few times now (and having a pretty straightforward revision history from the Nintendo Switch work to look at) here’s a really short primer:
- Plan to write in C, unless your platform demands something else (macOS and iOS required Objective-C, Haiku’s system API demands C++, etc). SDL is a C library, so keep it simple if you can.
- Add your target platform to include/SDL_platform.h
- You’ll probably want to have src/dynapi/SDL_dynapi.h disable the Dynamic API for now (or forever, in the case of EFI or an embedded platform) with a
#define SDL_DYNAMIC_API 0 line for your platform.
- Write an include/SDL_config_$MYPLATFORM.h file. Make sure include/SDL_config.h includes it correctly. You’ll want to copy something like SDL_config_minimal.h to start, but you should look at one of the more complicated ones (SDL_config_iphoneos.h or whatnot) as you add functionality, because this is what enables entire subsystems.
- Write a project file for your environment. If the existing CMake or configure script works for you, or just needs some minor additions, that’s probably easy enough, as it’ll definitely catch most Unix-like platforms, if nothing else. Other platforms might have their own build systems (like Xcode for iOS or whatever Android does now). Get the core pieces in there (The .c files in src, but none of the platform specific ones. So src/video/*.c, but not src/video/x11/*.c, etc).
- You’ll want to start adding in subsystem implementations now. If you can use something that exists (does your platform have an X server? Add in src/video/x11’s sources. Does it use pthreads? src/threads/pthreads, etc). Often time, you can get some existing pieces working with your platform, even if you have to write a new video subsystem implementation. When getting started, most of the subsystems have a “dummy” implementation, which doesn’t do anything, but this can get you bootstrapped until you can write your own. So src/audio/dummy/*.c will get you a “working” audio subsystem that doesn’t ever report any available audio hardware while you are busy trying to get video working. If your platform doesn’t ever have audio support, then adding the dummy backend is the entirety of that work.
- As you add backends (or use the dummy backend), make sure it’s enabled in your SDL_config_*.h file. Most subsystems let you have multiple backends, so you can keep the dummy backend (and we ship SDL like this, so users have an in-case-of-fire emergency switch to pull if they want to prevent SDL from touching some piece of the platform for whatever reason). It’s not unusual for a desktop platform to have dummy audio, “disk” audio (write audio output to a file), and three different ways to talk to the hardware as OSes added and deprecated APIs over the years. You might not need this flexibility, but it’s there if you want/need it.
- Adding a backend varies by subsystem, but generally you have a well-defined set of functions you implement, a struct that has function pointers that you fill in with those functions, and then a little #ifdef spaghetti at the upper level to tell SDL where to find that struct. Grep for “VideoBootStrap” in the src/video directory for an example of this. It’ll need you to set up the proper #define in your SDL_config header, add your sources to the build, and plug in the struct in src/video/SDL_video.c and src/video/SDL_sysvideo.h. Copy one of the existing implementations and use it as a starting point. Most of the subsystems work like this. Others, like src/thread or src/loadso, assume there’s only one implementation for the platform, so you’ll just implement a standard function that SDL calls directly and then you’re done. src/thread/pthread (etc) work like this. Just copy one of the existing implementations and rework it for your platform.
- Stuff that’s specific to your platform that is needed by multiple subsystems (like Linux D-Bus support) or new SDL APIs that are only for your platform (SDL_IsAndroidTablet() or whatever) can go in the src/core directory. Despite the name, it’s possible your platform won’t need any “core” code.
- Add your platform to SDL_GetPlatform() in src/SDL.c
- You might need to add platform-specific #ifdefs to src/SDL_assert.c, especially if you don’t have message boxes working. As this is theoretically for debugging purposes only, you can possibly skip this entirely, especially at the start of the work.
- You might want to add your platform to include/SDL_syswm.h so apps can get at the platform-level details you built on top of. It might not be important for an embedded system, and can be entirely ignored.
- Some platforms don’t use a standard Unix/ANSI C main function (the classic
int main(int argc, char *argv) function). SDL offers a static library in src/main that just offers whatever a platform wants that calls SDL_main(). So, for example, on Windows, it implements WinMain() so SDL apps don’t need to. This is optional, even if your platform doesn’t use main(), but appreciated if not. Try not to do any platform init in here if possible. Do that under SDL_InitSubSystem() if you can, so apps that use SDL but aren’t really SDL apps aren’t married to you. The best example is a scripting language that otherwise doesn’t care about SDL but might want to call into an SDL module. Don’t make them rely on SDLmain!
- SDL, internally, doesn’t use the C runtime at all. We have reimplementations of everything we need, including malloc(), so it can work on platforms that don’t have a robust C runtime (but on platforms that do, like Linux, our “reimplementation” just passes through to the standard C runtime). If you’re really screwed–as you might be with EFI?–you might have to go into src/stdlib and implement something for your platform. For example, our malloc implementation is actually the open-source dlmalloc, which will eventually need system calls to request memory pages (VirtualAlloc() on win32, mmap() elsewhere)…you might need to add something for your platform if it’s really exotic. If your platform reliably provides a quality C runtime, just use that and avoid all this. Many platforms do. And, when you need something like strcmp(), you should call SDL_strcmp() instead. This is a huge pile of #defines in your SDL_config_*.h file to enable pieces of the C runtime function by function, so you can only turn on the pieces you need (as sometimes we only need a little help instead of a complete replacement of the C runtime). You’ll want to copy this out of another SDL_config header. The configure and CMake scripts set up these defines by testing every single function’s availability, which is a strong argument for using configure or CMake if you can.
- SDL doesn’t need threads to work (as it works on Emscripten, for example), but the audio subsystem wants to spin an internal thread by default. You’ll need to deal with that in your audio backend if you can’t do threads (MacOS Classic used a hardware interrupt back in the day, Emscripten sets up a callback function that the browser calls at a regular interval, etc).
- A text file in Markdown format in docs/README-myplatform.md is appreciated but not required. But it’s super-nice to get a quick start for building/running your code and what its features and limitations are.
- Once you have some of SDL building, the “test” directory has small programs that test specific parts of the library. test/testwm.c is good once you can get a basic window on the screen. test/testsprite2.c is a good test of the render subsystem. test/loopwave.c will play a .wav file to an audio device. There’s also testjoystick, testgamecontroller, etc. These are often the fastest route to incrementally testing what you build.
- Send patches to us (through bugzilla) if appropriate. We don’t promise to include them in the upstream sources for really obscure platforms, as it becomes our maintenance burden, but we might! Needless to say, if you want to contribute this, we really appreciate if you match the rest of SDL’s coding style. We don’t have a formal document on this, but it’s pretty obvious (4 spaces, not tabs; starting brace on the same line; braces even if there’s only a single statement; /* comments */, not // comments; etc), and we’re willing to work with you on this, but meet us halfway, please.
(This was way longer than I expected; I should put this in the wiki…)
My understanding is that EFI doesn’t have this problem, but for future people that land here by googling for “SDL BIOS”: SDL assumes you have a linear address space that’s 32-bits or wider…so avoid attempts to port to 16-bit segmented memory models.
Wow, I didn’t expect such a detailed response. I’m going to start a new job soon, and this information will be invaluable to me over the next few months. I’ll post updates on my progress here I suppose if I move ahead with the project.
Thank you very much!