Simple DirectMedia Layer 2.0

Newcomer-Friendly

© Lead Image © tomwang, 123RF.com

© Lead Image © tomwang, 123RF.com

Author(s):

After several years of development work, version 2.0 of the SDL library was released in August 2013. Despite its many innovations, migrating to and getting started with SDL 2.0 is amazingly easy.

Since 1998, Simple DirectMedia Layer (SDL) has formed the basis for countless games, multimedia applications, and even virtualization. The small cross-platform library makes it easy for applications to access the graphics card, the audio hardware, the keyboard, and other input devices. Because it is available for many different platforms and operating systems, you can easily port any programs that have been developed with it (see the "Supported Operating Systems" box). Without SDL, Linux users would probably still be waiting for conversions of blockbuster games like Portal (Figures  1 and 2).

Figure 1: Almost all games offered via Steam, including blockbusters like the puzzle game Portal, are based on SDL.
Figure 2: Even serious applications, such as the virtualization software Qemu and VirtualBox, use SDL for their screen output.

Supported Operating Systems

Officially, SDL 2.0.0 supported the following operating systems when this issue went to press:

  • Linux as of kernel 2.6
  • Android as of version 2.3.3
  • Windows XP, Vista, 7, and 8
  • Mac OS X as of version 10.5
  • iOS as of version 5.1.1

Although SDL 2.0 will probably compile and support use on NetBSD, FreeBSD, OpenBSD, and Solaris, the developers provide no guarantees. Unofficial ports to Haiku, PlayStation Portable, and Pandora also exist.

The list of supported systems is significantly shorter than that of SDL 1.2. Because of a lack of maintainers, the team was forced in particular to remove ports for older operating systems, such as OS/2 and BeOS.

For an amazing 12 years, SDL version 1.2 provided the underpinnings of numerous programs. Changes have only come in small doses, with the last revision (1.2.15) appearing in January 2012. SDL 1.2 is thus considered extremely robust, but hardware development has mercilessly left it behind. In the background, SDL developers have already been working for several years in parallel on a revamped, modern variant.

The developer versions initially had a version number of 1.3, but – because of the many internal changes – the developers made the leap to 2.0 in February 2012. Although this new branch had already been used in many software projects, the developers were reluctant to put a "Stable" stamp on it. In August 2013, however, SDL inventor and project manager Sam Lantinga drew the line and officially released the current state of development as version 2.0.0.

2.0 Picks Up Speed

The new version finally can open multiple windows at the same time, detect multiple connected screens, and control multiple audio devices simultaneously. The SDL now uses state-of-the-art 3D graphics hardware acceleration for 2D graphic output. Under Linux, image data finds its way to the screen via the OpenGL interface. In an emergency, or on request, the lean library can also compute the graphics itself with an internal software-based renderer.

SDL still only offers drawing functions for 2D graphics. Programmers can create 3D graphics with OpenGL as of version 3.0 – or OpenGL ES on mobile devices. SDL then handles all other tasks, such as the audio output or querying the keyboard. Under Windows, Mac OS X, and Linux, the new version of SDL can finally deal with force-feedback devices; it also supports input devices with a touch interface.

Improved power management aims to ensure longer battery life on mobile devices. Additionally, numerous small improvements, such as Unicode support, have been implemented. Existing SDL programs can be ported easily to SDL 2.0, but because of the many changes, a recompile is not enough.

A licensing change also has been made: The LGPL license of the legacy SDL 1.2 made it difficult to use in commercial projects. Lead developer Lantinga even founded a company from which developers could acquire a license for commercial programs in the meantime. The new version 2.0, however, is released under the more liberal zlib license, which permits unrestricted commercial use of the library.

Getting Started

SDL is written in C and provides only a C interface. It can be used directly from C++ programs, and bindings exist for Python, C#, and Pascal [2]. Because SDL 2 is not yet included by any distro, you will need to build and install it yourself. To use the full feature set, you obviously require a C compiler and make, but also the developer packages for X11, OpenGL, ALSA, PulseAudio, libudev, pthreads, D-Bus, and  – depending on your needs  – ESD (the Enlightened Sound Daemon), the aRts audio server, and the libts library for controlling touch screens.

