DEV Community

loading...
Cover image for Using SDL2: Opening a Window

Using SDL2: Opening a Window

Noah11012
Updated on ・7 min read

In this part, we'll get a window up and running. First, create a new C++ file and call it whatever you want. It doesn't matter, but I'll call mine main.cpp.

Copy and paste the following code:

#include <SDL2/SDL.h>
#include <iostream>

int main()
{
    if(SDL_Init(SDL_INIT_VIDEO) < 0)
    {
        std::cout << "Failed to initialize the SDL2 library\n";
        return -1;
    }

    SDL_Window *window = SDL_CreateWindow("SDL2 Window",
                                          SDL_WINDOWPOS_CENTERED,
                                          SDL_WINDOWPOS_CENTERED,
                                          680, 480,
                                          0);

    if(!window)
    {
        std::cout << "Failed to create window\n";
        return -1;
    }

    SDL_Surface *window_surface = SDL_GetWindowSurface(window);

    if(!window_surface)
    {
        std::cout << "Failed to get the surface from the window\n";
        return -1;
    }

    SDL_UpdateWindowSurface(window);

    SDL_Delay(5000);
}
Enter fullscreen mode Exit fullscreen mode

If you have your IDE like Visual Studio correctly configured then press the "run" button. If you use the terminal like me, you can use g++ or clang++.

g++ -o sdl2-program main.cpp `sdl2-config --cflags --libs

clang++ -o sdl2-program main.cpp `sdl2-config --cflags --libs

Compile and run it. You will see a black window pop up that is unresponsive and will close after 5000 milliseconds or 5 seconds.

Now let's walk through the code to see what's happening.

if(SDL_Init(SDL_INIT_VIDEO) < 0)

SDL2 needs to be initialized first before we can use any functions that it provides. SDL2 is separated into sections called subsystems. Right now, we only want video support. If you want more than one subsystem initialized, OR the different flags together:

SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO)

SDL_Init() returns 0 on success and a negative number on failure. We check to see if this function fails. If so, print to the console letting the user know.

{
        std::cout << "Failed to initialize the SDL2 library\n";
        std::cout << "SDL2 Error: " << SDL_GetError() << "\n";
        return -1;
}
Enter fullscreen mode Exit fullscreen mode

SDL_GetError() gets the error message that a failed SDL function sets.

Next, we create the actual window.

SDL_Window *window = SDL_CreateWindow("SDL2 Window",
                                          SDL_WINDOWPOS_CENTERED,
                                          SDL_WINDOWPOS_CENTERED,
                                          680, 480,
                                          0);
Enter fullscreen mode Exit fullscreen mode

SDL_CreateWindow() takes several arguments:

  1. The title of the window
  2. the x position of the window
  3. the y position of the window
  4. width of the window
  5. height of the window

It returns a pointer to an SDL_Window on success or NULL on failure.

We check to see if the window failed and if so, let the user know:

if(!window)
{
    std::cout << "Failed to create window\n";
    std::cout << "SDL2 Error: " << SDL_GetError() << "\n";
    return -1;
}
Enter fullscreen mode Exit fullscreen mode

Now we get the window's surface. An SDL_Surface is a struct that contains a collection of pixels for software blitting. We'll get more into what blitting is later when we get an image on the screen, but suffice it to say, blitting is the process of copying an image from one surface to another.

SDL_Surface *window_surface = SDL_GetWindowSurface(window);

if(!window_surface)
{
    std::cout << "Failed to get the surface from the window\n";
    std::cout << "SDL2 Error: " << SDL_GetError() << "\n";
    return -1;
}
Enter fullscreen mode Exit fullscreen mode

You know the drill by now.

Finally, we update the window's surface by calling SDL_UpdateWindowSurface(). The function takes a pointer to an SDL_Window and returns 0 on success and a negative number on failure. Here we won't check if something goes wrong.

We then call the SDL_Delay() function. If we didn't have it we wouldn't see the window because it would be created and quickly close. We pause for 5 seconds to see if everything works.

Okay, Noah, this is dandy and all but this isn't very functional. How do we keep the window open until the user closes it?

Good question. Before I can answer that question I'll have to explain what an event is.

An event is something that allows us to know if the user did something. Did the user move their mouse, typed on the keyword, moved the joystick on a controller, etc.

The way events get to us is in the form of a queue. A queue is a data structure that follows the First In, First Out or FIFO principle. Think of a line of people. The first person who enters the line is the first to exit the line. The same with a queue of events. The event that is first triggered is the first to be processed.

Events in SDL2 are stored in a union called SDL_Event. If you don't know what a union is it's like a struct except only one member is stored with data at a time.

We can poll for events with SDL_PollEvent(). Polling means to retrieve an event from the queue. SDL_PollEvent() takes a pointer to an SDL_Event and returns 1 if there is a pending event and 0 if no events are in the queue.

We can use a while loop to continuously check if an event needs to be processed.

