SDL Wiki

SDL 1.2 to 2.0 Migration Guide



After many years in development, SDL 2.0 has finally been released!

We are quite proud of it, and we'd like games that are using SDL 1.2 to move up right away. As this can feel like a daunting task, this document is a simple walkthrough of how to migrate to the new library. We think you'll find it's not as hard as you think, and often times, you'll be either replacing function calls with direct equivalents or undoing some hacks in your code to deal with 1.2 deficiencies.

We think you'll be very happy with SDL 2.0, both for the new features and the better experience over SDL 1.2. This document doesn't try to cover all the awesome new things in SDL2--and there are many--but just the things you need to do to get running right now. Once you've ported your code, definitely check out the new stuff; you'll probably want to add some of it to your application, too.

Overview of new features

These are the most important new features in SDL 2.0:

The Introduction page has a more extensive listing about what features SDL offers in overall (including old 1.2 features).

Looking for more information

The best places for information are:

Moving from SDL 1.2 to 2.0

Some general truths

There is no compatibility layer built in to SDL2. If an API changed for 2.0, we've changed or removed the old functions where it makes sense. If you point your 1.2 program at the 2.0 headers, it will probably fail to compile. This document will try to talk you through the most important changes, and the ones most likely to trip you up.

There's no SDL_main! Well, okay, there is, and it now does what it was always meant to: be a small piece of code that hides the difference between main() and WinMain() on Windows. There's no initialization code in it, and it's completely optional. This means you can use SDL without it taking over your mainline, which is nice for plugins that use SDL, or scripting languages with an SDL module. All the stuff you'd want the 1.2 SDL_main for is now in SDL_Init() where it belongs.

There's no SDL parachute anymore. What 1.2 called SDL_INIT_NOPARACHUTE is a default and only state now. This would cause problems if something other than the main thread crashed, and it would interfere with apps setting up their own signal/exception handlers. On the downside, some platforms don't clean up fullscreen video well when crashing. You should install your own crash handler, or call SDL_Quit() in an atexit() function or whatnot if this is a concern. Note that on Unix platforms, SDL still catches SIGINT and maps it to an SDL_QUIT event.


Setting up a game with the new video API

The video API is the most dramatic change from 1.2. Needs have changed a great deal since SDL's original API was designed in the late 1990's. To deal with modern hardware and OS features, we have almost completely replaced the old 1.2 video API.

Don't worry, the new one is pretty great, and once you understand what's changed, you're going to be very happy with the new features it can bring to your 1.2 game. We'll discuss those later.

The good news: if your game used OpenGL, you probably don't have much to do: change a handful of function calls to their SDL2 equivalents, and you're good to go.

For 2D graphics, SDL 1.2 offered a concept called "surfaces," which were memory buffers of pixels. The screen itself was a "surface," if you were doing 2D software rendering, and we provided functions to copy ("blit") pixels between surfaces, converting formats as necessary. You were almost always working on the CPU in system RAM, not on the GPU in video memory. SDL 2.0 changes this; you almost always get hardware acceleration now, and the API has changed to reflect this.

If you have a 2D game, chances are you've taken one of three approaches to rendering. We'll go through them all, but first, let's talk about introductory stuff.

Remember SDL_SetVideoMode()? It's completely gone. SDL 2.0 allows you to have multiple windows, so the old function didn't make sense any more.

So you might have had something like this:

SDL_WM_SetCaption("My Game Window", "game");
SDL_Surface *screen = SDL_SetVideoMode(640, 480, 0, SDL_FULLSCREEN | SDL_OPENGL);

Which is now this:

SDL_Window *screen = SDL_CreateWindow("My Game Window",
                          640, 480,
                          SDL_WINDOW_FULLSCREEN | SDL_WINDOW_OPENGL);