The usual group of three commands completes the installation: ./configure; make; make install. Be sure to check the output from configure for unfulfilled dependencies; otherwise, you may find that required features, such as support for ESD, are missing later on. After installing, run ldconfig once as root. Incidentally, SDL 2 can be installed and run parallel to the legacy SDL 1.2: The library itself is called libSDL2.so, and the header files end up in the SDL2 subdirectory.

Humble Steps

Because SDL 2.0 has a clear and simple API, the first steps are easy. If you have previously worked with the legacy version 1.2, you will also find many things familiar. In your own program, you first include SDL as a single header file:

#include "SDL2/SDL.h"

Before you can take advantage of SDL's features, you must initialize its subsystems. The following line handles this:

SDL_Init(SDL_INIT_EVERYTHING);

The parameter that SDL_Init() expects is a flag specifying which subsystem the function should initialize. The SDL_INIT_EVERYTHING flag enables all available subsystems. If you only want graphics output, you can use SDL_INIT_VIDEO as an alternative.

Developers can link multiple flags with a logical OR to initialize multiple selected subsystems. The following statement enables the video and audio subsystems:

SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);

If the call was successful, SDL_Init() returns a 0. If an error has occurred, however, you see a negative value. The corresponding error message is provided by the SDL_GetError() function. Most other SDL functions also use this form of error handling. Thus, to prevent your code from running into a wall, you should always evaluate the return value:

if (SDL_Init(SDL_INIT_EVERYTHING) != 0){
  printf("error: %s \n", SDL_GetError());
  return 1;
}

Although this part is familiar to SDL 1.2 users, unexplored terrain now follows: SDL 2 has ditched the good old SDL_SetVideoMode().

If the initialization was successful, the user can open a window. The function responsible for this, SDL_CreateWindow(), requires a name for the title bar, the position of the upper-left corner of the window on the screen, and the size of the window in pixels as parameters:

SDL_Window *window = SDL_CreateWindow(↩
  "Hello World!",250, 300, 640, 480, SDL_WINDOW_SHOWN);

The window is titled Hello World! At 640x480 pixels, it appears on the desktop 250 pixels from the left and 300 pixels from the top of the screen. The SDL_WINDOW_SHOWN flag ensures that the window is visible onscreen, whereas SDL_WINDOW_HIDDEN would initially hide it.

An alternative would be SDL_WINDOW_FULLSCREEN, which displays the contents of the window in full-screen mode. You can link other flags with an OR; for example, SDL_WINDOW_BORDERLESS removes the window frame, and SDL_WINDOW_MAXIMIZED maximizes the window.

The SDL_CreateWindow() function returns a pointer to an SDL_Window object. You can use the pointer to address the window. If an error has occurred, then the pointer is NULL. The reason is again revealed by SDL_GetError().

Great Artist

Next, the developer needs a renderer, that is, the tool that draws the window. How you do this is up to you. You could use DirectX in Windows, whereas you would normally use OpenGL on Linux. A renderer is generated by the SDL_CreateRenderer() function:

SDL_Renderer *renderer = SDL_CreateRenderer(window, -1,↩
  SDL_RENDERER_ACCELERATED |SDL_RENDERER_PRESENTVSYNC);

As its first parameter, this command expects a pointer to the window into which the renderer will later dump all the output. The -1 flag tells SDL to select a renderer with specific properties. The following flags specify what they are. In the example, I want the renderer to use the graphics card's hardware acceleration (SDL_RENDERER_ACCELERATED) and synchronize the screen buildup with the screen frequency (SDL_RENDERER_PRESENTVSYNC). SDL_CreateRenderer() finally outputs a pointer to an appropriate renderer. If this pointer is NULL, an error occurred, and the reason is displayed by SDL_GetError().

You now can tell the renderer to empty the contents of the window:

SDL_SetRenderDrawColor(renderer, 235, 235, 235, 255);
SDL_RenderClear(renderer);

The first function sets drawing color to a light gray with an RGB value of 235, 235, 235. The last parameter of SDL_SetRenderDrawColor() sets the transparency (more precisely, the value of the alpha channel). In this case, I want the renderer to make light gray opaque. SDL_RenderClear() then paints the window with the selected color.

Invitation

