Handmade Penguin Chapter 2: Opening an SDL Window

This chapter covers roughly the content in the Day 2: Opening a Win32 Window part of the Handmade Hero course, under the Linux operating system.

<-- Chapter 1 | Back to Index | Chapter 3 -->

Initialising SDL

We're going to be using SDL a lot today. It'd be a good idea to keep the SDL Wiki open in case you need to look things up!

To begin with, you need to initialise SDL. We didn't need to do this for the SDL_ShowSimpleMessageBox() function, but it's a special case (you need to be able to show a message box if SDL initialisation fails, don't you). Fortunately, initialising SDL is easy: we just use the SDL_Init function. We also need to tell SDL which subsystems we want to initialise: to start off we want to initialise only the video subsystem, which handles graphics and window management.

if (SDL_Init(SDL_INIT_VIDEO) != 0)
{
	//TODO: SDL_Init didn't work!
}

You notice that SDL_Init() returns an error code. This is basically a number which is 0 if no error occurred and some other number otherwise. You can use SDL_GetError to convert an SDL error code into a string that describes the error.

The other thing we need to do is to shutdown SDL once we're finished. We can do this with SDL_Quit(), so let's add that to the end of our main() function.

Be warned! If you're using SDL on Windows under a .NET language (possibly using the SDL2# wrapper), you may find that your program may silently exit when run under the Visual Studio debugger. You'll want to add SDL_SetHint(SDL_HINT_WINDOWS_DISABLE_THREAD_NAMING, "1"); before calling SDL_Init(). This will tell SDL not to name threads, which on Windows is implemented by triggering a special exception. The native code debuggers on Windows trap this exception and name the thread, but the .NET debugger doesn't handle it properly, causing the process to die.
Thanks to Jack Mott for pointing this out!

Creating a window

The good news is that setting up a window is significantly easier with SDL than it is on windows. We need to create a variable which is a pointer to a struct of type SDL_Window:

SDL_Window *Window;
This is called a window handle (much like the HWND in Windows) and is how we tell SDL which window we want to talk about. It's not too important when you only have one window, but it's almost impossible to have multiple windows without them.

Now we just need to create our window. We don't need to register window classes or anything, just call the SDL_CreateWindow() function. The SDL_CreateWindow() function takes a lot of arguments: let's go through them!

We can put that together in our main() function, between SDL_Init() and SDL_Quit():
Window = SDL_CreateWindow("Handmade Hero",
                          SDL_WINDOWPOS_UNDEFINED,
                          SDL_WINDOWPOS_UNDEFINED,
                          640,
                          480,
                          SDL_WINDOW_RESIZABLE);
If we then compile and run our program, we see a window: success! It disappears after a split second though. We're creating it, but then our program quits immediately. We need to keep the program running while the window is on the screen, and quit when the user closes the window. We'll do that as part of an event loop.

Events and SDL_Event

Events in SDL are given in the form of a union called SDL_Event. A union is like a struct, but all of the members share the same memory. Let's look at the definition of SDL_Event (you can find it in the SDL_events.h header file, which usually lies in /usr/include/SDL2):

typedef union SDL_Event
{
    Uint32 type;                        /**< Event type, shared with all events */
    SDL_CommonEvent common;             /**< Common event data */
    SDL_WindowEvent window;             /**< Window event data */
    SDL_KeyboardEvent key;              /**< Keyboard event data */
    SDL_TextEditingEvent edit;          /**< Text editing event data */
    SDL_TextInputEvent text;            /**< Text input event data */
    SDL_MouseMotionEvent motion;        /**< Mouse motion event data */
    SDL_MouseButtonEvent button;        /**< Mouse button event data */
    SDL_MouseWheelEvent wheel;          /**< Mouse wheel event data */
    SDL_JoyAxisEvent jaxis;             /**< Joystick axis event data */
    SDL_JoyBallEvent jball;             /**< Joystick ball event data */
    SDL_JoyHatEvent jhat;               /**< Joystick hat event data */
    SDL_JoyButtonEvent jbutton;         /**< Joystick button event data */
    SDL_JoyDeviceEvent jdevice;         /**< Joystick device change event data */
    SDL_ControllerAxisEvent caxis;      /**< Game Controller axis event data */
    SDL_ControllerButtonEvent cbutton;  /**< Game Controller button event data */
    SDL_ControllerDeviceEvent cdevice;  /**< Game Controller device event data */
    SDL_QuitEvent quit;                 /**< Quit request event data */
    SDL_UserEvent user;                 /**< Custom event data */
    SDL_SysWMEvent syswm;               /**< System dependent window event data */
    SDL_TouchFingerEvent tfinger;       /**< Touch finger event data */
    SDL_MultiGestureEvent mgesture;     /**< Gesture event data */
    SDL_DollarGestureEvent dgesture;    /**< Gesture event data */
    SDL_DropEvent drop;                 /**< Drag and drop event data */

    /* This is necessary for ABI compatibility between Visual C++ and GCC
       Visual C++ will respect the push pack pragma and use 52 bytes for
       this structure, and GCC will use the alignment of the largest datatype
       within the union, which is 8 bytes.

       So... we'll add padding to force the size to be 56 bytes for both.
    */
    Uint8 padding[56];
} SDL_Event;

Phew: that's a big union. The first thing we notice is that it's actually a typedef. This is for the same reason WNDCLASS was actually a typedef of struct tagWNDCLASS: in C (as opposed to C++), structs and unions are not part of the global namespace. The type of a struct called mystruct would actually be struct mystruct, just typing mystruct wouldn't work. The typedef is a way around that: typedefs are a part of the global namespace, so they can be referenced by name.

The first element of the union is Uint32 type: this is a 32-bit unsigned integer, which represents the type of event which has occurred. The list of possible events is found in the SDL_EventType enumeration.

We then see a whole bunch of structs. These contain the parameters for different types of events. This is the real reason that SDL_Event is a union, if it were a struct, we'd be storing parameters for every type of event, even though only one would be valid. As this is a waste of memory, we stuff them all in the same memory with a union. "Wait a minute," you may say, "how can both type and one of the structs be valid if they both share the same memory?" The secret there is simply that the first element of all of those structs is also Uint32 type, so the first 4 bytes of memory always represent the type of event.

Handling Events

So how are we going to handle events. Let's start by writing a function to handle an event.

bool HandleEvent(SDL_Event *Event)
{
    bool ShouldQuit = false;
    return(ShouldQuit);
}
We'll make the function accept a pointer to an event, as a pointer is only 4 or 8 bytes long, and so can be copied quickly and easily, whereas an instance of SDL_Event is 56 bytes, which is somewhat bigger and therefore slower to copy. This sort of efficiency is usually not important, but since it doesn't make the code any more complicated, it's not too bad of an idea. We'll make our function return a boolean (true or false) value, which will be true if we want the program to quit, and false otherwise.

Be warned! The bool type was not originally a part of the C programming language, but it has always been a part of C++. Since we're compiling as C++, we can use it with no problems. If you want to use pure C, you either need to define your own bool type, or (if your compiler supports the C99 standard) include the stdbool.h header.

So now let's handle some events! This looks very similar to the MainWindowCallback() function in the stream, because we're going to be using a switch statement. We want to do something different depending on the value of Event->type (because Event is a pointer, we use -> instead of . to access a member), so we'll switch on Event->type.

switch(Event->type)
{

}

We now just need to work out what messages we're going to handle. Let's start with SDL_QUIT, as it's probably the most important message.

case SDL_QUIT:
{
    printf("SDL_QUIT\n");
    ShouldQuit = true;
} break;
We print out SDL_QUIT with printf (remember, we don't have OutputDebugString on Linux) to let us know we're handling an SDL_QUIT message. We'll also set ShouldQuit to true, so that we return true, and the function that called us knows to quit the program.

By itself, SDL_QUIT is pretty boring. Let's handle some other events. What if we want to handle our window getting resized? If you look in SDL_EventType, however, you won't find a resize event. This is because the resize event is actually a type of window event. We do see SDL_WINDOWEVENT, though. There's a struct in SDL_Event called SDL_WindowEvent. If we look, it has a member event which contains an SDL_WindowEventID. Behold, more event types — including SDL_WINDOWEVENT_RESIZED.

To handle this event-type within an event-type scenario, we need to have a switch statement within a switch statement.

case SDL_WINDOWEVENT:
{
    switch(Event->window.event)
    {
        case SDL_WINDOWEVENT_RESIZED:
        {
        } break;
    }
} break;

When we get resized, we probably want to know the new size, so let's print that out. If we look at the documentation for SDL_WindowEventID, we see that it says that the "window has been resized to data1 x data2". This means that the new width of the window is stored in Event->window.data1 and the new height of the window is stored in Event->window.data2. To print these out, we'll use a slightly more advanced feature of printf():

printf("SDL_WINDOWEVENT_RESIZED (%d, %d)\n", Event->window.data1, Event->window.data2);
The %ds in the printf() string tell printf() to look for an integer as an argument to printf() and to print it in decimal. Neat, isn't it?

So how are we actually going to get these events and send them to our handler? Like with windows, SDL has an event loop and handles messages. Unlike windows, SDL does not have a callback: you get the messages directly. (Actually, you can use a callback by setting something called an Event Filter if you want, but most people don't bother.) There are two (technically four) functions for getting messages in SDL: SDL_PollEvent and SDL_WaitEvent. SDL_PollEvent() returns immediately if there are no events waiting, whereas SDL_WaitEvent() won't return until there is an event. We'll start by using SDL_WaitEvent(), as it matches the GetMessage function in windows. (SDL_PollEvent() is the counterpart for the windows function PeekMessage.)

So that our program doesn't quit instantly, we'll want to loop forever trying to get messages (or at least until we get a SDL_QUIT message). In that loop we want to call SDL_WaitEvent(), and pass it a pointer to an SDL_Event. We then want to handle the event by passing it to our HandleEvent function, and break out of the loop if it returns true.

for(;;)
{
    SDL_Event Event;
    SDL_WaitEvent(&Event);
    if (HandleEvent(&Event))
    {
        break;
    }
}

If we now run our program, we can see our window. If we resize it, we can see the resize events in the console. If we close the window, it closes, and our program quits:
Our Window!
Our game is clearly almost done! There's one small thing you might notice: our window still shows whatever was underneath it. This is because we're not rendering anything, and that's our next challange.

Rendering and Redrawing

To draw render things in SDL, we need an SDL_Renderer. (Note that this is not actually true: you can render with OpenGL instead.) The SDL_Renderer handles all of the actual drawing to the screen. Behind the scenes, there are acutally several different backends for SDL_Renderer, so that it will always run well on whatever hardware/software the user has. To create a renderer, we use the SDL_CreateRenderer() function, which renders a pointer to an SDL_Renderer.

// Create a "Renderer" for our window.
SDL_Renderer *Renderer = SDL_CreateRenderer(Window,
                                            -1,
                                            0);
The three arguments SDL_CreateRenderer() expects are our SDL_Window pointer, the index of the underlying driver (or -1 to autodetect) and some SDL_RendererFlags. Since SDL_CreateRenderer() needs our SDL_Window pointer, we probably should make sure that SDL_CreateWindow succeded and that we actually have a window, so let's wrap the rest of our code (up until we call SDL_Quit() inside an if (Window) block. Similarly, let's check that the SDL_Renderer was created successfully and put the rest of our code in an if (Renderer) block as well. At some point we'll want an else statement to handle the error, but we don't really have anything to do to fix the problem yet.

So how do we know when we need to draw the screen. Most games just redraw the screen as often as possible, but that would be a waste for other applications, so there has to be a way for an application to find out when it needs to redraw itself. So there is, SDL_WINDOWEVENT_EXPOSED. Let's add a new case to our inner switch statement:

case SDL_WINDOWEVENT_EXPOSED:
{
} break;

Like with the windows code in the stream, let's alternate rendering black and white so that we can see more easily when our window is being re-rendered. We'll use a static variable to track which colour we're trying to render, and use SDL_SetRenderDrawColor() to set the colour our SDL_Renderer will use. The first thing we notice, is that SDL_SetRenderDrawColor needs access to our SDL_Renderer, and we can't access our Renderer variable from within this function. We could make Renderer global, but that would come back to bite us if we ever wanted to add another window with its own renderer. Fortunately, there is an SDL_GetRenderer() function. Unfortunately, it needs a pointer to our SDL_Window, which we can't get. If we look up SDL_WindowEvent, we see that it has a windowID member. That's almost what we want, and we can use the SDL_GetWindowFromID() function to get it.

SDL_Window *Window = SDL_GetWindowFromID(Event->window.windowID);
SDL_Renderer *Renderer = SDL_GetRenderer(Window);
static bool IsWhite = true;
if (IsWhite == true)
{
    SDL_SetRenderDrawColor(Renderer, 255, 255, 255, 255);
    IsWhite = false;
}
else
{
    SDL_SetRenderDrawColor(Renderer, 0, 0, 0, 255);
    IsWhite = true;
}
The other parameters to SDL_SetRenderDrawColor() form the colour we're setting. They're all 8-bit unsigned integers, one each for red, green, blue and alpha. We'll no doubt look into how these colours work later on, but the basic gist is that every colour is a mix of red, green and blue and these integers represent the intensities of each of those components, ranging from none (0) to complete (255). The alpha component is used for semitransparent objects, with 0 being invisible and 255 being opaque. White is (255, 255, 255) and black is (0,0,0).

Finally, we actually want to render something. Unlike windows, SDL doesn't tell us what part of our window needs re-rendering, so we'll just clear the whole window with the SDL_RenderClear() function, which simply takes our SDL_Renderer pointer as a parameter and clears the window to the draw colour we set previously. To make the things we've rendered actually appear on the screen, we need to call the SDL_RenderPresent() function.

SDL_RenderClear(Renderer);
SDL_RenderPresent(Renderer);
When we now compile and run our code, we get a nice window which is black or white. We notice it change colour (usually flickering quickly) if we resize it, drag it off the edge of the screen, or drag another window over it. Neat.

Be warned! If your window doesn't change colour when you drag it off the edge of the screen or drag another window over it, you may have a compositing window manager. These window managers keep a copy of what your window should look like, so it doesn't need to redraw itself so often. You can often disable compositing in your window manager's settings (it's often called something like "Desktop Effects"). If you're using KDE, pressing Alt+Shift+F12 will toggle compositing on or off.

End of Lesson!

Congratulations, you've reached the end of Chapter 2.

If you've bought Handmade Hero, the source for the Linux version can be downloaded here.


<-- Chapter 1 | Back to Index | Chapter 3 -->