Hi.
For those of you who don't know what SDL is, go to
www.libsdl.org
IntroductionSo SDL2 is out.
It's awesome, I know.
Unfortunately, there's still not much documentation out there on the internet. This is some of what I gathered here and there.
There's an official migration guide, but I find it lacking. Link:
http://wiki.libsdl.org/MigrationGuide
The mainIn general, you want your main to do the least amount of work possible. Have it delegate it to other functions (or objects if you're using
OOP). The general structure should be something like this:
int main(int argc, char* argv[])
{
/* Initialization */
/* Main loop: */
bool running = true;
while(running)
{
/* Render */
/* Get input and poll events */
if(/* Got quit event */)
{
running = false;
}
/* Run logic */
}
/* Deinitialization */
return 0;
}
In SDL2, it would look like this:int main(int argc, char* argv[])
{
if(SDL_Init(SDL_INIT_EVERYTHING) < 0)
{
/* Handle problem */
fprintf(stderr, "%s\n", SDL_GetError());
}
SDL_Window* window = SDL_CreateWindow("Window caption", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 852, 480, 0);
if(window == NULL)
{
/* Handle problem */
fprintf(stderr, "%s\n", SDL_GetError());
SDL_Quit();
}
SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, 0);
if(renderer == NULL)
{
/* Handle problem */
fprintf(stderr, "%s\n", SDL_GetError());
SDL_Quit();
}
bool running = true;
while(running)
{
SDL_Rect where;
/* Clear the buffer of color, setting it to black */
SDL_RenderClear(renderer);
/* Do some rendering to the screen surface. For example: */
where.x = player.position_x; where.y = player.position_y;
where.w = player.width; where.h = player.height;
SDL_RenderCopy(renderer, player.texture, NULL, NULL);
/* Draw the buffer into the window */
SDL_RenderPresent(renderer);
/* Handle input and events: */
SDL_Event sdl_event;
while(SDL_PollEvent(&sdl_event) > 0)
{
switch(sdl_event.type)
{
case(SDL_QUIT):
running = false;
break;
}
}
/* Run your logic. For example: */
player.update();
}
SDL_Quit();
return 0;
}
As opposed to SDL1.2:int main(int argc, char* argv[])
{
if(SDL_Init(SDL_INIT_EVERYTHING) < 0)
{
/* Handle problem */
fprintf(stderr, "%s\n", SDL_GetError());
}
SDL_Surface* screen = SDL_SetVideoMode(640, 480, 0, 0);
if(screen == NULL)
{
/* Handle problem */
fprintf(stderr, "%s\n", SDL_GetError());
SDL_Quit();
}
bool running = true;
while(running)
{
SDL_Rect where;
/* Clear the buffer of color, setting it to black */
SDL_FillRect(screen, NULL, SDL_MapRGB(window->format, 0, 0, 0));
/* Do some rendering to the screen surface. For example: */
where.x = player.position_x; where.y = player.position_y;
SDL_BlitSurface(player.graphic, NULL, screen, &where);
/* Draw the buffer into the window */
SDL_Flip(screen);
/* Handle input and events: */
SDL_Event sdl_event;
while(SDL_PollEvent(&sdl_event) > 0)
{
switch(sdl_event.type)
{
case(SDL_QUIT):
running = false;
break;
}
}
/* Run your logic. For example: */
player.update();
}
SDL_Quit();
return 0;
}
The pragmatic mainIn the previous chapter, I've written something like
fprintf(stderr, "%s\n", SDL_GetError()); wherever some error happened. What this does is simply ask SDL for a string describing what happened (using
SDL_GetError() and print it into
stderr (which is redirected by SDL to be a log file named stderr.txt). I find it inconvenient.
One very convenient thing that SDL2 provides you with is the ability to display message boxes
in a cross-platform manner. The way you do this is with
SDL_ShowSimpleMessageBox()The way I handle this is with exceptions.
int main(int argc, char* argv[])
{
SDL_Window* window = NULL;
try
{
if(SDL_Init(SDL_INIT_EVERYTHING) < 0)
{
throw(std::exception(SDL_GetError()));
}
window = SDL_CreateWindow("Window caption", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 852, 480, 0);
if(window == NULL)
{
throw(std::exception(SDL_GetError()));
}
SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, 0);
if(renderer == NULL)
{
throw(std::exception(SDL_GetError()));
}
/* The main loop... */
SDL_Quit();
}
catch(std::exception& e)
{
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Unhandled exception", e.what(), window);
}
catch(std::exception* e)
{
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Unhandled exception", e->what(), window);
}
catch(...)
{
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Unhandled exception", "Unknown exception type", window);
}
return 0;
}
The next thing that I want to change is the C-style handling of things. Yuck!
Let's use
RAII!
The benefit of using RAII is that the language's scoping rules handle the resource's lifetime. While writing this, I actually forgot to call
SDL_Quit() on some return paths. RAII makes this implicit.
class SDLHandle
{
private:
static int ref_count;
SDL_Window* window;
SDL_Renderer* renderer;
public:
SDL_Window* getWindow() const { return window; }
SDL_Renderer* getRenderer() const { return renderer; }
SDLHandle(const char* caption)
{
if(ref_count == 0)
{
if(SDL_Init(SDL_INIT_EVERYTHING) < 0)
{
const char* err = SDL_GetError();
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Critical: Unable to initialize SDL", err, NULL);
throw(std::exception(err));
}
}
++ref_count;
try
{
window = SDL_CreateWindow(caption, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 852, 480, 0);
if(window == NULL)
{
throw(std::exception(SDL_GetError()));
}
renderer = SDL_CreateRenderer(window, -1, 0);
if(renderer == NULL)
{
throw(std::exception(SDL_GetError()));
}
}
catch(std::exception& e)
{
const char* err = SDL_GetError();
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Unable to initialize SDL", err, window);
throw(std::exception(err));
}
}
~SDLHandle()
{
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
--ref_count;
if(ref_count == 0)
{
SDL_Quit();
}
}
};
int SDLHandle::ref_count = 0;
int main(int argc, char* argv[])
{
SDLHandle sdl("Window caption");
bool running = true;
while(running)
{
// Business as usual...
}
return 0;
}
And by the way, handling multiple windows just got easier.
More on renderingRendering can be done in two ways: Either you render with a specific API (like
OpenGL), or you render with SDL's rendering API.
To render with OpenGL, simply don't create an
SDL_Renderer. Instead, you need to
SDL_GL_CreateContext() Then use OpenGL as usual. Since you don't have an
SDL_Renderer, you can't use
SDL_RenderPresent() to draw your buffer. Instead, use
SDL_GL_SwapWindow()But this is not a tutorial on OpenGL. More on rendering with SDL:
Rendering with SDL
does not mean that you're rendering in software. In fact, SDL2's new API is very friendly towards
hardware accelerated APIs (OpenGL guys will know). What SDL actually does behind the scenes is look at which platform you're running the application on, and select the API which is estimated to give you the best performance and all of the
features you ask when initializing the renderer.
Naturally, it also gives you some convenience functions that you wouldn't have if you'd have used the API directly. One such feature is
SDL_RenderSetLogicalSize(). Simply put, this thing handles
letterboxing and
scaling of the display for you. You might want to do a
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear"); before calling this to improve your chances of getting a nice quality on a scaled resolution.
Window management and inputIf you're using OpenGL, then this is what you came to SDL for. Nevertheless, users that use only SDL's rendering API need to know this just as well.
Window ManagementSDL2 provides you with a lot more window management features than what SDL1.2 had in store. Most are done when you're calling
SDL_CreateWindow(). Here you can set the caption, the initial position, the dimensions (resolution), and many other
flags. Notable ones are
SDL_WINDOW_FULLSCREEN,
SDL_WINDOW_OPENGL and
SDL_WINDOW_RESIZABLE.
You also have plenty of
setters to modify these on the fly.
This really makes SDL1.2 look puny and insignificant, because it only had a couple of flags (
SDL_FULLSCREEN, SDL_OPENGL, SDL_RESIZABLE, SDL_SWSURFACE, SDL_HWSURFACE, SDL_HWACCEL), caption setting had to be done separately (preferably before window creation) using
SDL_WM_SetCaption(), and window positions had to be done with nasty environment-variable hacks. Oh, and you could have only one window at a time.
Here are some more awesome SDL2 window-management related functions:
SDL_ShowMessageBox(), SDL_ShowSimpleMessageBox()InputNothing much changed in this department between SDL1.2 and SDL2.
There are is some minor refactoring that you can see on the official migration guide.Input management in SDL is tied to event management. This makes sense; after all, the
OS does it that way.
The process is simple: Something generates events (like the OS), SDL creates an SDL_Event and enqueues it in a
queue, your application dequeues it and handles it.
You have three functions to manipulate the queue. You can dequeue from it with
SDL_PollEvent(), peek in it with
SDL_PeepEvents(), and you can enqueue into it with
SDL_PushEvent().
During one main loop iteration, multiple events may be generated by the OS. This is why you want to do the polling in a loop.
SDL_Event sdl_event;
while(SDL_PollEvent(&sdl_event) > 0) /* While there are more than 0 events in the queue */
{
/* Handle events */
}
SDL_Event is a rather big structure that you might want to read a lot about. You can get all sorts of useful information about the event in question from it.
The most useful piece of information that you're going to get is the
event's type. Get it like
sdl_event.type. Notable ones are:
SDL_QUIT, SDL_KEYDOWN, SDL_KEYUP, SDL_MOUSEMOTION, SDL_MOUSEBUTTONDOWN, SDL_MOUSEBUTTONUPHere's an example:
int cursor_x = 0;
int cursor_y = 0;
bool move_up = false;
bool move_down = false;
bool move_left = false;
bool move_right = false;
bool shoot = false;
SDL_Event sdl_event;
while(SDL_PollEvent(&sdl_event) > 0)
{
switch(sdl_event.type)
{
case(SDL_QUIT):
running = false;
break;
case(SDL_KEYDOWN):
switch(sdl_event.key.keysym.sym)
{
case(SDLK_w):
case(SDLK_UP):
move_up = true;
break;
case(SDLK_a):
case(SDLK_LEFT):
move_left = true;
break;
case(SDLK_s):
case(SDLK_DOWN):
move_down = true;
break
case(SDLK_d):
case(SDLK_RIGHT):
move_right = true;
break;
}
}
case(SDL_KEYUP):
switch(sdl_event.key.keysym.sym)
{
case(SDLK_w):
case(SDLK_UP):
move_up = false;
break;
case(SDLK_a):
case(SDLK_LEFT):
move_left = false;
break;
case(SDLK_s):
case(SDLK_DOWN):
move_down = false;
break
case(SDLK_d):
case(SDLK_RIGHT):
move_right = false;
break;
}
case(SDL_MOUSEMOTION):
cursor_x = sdl_event.motion.x;
cursor_y = sdl_event.motion.y;
break;
case(SDL_MOUSEBUTTONDOWN):
if(sdl_event.button.button == SDL_BUTTON_LEFT)
{
shoot = true;
}
break;
case(SDL_MOUSEBUTTONUP):
if(sdl_event.button.button == SDL_BUTTON_LEFT)
{
shoot = false;
}
break;
}
}