To display a picture in the window, you first need to load the picture. SDL itself only offers the SDL_LoadBMP() function, which loads an uncompressed BMP format image into memory. You only need to pass a file name to the function:

SDL_Surface *fig = SDL_LoadBMP("logo.bmp");

As in the legacy SDL 1.2, the result is a pointer to a surface. This data structure simply encapsulates the bitmap in RAM. To load other formats, you need to either load them yourself and fire them into an SDL_Surface data structure or use a helper library, such as the popular SDL_Image (see the "Accomplices" box).

Accomplices

SDL covers only the game programmer's basic requirements with its function set. For example, it only loads images in BMP format. Because of this, some developers have written additional helper libraries: SDL_net simplifies access to the network, for example; SDL_mixer allows mixing of audio material; SDL_ttf embellishes text using TrueType fonts, and SDL_Image loads images in popular file formats.

These and several other libraries have even received the status of "official" extensions and found a home on the SDL website. Since the redesign of the home page, they are, however, no longer linked directly [3].

Together with SDL, the four above-mentioned libraries have made the jump to version 2. Essentially, they support the new features in SDL. For example, SDL_Image can return the image as a texture. The following statement loads a TIFF photo, logo.tiff, from disk:

SDL_Texture *logo = IMG_LoadTexture(renderer, "logo.tiff");

Because the libraries only work with SDL 2, they are now called SDL2_Image, SDL2_ttf, SDL2_mixer, and SDL2_net.

Converted

At this point, a small problem arises: The renderer can paint only textures in the window and not surfaces. This means you need to convert the surface into a texture:

SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer, fig);

Because the surface is now redundant, the developer frees the memory allocated to it:

SDL_FreeSurface(fig);

Finally, you just need to tell the renderer to copy the texture into the window:

SDL_RenderCopy(renderer, texture, NULL,NULL);

The NULL in the penultimate parameter instructs the renderer to draw the entire texture in the window and not just a section. If the last parameter is NULL, the renderer fits the texture into the window. If the texture (i.e., the loaded image) has different dimensions from the window, it leads to distortion, as shown in Figure 3.

Figure 3: Here SDL_RenderCopy() was forced to fit the magazine logo into the window dimensions.

Fit

To fit the image properly, you need to tell the SDL_RenderCopy() function the size of the texture, as well as the position of the upper left corner of the image in the window. SDL_RenderCopy() expects this information as an SDL_Rect.

This data structure simply encapsulates the dimensions of a rectangle. In other words, SDL_RECT pos; first draws a rectangle. SDL counts the pixels from the left upper corner of the window, starting at 0. In this example, I want the upper-left corner of the texture to be 150 pixels from the left edge of the window and 100 pixels from the top edge of the window. These two numbers are stored in the variables x and y:

pos.x = 150;
pos.y = 100;

To define the width and height of the texture window, you use the pos.w and pos.h variables. To avoid texture distortion by the renderer, you thus assign the width and height of the texture to it. You can either painstakingly use a paint program to discover these two values, or you can use the SDL_QueryTexture() function:

SDL_QueryTexture(texture, NULL, NULL, &pos.w, &pos.h);

This returns the pixel format (RGB, YUV, or similar), a pointer to the actual bitmap in memory, and the width and height of the texture. This information is no longer of interest, which is why SDL_QueryTexture() keeps it to itself, thanks to the two NULLs.

At this point, pos contains the position and size that the renderer will use to paint the texture in the window. You only need to pass a pointer to pos to SDL_RenderCopy():

SDL_RenderCopy(renderer, texture, NULL,&pos);

The results are shown in Figure 4. Using the same principle, you can then draw more textures in the window. The renderer draws the first textures copied first. In the following example,

SDL_RenderCopy(renderer, rear, NULL,NULL);
SDL_RenderCopy(renderer, front, NULL, NULL);

the renderer would first paint the rear texture in the window and then glue the front texture on top. At the end of the code, you need to tell the renderer to display the updated window contents:

SDL_RenderPresent(renderer);

This last step only seems superfluous at first glance: If the renderer directly output every drawing action in the window, you would be able to see how the image was built up from individual textures. The result would be ugly, flickering animations.

Figure 4: After passing the dimensions and the position of the texture to SDL_RenderCopy(), the logo is finally displayed correctly.

