release
dev builds
extras
themes manual
wiki
device status forums
mailing lists
IRC bugs
patches
dev guide



Search | Go
Wiki > Main > SdlPort

SDL Plugin Port

Rockbox has a port of the Simple DirectMedia? Library (version 1.2.15, commonly known as SDL) as a static library which various programs can be compiled against. Having SDL support allows us to accelerate the process of porting other software.

The port resides in the apps/plugins/sdl/ directory and was largely done by me (FranklinWei). The purpose of this page is to document the history and technical details of the port, mostly for my own benefit, but hopefully these notes will be useful to others as well. If you're looking to port a program which uses SDL, see below for a guide.

The SDL runtime is currently used by PluginDuke3D, PluginQuake, and PluginWolf3D?. Ports of other programs have also been attempted with varying degrees of success. They are still available in old Gerrit patch sets if anyone is interested.

History

I'm writing this about 2 years after the fact, so much of the following is reconstructed from commit history and IRC. From what I can gather, the idea for the port came around 21 January 2017.

2017-01-21:
<__builtin> hmm, it seems like the easiest way to approach this port [of Prince of Persia] is by writing a SDL wrapper
<__builtin> or maybe I could just port SDL, who knows? :D

... about 1 hour passes ...

<__builtin> woot! SDL compiles!!! (-ish)
<__builtin> though I don't have any drivers that actually do anything :P

...

<__builtin> it actually fits in the plugin buffer! :D

2017-01-22:
<pamaury>__builtin: are you *sure* you want to port SDL to rockbox ?
<__builtin> pamaury: too late now :)

It seems that I began porting Wolfenstein 3-D in early September 2017, and I had gotten Duke Nukem working by the middle of that same month. Quake was first pushed to Gerrit on 25 September 2017.

Duke3D was merged in December 2017, and Wolf3D? and Quake sat in Gerrit for two years until I finally merged them both in July 2019.


Technical overview

Compilation

Programs reside in their own directory under apps/plugins/sdl/progs/. The master branch currently has ports of duke3d, quake, and wolf3d in correspondingly named directories. Every program must have its main function renamed to my_main(int argc, char* argv[]).

The compilation of each program is governed by sdl.make (see below for details on adding a new program). On native hosts, programs are compiled to overlays by default, which are loaded by a stub loader, such as apps/plugins/duke3d.c. This stub loader does nothing but load the actual program into the audio buffer (hence "overlay") and run it from there.

Compilation is done statically against the SDL sources. That is, each plugin has its own binary copy of SDL in its object file. This is inefficient because the SDL code is duplicated, but this has negligible impact (only few KB of wasted disk space) and simplifies the linking process greatly.

In order to prevent conflicts with system SDL libraries (in simulator builds, particularly), the sdl.make has a build rule which inserts a rb_ prefix to every symbol in the ported SDL. This is done with objcopy --redefine-syms and the file redefines.txt. This is transparent to the source code, but might cause confusion when debugging.

Control flow

Execution begins in main.c:plugin_start(), which spawns a new Rockbox thread in which your program's my_main() is run (this allows allocating a larger stack than default).

There are several rockbox-specific drivers in SDL, located in apps/plugins/sdl/src/{audio,thread,timer,video}/. The video driver is likely of the most interest to a game port, as it in fact controls button input as well.

Video driver

The video driver is located in src/video/rockbox. The video driver handles not only video, but also input (probably not good design, but pfft... whatever). The driver additionally handles rotation of the screen and keymap.

The driver accepts any size video mode, as long as the pixel format is correct (see code for this). The RGB565SWAPPED pixel format is also supported, but it relies on a final swapping step done in the update routine which adds some overhead, as I don't believe non-contiguous color channels are supported by SDL.

There are two special cases for the video mode, which offer performance benefits:

1. requested_h is LCD_HEIGHT && requested_w is LCD_WIDTH: In this case, writes are done directly to the LCD framebuffer and is called direct mode. This is very fast, and is likely what you want. To trigger this, make sure the program you are porting requests a screen size of LCD_WIDTHxLCD_HEIGHT when calling SDL_SetVideoMode.

2. requested_h is LCD_WIDTH && requested_w is LCD_HEIGHT: The driver detects this case and automatically rotates both the keymap and framebuffer by 90 degrees. This mode requires a copy, so it is not as efficient as direct mode. This is useful for rotating the screen in games, a la PluginPacbox.

All other screen resolutions will be scaled to LCD_WIDTHxLCD_HEIGHT transparently using simple_resize_bitmap, which incurs a performance penalty.

Audio driver