You can see that this maps pretty closely to 1.2. The difference is that you can have multiple windows (if you want), and you can control them more. SDL_WM_SetCaption() is gone, because we want to allow each window to have its own title (you can change it later with SDL_SetWindowTitle()), and we want to let you specify a window position (or, in this case, use SDL_WINDOWPOS_UNDEFINED since we don't care where the system places it. SDL_WINDOWPOS_CENTERED is also a good choice).

Extra credit for letting users specify a display for the window: SDL2 also allows you to manage systems with multiple monitors. Don't worry about that right now, though.

So now that your window is back on the screen, let's talk strategy. SDL2 still has [[SDL_Surface]], but what you want, if possible, is the new [[SDL_Texture]]. Surfaces are always in system RAM now, and are always operated on by the CPU, so we want to get away from there. SDL2 has a new rendering API. It's meant for use by simple 2D games, but most notably, it's meant to get all that software rendering into video RAM and onto the GPU. And even if you just want to use it to get your software renderer's work to the screen, it brings some very nice benefits: if possible, it will use OpenGL or Direct3D behind the scenes, which means you'll get faster blits, a working Steam Overlay, and scaling for free.

The setup looks like this.

SDL_SetVideoMode() becomes SDL_CreateWindow(), as we discussed before. But what do we put for the resolution? If your game was hardcoded to 640x480, for example, you probably were running into monitors that couldn't do that fullscreen resolution at this point, and in windowed mode, your game probably looked like an animated postage stamp on really high-end monitors. There's a better solution in SDL2.

We don't call SDL_ListModes() anymore. There's an equivalent in SDL2 (call SDL_GetDisplayMode() in a loop, SDL_GetNumDisplayModes() times), but instead we're going to use a new feature called "fullscreen desktop," which tells SDL "give me the whole screen and don't change the resolution." For our hypothetical 640x480 game, it might look like this:

SDL_Window *sdlWindow = SDL_CreateWindow(title,
                             0, 0,

Notice how we didn't specify 640 or 480...fullscreen desktop gives you the whole display and ignores any dimensions you specify. The game window should come up immediately, without waiting for the monitor to click into a new resolution, and we'll be using the GPU to scale to the desktop size, which tends to be faster and cleaner-looking than if an LCD is faking a lower resolution. Added bonus: none of your background windows are resizing themselves right now.

Now we need a rendering context.

SDL_Renderer *renderer = SDL_CreateRenderer(sdlWindow, -1, 0);

A renderer hides the details of how we draw into the window. This might be using Direct3D, OpenGL, OpenGL ES, or software surfaces behind the scenes, depending on what the system offers; your code doesn't change, regardless of what SDL chooses (although you are welcome to force one kind of renderer or another). If you want to attempt to force sync-to-vblank to reduce tearing, you can use SDL_RENDERER_PRESENTVSYNC instead of zero for the third parameter. You shouldn't create a window with the SDL_WINDOW_OPENGL flag here. If SDL_CreateRenderer() decides it wants to use OpenGL, it'll update the window appropriately for you.

Now that you understand how this works, you can also do this all in one step with SDL_CreateWindowAndRenderer(), if you don't want anything fancy:

SDL_Window *sdlWindow;
SDL_Renderer *sdlRenderer;
SDL_CreateWindowAndRenderer(0, 0, SDL_WINDOW_FULLSCREEN_DESKTOP, &sdlWindow, &sdlRenderer);

Assuming these functions didn't fail (always check for NULLs!), you are ready to start drawing to the screen. Let's get started by clearing the screen to black.

SDL_SetRenderDrawColor(sdlRenderer, 0, 0, 0, 255);

This works like you might think; draw in black (r,g,b all zero, alpha full), clear the whole window, put the cleared window on the screen. That's right, if you've been using SDL_UpdateRect() or SDL_Flip() to get your bits to the screen, the render API uses SDL_RenderPresent().

One more general thing to set up here. Since we're using SDL_WINDOW_FULLSCREEN_DESKTOP, we don't actually know how much screen we've got to draw to. Fortunately, we don't have to know. One of the nice things about 1.2 is that you could say "I want a 640x480 window and I don't care how you get it done," even if getting it done meant centering the window in a larger resolution on behalf of your application.

For 2.0, the render API lets you do this...

SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");  // make the scaled rendering look smoother.
SDL_RenderSetLogicalSize(sdlRenderer, 640, 480);

...and it will do the right thing for you. This is nice in that you can change the logical rendering size to achieve various effects, but the primary use is this: instead of trying to make the system work with your rendering size, we can now make your rendering size work with the system. On my 1920x1200 monitor, this app thinks it's talking to a 640x480 resolution now, but SDL is using the GPU to scale it up to use all those pixels. Note that 640x480 and 1920x1200 aren't the same aspect ratio: SDL takes care of that, too, scaling as much as possible and letterboxing the difference.

Now we're ready to start drawing for real.

If your game just wants to get fully-rendered frames to the screen

A special case for old school software rendered games: the application wants to draw every pixel itself and get that final set of pixels to the screen efficiently in one big blit. An example of a game like this is Doom, or Duke Nukem 3D, or many others.

For this, you're going to want a single SDL_Texture that will represent the screen. Let's create one now for our 640x480 game:

sdlTexture = SDL_CreateTexture(sdlRenderer,
                               640, 480);

This represents a texture on the GPU. The gameplan is to finish each frame by uploading pixels to this texture, drawing the texture to the window, and flipping this drawing onto the screen. SDL_TEXTUREACCESS_STREAMING tells SDL that this texture's contents are going to change frequently.

Before you probably had an SDL_Surface for the screen that your app drew into, then called SDL_Flip() to put it to the screen. Now you can create an SDL_Surface that is always in RAM instead of using the one you would have gotten from SDL_SetVideoMode(), or just malloc() a block of pixels to write into. Ideally you write to a buffer of RGBA pixels, but if you need to do a conversion, that's okay too.

extern Uint32 *myPixels;  // maybe this is a surface->pixels, or a malloc()'d buffer, or whatever.

At the end of the frame, we want to upload to the texture like this:

SDL_UpdateTexture(sdlTexture, NULL, myPixels, 640 * sizeof (Uint32));

This will upload your pixels to GPU memory. That NULL can be a subregion if you want to mess around with dirty rectangles, but chances are modern hardware can just swallow the whole framebuffer without much trouble. The final argument is the pitch--the number of bytes from the start of one row to the next--and since we have a linear RGBA buffer in this example, it's just 640 times 4 (r,g,b,a).

Now get that texture to the screen:

SDL_RenderCopy(sdlRenderer, sdlTexture, NULL, NULL);

That's all. SDL_RenderClear() wipes out the existing video framebuffer (in case, say, the Steam Overlay wrote over it last frame), SDL_RenderCopy() moves the texture's contents to the video framebuffer (and thanks to SDL_RenderSetLogicalSize(), it will be scaled/centered as if the monitor was 640x480), and SDL_RenderPresent() puts it on the screen.

If your game wants to blit surfaces to the screen

This scenario has your SDL 1.2 game loading a bunch of graphics from disk into a bunch of SDL_Surfaces, possibly trying to get them into video RAM with SDL_HWSURFACE. You load these once, and you blit them over and over to the framebuffer as necessary, but otherwise they never change. A simple 2D platformer might do this. If you tend to think of your surfaces as "sprites," and not buffers of pixels, then this is probably you.

You can build individual textures (surfaces that live in GPU memory) like we did for that one big texture:

sdlTexture = SDL_CreateTexture(sdlRenderer,
                               myWidth, myHeight);

Which does what you'd expect. We use SDL_TEXTUREACCESS_STATIC, because we're going to upload our pixels once instead of over and over. But a more convenient solution might be:

sdlTexture = SDL_CreateTextureFromSurface(sdlRenderer, mySurface);

Use this, and you load your SDL_Surface as usual, but then at the end you make a texture out of it. Once you have an SDL_Texture, you can free the original surface.

At this point, your 1.2 game had a bunch of SDL_Surfaces, which it would SDL_BlitSurface() to the screen surface to compose the final framebuffer, and eventually SDL_Flip() to the screen. For SDL 2.0, you have a bunch of SDL_Textures, that you will SDL_RenderCopy() to your Renderer to compose the final framebuffer, and eventually SDL_RenderPresent() to the screen. It's that simple. If these textures never need modification, you might find your framerate has just gone through the roof, too.

If your game wants to do both

Things get slightly more complicated if you want to blit surfaces and modify individual pixels in the framebuffer. Round trips--reading data back from textures--can be painfully expensive; generally you want to be pushing data in one direction always. You are probably best off, in this case, keeping everything in software until the final push to the screen, so we'll combine the two previous techniques.

The good news: the 1.2 SDL_Surface API mostly still exists. So change your screen surface from this:

SDL_Surface *screen = SDL_SetVideoMode(640, 480, 32, 0); this...

// if all this hex scares you, check out SDL_PixelFormatEnumToMasks()!
SDL_Surface *screen = SDL_CreateRGBSurface(0, 640, 480, 32,
SDL_Texture *sdlTexture = SDL_CreateTexture(sdlRenderer,
                                            640, 480);

...and continue blitting things around and tweaking pixels as before, composing your final framebuffer into this SDL_Surface. Once you're ready to get those pixels on the screen, you do this just like in our first scenario:

SDL_UpdateTexture(sdlTexture, NULL, screen->pixels, screen->pitch);
SDL_RenderCopy(sdlRenderer, sdlTexture, NULL, NULL);

Note that texture creation may be both expensive and a limited resource: don't call SDL_CreateTextureFromSurface() every frame. Set up one texture and one surface and update the former from the latter.

There are more features to the Render API, some of which may be able to replace your application's code: scaling, line drawing, etc. If you are reading this section because you have simple needs beyond blitting surfaces, you might be able to stop poking individual pixels and move everything onto the GPU, which will give your program a significant speed boost and probably simplify your code greatly.

Other Renderer API notes

You can do some simple effects with the render API without having to get down into direct pixel manipulation. Some of these were available on 1.2 surfaces.


If you were already using OpenGL directly, your migration is pretty simple. Change your SDL_SetVideoMode() call to SDL_CreateWindow() followed by SDL_GL_CreateContext(), and your SDL_GL_SwapBuffers() call to SDL_GL_SwapWindow(window). All the actual calls into the GL are exactly the same.

If you had used SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, x), this has changed. There is now an SDL_GL_SetSwapInterval(x) call, so you can change this on an existing GL context.

Note that SDL 2.0 can toggle windowed/fullscreen and back with OpenGL windows without losing the GL context (hooray!). Use SDL_SetWindowFullscreen() for this.


The good news is that SDL 2.0 has made Unicode input usable. The bad news is that it will take some minor changes to your application.

In 1.2, many applications that only cared about US English still called SDL_EnableUNICODE(1), because it was useful to get the character that was associated with a keypress. This didn't work well once you got outside of English, and it really didn't work at all once you got to Asian languages.

It turns out, i18n is hard.

SDL changed this. SDL_EnableUNICODE() is gone, and so is SDL_Keysym's unicode field. You no longer get character input from SDL_KEYDOWN events. Use SDL_KEYDOWN to treat the keyboard like a 101-button joystick now. Text input comes from somewhere else.

The new event is SDL_TEXTINPUT. This is triggered whenever there's new text entered by the user. Note that this text might be coming from keypresses, or it might be coming from some sort of IME (which is a fancy way of entering complicated, multi-character text). This event returns entire strings, which might be one char long, or several codepoints of multi-character data. This string is always in UTF-8 encoding.

If all you care about is whether the user pressed a certain key, that's still SDL_KEYDOWN, but we've split this system into two pieces since 1.2: keycodes and scancodes.

Scancodes are meant to be layout-independent. Think of this as "the user pressed the Q key as it would be on a US QWERTY keyboard" regardless of whether this is actually a European keyboard or a Dvorak keyboard or whatever. The scancode is always the same key position.

Keycodes are meant to be layout-dependent. Think of this as "the user pressed the key that is labelled 'Q' on their specific keyboard."

In example, if you pressed the key that's two keys to the right of CAPS LOCK on a US QWERTY keyboard, it'll report a scancode of SDL_SCANCODE_S and a keycode of SDLK_S. The same key on a Dvorak keyboard, will report a scancode of SDL_SCANCODE_S and a keycode of SDLK_O.

Note that both keycodes and scancodes are now 32 bits, and use a wide range of numbers. There's no SDLK_LAST anymore. If your program had a lookup table of SDLK_LAST elements, to map between SDL keys and whatever your application wanted internally, that's no longer feasible. Use a hash table instead. A std::map will do. If you're mapping scancodes instead of keycodes, there's SDL_NUM_SCANCODES, which you can use for array bounds. It's 512 at the moment.

SDLMod is now SDL_Keymod and its "META" keys (the "Windows" keys) are now called the "GUI" keys.

SDL_GetKeyState() has been renamed to SDL_GetKeyboardState(). The returned array should now be indexed by SDL_SCANCODE_* values (see SDL_Scancode) instead of SDL_Keysym values.

Now, for mouse input.

The first change, simply enough, is that the mousewheel is no longer a button. This was a mistake of history, and we've corrected it in SDL 2.0. Look for SDL_MOUSEWHEEL events. We support both vertical and horizontal wheels, and some platforms can treat two-finger scrolling on a trackpad as wheel input, too. You will no longer receive SDL_BUTTONDOWN events for mouse wheels, and buttons 4 and 5 are real mouse buttons now.

If your game needed to roll the mouse in one direction forever, for example to let a player in an FPS to spin around without the mouse hitting the edge of the screen and stopping, you probably hid the mouse cursor and grabbed input:


In SDL2, this works slightly differently. You call...


...and SDL does the rest.


SDL_PushEvent() now returns 1 on success instead of 0.

Events mask are now specified using ranges:





The good news for audio is that, with one exception, it's entirely backwards compatible with 1.2. If you want the new features, they're available to you, but you'll probably just compile and run without them.

That one really important exception: The audio callback does NOT start with a fully initialized buffer anymore. You must fully write to the buffer in all cases. If you don't have enough audio, your callback should write silence. If you fail to do this, you'll hear repeated audio, or maybe audio corruption. If you want to restore the old behavior of unconditionally initializing the buffer, just put an SDL_memset(stream, 0, len) at the start of your callback.


Joystick events now refer to an SDL_JoystickID. This is because SDL 2.0 can handle joysticks coming and going, as devices are plugged in and pulled out during your game's lifetime, so the index into the device list that 1.2 uses would be meaningless as the available device list changes.

To get an SDL_JoystickID for your opened SDL_Joystick*, call:

SDL_JoystickID myID = SDL_JoystickInstanceID(myOpenedStick);

And compare the joystick events' which field against myID. If you aren't using the event queue for joysticks, SDL_JoystickGetAxis() and friends work just like SDL 1.2.

You should also check out the new Game Controller API too, because it's cool, and maybe you did a lot of tap dancing with the 1.2 API that this new code would solve more cleanly. You can find it in SDL_gamecontroller.h. The Game Controller API integrates really nicely with Steam Big Picture Mode: you get automatic configuration of most controllers, and a nice UI if you have to manually configure it. In either case, Steam passes this configuration on to your SDL application.

Support for the older joystick API (/dev/input/js) for Linux has been dropped from SDL2. SDL2 only supports the newer events API (/dev/input/event) for joysticks. These events are not normally readable for normal user accounts, so even if joysticks are plugged in you will likely have none detected. This is something that end users will have to configure for themselves.


SDL_KillThread() is gone. It was never safe or reliable. The best replacement is to set a flag that tells a thread it should quit. That thread should check the flag with some frequency, and then the "killing" thread calls SDL_WaitThread() to clean up.

SDL_CreateThread() takes an extra parameter now, a name for the thread, which can be used by debuggers to identify it. If you don't care about that, just stuff an extra NULL into your function call.

Audio CDs

The 1.2 CD API is completely gone. There's no replacement. Chances are you aren't shipping your music as CD-Audio tracks on a disc at this point, if you're shipping a disc at all, and in modern times, machines don't come with CD players wired to play audio directly, or CD players at all!

You can use Ogg Vorbis or some other audio file format for music, many of which are provided by SDL_mixer.

Dead platforms

We ripped out a bunch of old platforms, like Windows CE and Mac OS 9. It would be easier to list the ones we still support: Windows (XP and later, and UWP), Linux, macOS, iOS, Android, Emscripten, various console ports. In SDL tradition, there are others on the periphery that work but aren't heavily supported, like Haiku and Sony PSP. We'll add any platform that someone sends patches for, but it seemed like it was time to say goodbye to some old friends when moving to the new version.

Mobile platforms

There have been, for many years, unofficial ports of SDL 1.2 to iOS and Android. SDL now supports these platforms directly, and the 2.0 API is much better suited to them. Most of the advice you've gotten elsewhere in this document applies, but there are a few other things worth noting.

First, there are certain events that only apply to mobile devices, or better said, apply to the way mobile device OSes tend to operate in a post-iPhone world. We originally tried to map these to the existing SDL events (such as "your application is going to the background" being treated like a desktop window losing focus), but there's a more urgent concern: most of these events need an immediate response, and if the app doesn't give one, the OS will kill your application.

As such, we've added new SDL events for some Android and iOS specific details, but you should set up an SDL event filter to catch them as soon as the OS reports them, because waiting until your next SDL_PollEvent() loop will be too late.

For example, there's SDL_APP_WILLENTERBACKGROUND, which is iOS's applicationWillResignActive(), and if you draw to the screen after this event arrives, iOS terminates your process. So you want to catch this immediately:

int SDLCALL myEventFilter(void *userdata, SDL_Event * event)
    if (event->type == SDL_APP_WILLENTERBACKGROUND) {
        // free up resources, DON'T DRAW ANY MORE until you're in the foreground again!
    // etc
    return 1;

// somewhere near startup...

// this calls myEventFilter(data, event) as soon as event is generated.
SDL_AddEventWatch(myEventFilter, data);

Second, there are real touch events now, instead of trying to map this to mouse input. You can track touches, multiple fingers, and even complex gestures. You probably want to use those. Refer to SDL_touch.h for a list of these functions, and look for SDL_Finger* in SDL_events.h.

Note that SDL will also map simple touches to look like mouse events (with the mouse event's "which" field set to SDL_TOUCH_MOUSEID), which means that if you don't care about more complex touch interfaces, your existing desktop app might still work out of the box on a phone where the user is poking the screen with a finger. As such: mobile-aware apps should probably ignore SDL_TOUCH_MOUSEID events, but still respect "real" mouse events in addition to the touch events--some mobile devices support USB and Bluetooth mice, after all!--but this is something to consider more deeply when you start to polish your app, after you are up and running on SDL2.

There are a handful of other mobile-friendly functions, like SDL_StartTextInput(), which will show the on-screen keyboard. Make use of them.

In addition, there are also Android and iOS specific functions, to let you access platform-specific features that wouldn't make sense in a general API. Refer to SDL_system.h for a list of these functions.


SDL_RWread() and SDL_RWwrite() now return 0 on error instead of -1.

If you wrote your own SDL_RWops implementation, the function signatures have changed. Functions now use Sint64 and size_t instead of int so they can work with large files. In many cases, you can just update your function signatures and keep working as before, but if you had bumped up against these limitations, you might be happy to have a solution. Calling applications should know that the return values have changed.

There is also a size method to RWops, now. It is called SDL_RWsize(). This lets a RWops report the size of the stream without having to make the app seek to zero bytes from the end; in other words, you can report a total size for streams that can't seek. For streams that can't even do that, you can still return -1.

Add-on libraries

The official extensions SDL_image, SDL_ttf, SDL_mixer and SDL_net have a version dedicated to SDL 2.0: SDL2_image, SDL2_ttf, SDL2_mixer and SDL2_net. You may need to download them from the GitHub repositories for the latest fixes. Subsequently, of course, you will have to link against e.g. SDL2_image, not SDL_image, to compile your program.

These libraries will not be supporting 1.2 going forward, and any compatibility with 1.2 is likely to vanish at some point from newer versions.

SDL_gfx can also be compiled with 2.0 starting since 2.0.21 (May 2010).

Summary of some renamed or replaced things

A short cheat sheet where some of the old functions and other stuff went:

Other stuff

There's an enormous amount of new and interesting functionality in SDL 2.0 that 1.2 couldn't even dream of. We've only tried to explain what you might have to do to get your 1.2 program running on 2.0 here, but you should explore the documentation for things that you might have always wished for and, until now, done without. For example, every game I've ever ported ended up with a message box function that looked like this:

fprintf(stderr, "MSGBOX: %s\n%s\n", title, text);   // oh well.

Now there's SDL_ShowSimpleMessageBox(). You're welcome!

If you skipped ahead, go back and check out all the new features at the overview!

[ edit | delete | history | feedback | raw ]

[ front page | index | search | recent changes | git repo | offline html ]

All wiki content is licensed under Creative Commons Attribution 4.0 International (CC BY 4.0).
Wiki powered by ghwikipp.