Bus Stop

If you were to put together the code snippets as a program and launch it, you would briefly see a window appear and immediately disappear. To prevent that, you could stop the program just for two seconds:

SDL_Delay(2000);

However, a more elegant approach is to allow the user to quit by pressing a key, clicking on the window, or pressing the close button in the title bar. From this point on, SDL 1.2 users will be back on familiar ground: Keystrokes, mouse clicks, and other inputs are events in SDL.

Please Wait

Each event first ends up in a queue. The next event in the queue is retrieved by the SDL_WaitEvent() function. If the queue is empty, SDL_WaitEvent() waits until an event occurs. Then, you only need to query what event is occurring. The resulting switch/case monster is shown in Listing 1.

Listing 1

Event Handling in SDL

 

The helper variable quit only indicates whether the end of the program has been reached. SDL_WaitEvent() picks up the next event from the queue and puts it into a data structure of the SDL_Event type. The structure stores the event type in a variable named type. If the user presses a key, type has a value of SDL_KEYDOWN. The myevent.key.keysym.scancode event variables reveal what key this was. The key number is translated by SDL_GetScancodeName() into the corresponding letter or the label.

Because the keyboard on any given system will tend to differ, SDL assigns a symbolic name to each key, which is stored in the myevent.key.keysym.sym variable. The SDL_GetKeyName() function converts this to a readable result (Figure 5). An SDL_MOUSEBUTTONDOWN type event is created if the user clicks on the window. The mouse pointer coordinates are stored in the variables myevent.motion.x and myevent.motion.y. A game would evaluate these coordinates and check which game object lies below them.

Figure 5: In this example, the user has pressed the semicolon on the (;) keyboard. The programmer should always evaluate the virtual key codes to the extent possible.

The user triggers the SDL_QUIT event on quitting the program. In addition to the three cases presented, SDL supports many more events [4]. For example, if the user releases a key, this triggers an SDL_KEYUP event.

Loop the Loop

In contrast to SDL 1.2, SDL_WaitEvent() no longer sends the application or the process to sleep. Instead, SDL_WaitEvent() internally runs through a loop until an event occurs. However, the developers have already said that this behavior might change again in the future.

Final Cleaning

To clean up, you should always release the memory occupied by a texture, destroy the renderer and the window, and wipe down SDL with a damp cloth:

SDL_DestroyTexture(texture);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(mywindow);
SDL_Quit();

The complete program is shown in Listing 2. To improve readability, it only fields errors on initialization. You can easily build the program:

gcc -Wall -o helloworld helloworld.c -lSDL2

Listing 2

The Application

 

The application in Listing 2 only demonstrates how to use a small fraction of the options offered by the SDL. A complete description of the feature scope would fill an entire book. Unfortunately, this kind of documentation is not the developer's favorite subject. The official SDL wiki (Figure 6) provides only an incomplete function reference [5]. Developers are advised to use the comments in the header files as a reference.

Figure 6: The SDL developers are desperately looking for volunteers to update the wiki.

Converts from the legacy version 1.2 should read also the Migration Guide [6]. A well-made tutorial for C++ developers can be found on GitHub [7]. All other instructions on the Internet still referred to the legacy SDL 1.2 when this issue went to press.

Conclusions

SDL 2.0 greatly simplifies programming of platform-independent multimedia applications. Listing 2 runs unchanged on all supported systems. The new functions, such as supporting multiple windows and graphics acceleration, were overdue and eliminated the hacks from the past.

As the word "simple" in the name suggests, SDL is still a low-level library that primarily aims to simplify and unify hardware access to multiple platforms. Even the simple drawing functions for rectangles and circles were only added to the new version after the developers received (multiple) requests from users.

If you are looking for fast results, or if you build prototypes, you will probably prefer a games or 3D engine like Pygame, Unity, or Ogre 3D. The developers have postponed some features originally planned for SDL 2.0 to later versions. The idea was to keep the API stable for the time being; changes are only being discussed for version 2.1.

Version 2.0.0 has a few minor bugs [8]; nevertheless, SDL 2.0 is already in use, where stable. New applications should no longer rely on the legacy SDL 1.2 – the future clearly belongs to version 2.0.