Our audio driver supports only 16-bit signed integer PCM audio, with stereo interleave (even indexes: left channel, odd: right). Any hardware-supported sample rate is supported by the audio driver. To simplify things for games, there is a global RB_SAMPR define which defaults to a reasonable frequency which should be supported (currently 22.1 KHz).

You should take care that your program does not request an unsupported sample rate, as doing so will force SDL to interpolate your samples to RB_SAMPR in realtime.

SDL's audio code runs in a separate, long-running thread which is created through the thread driver (see below).

The audio driver works as follows:

1. Driver (our code) tells SDL to place samples in an internally allocated mixing buffer (ROCKBOXAUD_GetAudioBuf()).

2. SDL audio thread calls the program's callback to get samples.

3. SDL calls our PlayAudio function to tell the driver it wants the samples in the mixing buffer to be played.

4. Our driver has a circular list of buffers (currently 4), including the currently playing buffer, in order to reduce skipping at the cost of latency. PlayAudio finds the next free buffer and copies the samples from the mixing buffer into it. (We could probably optimize this by telling SDL to directly place samples into the next free buffer, but it's complicated).

5. Finally, a Rockbox IRQ calls our get_more to request samples. get_more points Rockbox to the next buffer in line, which is then played.

This design avoids copying in the IRQ context. There is a potential for optimization by reducing copying between the mixing buffer and the circular queue.

Thread driver

The SDL audio driver needs to run in a separate thread. We provide a bare-bones thread driver in apps/plugins/sdl/src/thread/rockbox. This driver defaults to creating a thread with a priority of PRIORITY_BUFFERING, which is exactly what the SDL audio driver does.

We do not support killing threads.


Porting new programs

Prerequisites

When deciding to port a program, there are a couple things you should make sure of first:

  • The program uses SDL 1.2 (as opposed to the more modern SDL 2.0, which we do not support).

  • You have the source code of the program and it is written in C, or you are willing to rewrite it in C (this is not impossible if the original source is C++).

  • The source code is licensed under a GPLv2+-compatible license. If not, it may still be possible to port, but it will not be merged.

  • You are able to compile and run the source code on a modern Linux machine. If not, you will find debugging to be a very painful experience.

  • The program does not rely on any hardware absent from our targets (i.e. anything that has to do with hardware rendering, etc.). This basically rules out any 3D game without a software renderer (unless you're looking to also write an OpenGL? driver, of course).

  • The program runs without requiring too much memory. This is very hard to define exactly, but the currently ported programs use about ~10-20MB of heap space when running.

Once you have verified these facts, you can begin the port process. Before you begin, note that ports of complex programs to run on new platforms are non-trivial: from experience, they can take anywhere from half an afternoon to several months to perfect.

Porting process

The general outline of the porting process is given below. Note that this guide may not be totally accurate or complete (ask __builtin in IRC if you attempt this).

1. Copy your program's sources into its own directory under apps/plugins/sdl/progs/. You can leave out any irrelevant files.

2. Create apps/plugins/sdl/SOURCES., modeled after SOURCES.duke. This file should have all your program source files listed.

3. Edit sdl.make by duplicating all lines referring to "DUKE3D" and editing them to refer to your program. You will need duplicate build rules as well. Make sure you are thorough and do not miss anything.

4. Rename your program's main() to be my_main(int argc, char *argv[])

5. Copy the duke3d stub loader apps/plugins/duke3d.c to apps/plugins/MYPROGRAM.c, and edit the path to point to your program.

6. Add your program to apps/plugins/CATEGORIES.

7. Compile and fix errors (see below). This is the most involved step by far.

8. Test/debug on simulator (optional).

Common issues when porting

Unaligned accesses

ARM cannot do unaligned loads correctly. Reading an unaligned value will give undefined behavior. SDL currently builds with -Wcast-align to catch these errors -- fix them.

Missing functions

The SDL library includes a set of POSIX-like wrapper functions which tries to make programs happy. They are not complete.

The usual procedure is to #define symbols to what you would like them to be, in include/SDL_config_rockbox.h.

Simulator builds

The UI sim does not play well with the default settings. It is still possible to run Duke (and other SDL programs) under the UI simulator, by enabling it in SUBDIRS, using SDL threads (pass --sdl-threads to tools/configure), and compiling for 32-bit (add -m32 to CFLAGS and LDOPTS in tools/configure). This solves the Thread creation failed. Retryingmake_context(): Cannot allocate memory issue when running in the sim. (The need for 32-bit is only due to Duke3D?, not a limitation of the SDL library itself.)

-- FranklinWei - 15 Jun 2019

The port resides in the apps/plugins/sdl/ directory and was largely
r5 - 22 Jul 2019 - 22:19:59 - FranklinWei

Copyright by the contributing authors.