int main()
{
    if(SDL_Init(SDL_INIT_VIDEO) < 0)
    {
        std::cout << "Failed to initialize the SDL2 library\n";
        std::cout << "SDL2 Error: " << SDL_GetError() << "\n";
        return -1;
    }

    SDL_Window *window = SDL_CreateWindow("SDL2 Window",
                                          SDL_WINDOWPOS_CENTERED,
                                          SDL_WINDOWPOS_CENTERED,
                                          680, 480,
                                          0);

    if(!window)
    {
        std::cout << "Failed to create window\n";
        std::cout << "SDL2 Error: " << SDL_GetError() << "\n";
        return -1;
    }

    SDL_Surface *window_surface = SDL_GetWindowSurface(window);

    if(!window_surface)
    {
        std::cout << "Failed to get the surface from the window\n";
        std::cout << "SDL2 Error: " << SDL_GetError() << "\n";
        return -1;
    }

    SDL_Event e;
    while(SDL_PollEvent(&e) > 0)
    {
        SDL_UpdateWindowSurface(window);
    }
}
Enter fullscreen mode Exit fullscreen mode

Compile and run.

And now it quickly pops up and vanishes.

Hey! I thought we would have a window stay up on screen! You lied to us!

Woah! Before you start pelting me with tomatoes, I need you to remember something: SDL_PollEvent() returns 0 if no events are in the queue. A 0 in a while loop condition means to stop executing. This just means no events need to be processed. There is an easy fix for this: surround the event processing while loop with another while loop:

bool keep_window_open = true;
while(keep_window_open)
{
    SDL_Event e;
    while(SDL_PollEvent(&e) > 0)
    {
        SDL_UpdateWindowSurface(window);
    }
}
Enter fullscreen mode Exit fullscreen mode

Compile and run.

Okay, great. Now I can't close the window. How do we fix this?

The reason why you can't close the window is that we haven't processed the window close event.

Why doesn't SDL2 does this by default?

Well, think of this scenario: Let's say you've created a game and you want it set up where if the user clicks the X button then a dialog will prompt them to save any progress they made before they exit the game. If SDL2 didn't give us this ability to process the window close event, then you couldn't implement such a feature.

I'll be using a switch instead of a chain of if/if else statements because it looks nicer especially when we start processing more events.

bool keep_window_open = true;
while(keep_window_open)
{
    SDL_Event e;
    while(SDL_PollEvent(&e) > 0)
    {
        switch(e.type)
        {
            case SDL_QUIT:
                keep_window_open = false;
                break;
        }

        SDL_UpdateWindowSurface(window);
    }
}
Enter fullscreen mode Exit fullscreen mode

In an SDL_Event union we have a type member. Its purpose is to store the type of event it currently holds.

Compile and run.

Now everything works as expected.

Before we end this part, let's get an image onto the screen. You need a bitmap image for this to work. Bitmap images have the extension .bmp. Download one or create one and then we can get started.

SDL_Surface *image = SDL_LoadBMP("image.bmp");

if(!image)
{
    std::cout << "Failed to load image\n";
    std::cout << "SDL2 Error: " << SDL_GetError() << "\n";
    return -1;
}

bool keep_window_open = true;
while(keep_window_open)
{
    SDL_Event e;
    while(SDL_PollEvent(&e) > 0)
    {
        switch(e.type)
        {
            case SDL_QUIT:
                keep_window_open = false;
                break;
        }

        SDL_BlitSurface(image, NULL, window_surface, NULL);
        SDL_UpdateWindowSurface(window);
    }
}
Enter fullscreen mode Exit fullscreen mode

We load a bitmap image using SDL_LoadBMP() and check to see if it fails and if successful, blit it onto the screen using SDL_BlitSurface(). This function takes four arguments:

  1. The source surface from which we want to copy the pixels from
  2. A pointer to an SDL_Rect for the source surface
  3. The destination surface to where we want to copy the pixels to
  4. A pointer to an SDL_Rect for the destination surface

What is an SDL_Rect? It's a struct holding the position and dimensions of a rectangle.

Why do we need this exactly?

If we only wanted to copy a part of the image, we can use an SDL_Rect and position it over the source surface and extract the portion we want. If we want to position and scale the source surface we can use an SDL_Rect to overlay on top of the destination surface to where we want the surface to be copied to.

For now, pass in NULL for both the source and destination SDL_Rects because we want the whole image and we want to stretch the image to fit the entire window.

I made my bitmap in GIMP and made it with the same dimensions as the window and made the background black so that it seems like that there is text on the screen. If you have an image with different dimensions it might be blurry/squished.

SDL2 uses something called double buffering. We have two buffers available to us. The first one is called the back buffer and is our canvas: we can draw anything we want on it but until we call SDL_UpdateWindowSurface() it won't be displayed to the world. The front buffer is what is currently shown. When you call SDL_UpdateWindowSurface() it takes the back and front buffers and switches their places. This makes the front the back and the back the front.

Earlier I promised I would get more into what blitting was. Blitting is a term that means bit-boundary block transfer.

Well, gee, Noah. That's really helpful.

I know, but once you know what it is then it will hopefully make more sense.

Bit-boundary block transfer or blitting is the procedure of copying blocks of memory typically pixels from one source to another.

When we loaded the bitmap and displayed it onto the window, we were blitting the image's surface onto the window's surface. Remember that a surface is a collection of pixels. We were effectively copying the pixels from the image and pasting it onto the window.

Before we wrap up, I have one more thing to share. While these tutorials will hopefully be helpful, it is important to have a good documentation to refer.

The official one is over at the SDL2 Wiki.

What's next for us?

Next, we'll learn how to use SDL_Rects because they are important in SDL2 development and we will use them to move an image across the window.

All the source code for this series can be viewed and downloaded at Github:

https://github.com/Noah11012/sdl2-tutorial-code

Discussion (0)