We’ve learned how to create interactive applications with a semi-graphical interface using ncurses in this course, but we can use some of the same ideas to create real graphical output using SDL2.
This reading will introduce some of the basic components of SDL2 and connect them up with analogous concepts from ncurses.
If you’re using MathLAN, SDL2 will already be installed for you. However, if you’re using a personal machine you’ll need to install SDL2 yourself. The SDL library is designed to work on Windows, macOS, and Linux, but setup instructions will vary across platforms.
For example, on a Debian-based Linux system (like the MathLAN computers) you would run this command to install SDL2:
$ sudo apt install libsdl2-dev libsdl2-image-dev
On macOS, this command should install SDL2 if you have set up the Homebrew package manager:
$ brew install sdl2 sdl2_image
You won’t need to run any of these steps if you work on MathLAN, but you’re welcome to set up SDL2 on your own machine if you choose. You can find information on installing SDL2 on different platforms on the [SDL2 Installation Wiki Page](https://wiki.libsdl.org/SDL2/Installation}{:target=”_blank”}.
To use ncurses, we add the line #include <ncurses.h> to our source files.
That brings in the declarations for all the ncurses functions, types, and constants so we can use them in our code.
Later, when we compile our programs, we have to pass -lncurses to the compiler to link the ncurses library with our programs (which brings in the function definitions).
There are similar requirements for SDL2, but we’ll have to use a new tool called pkg-config (pronounced “package config”) as part of the process.
The pkg-config program keeps track of libraries installed on our system, and it can help us by generating the compiler options we need to pass in to clang to use those libraries.
As an example, the line below shows what pkg-config produces when we ask it to generate options for using SDL with image support on MathLAN:
$ pkg-config sdl2 SDL2_image --cflags --libs
-I/usr/include/SDL2 -D_REENTRANT -I/usr/include/libpng16 -I/usr/include/x86_64-linux-gnu -lSDL2_image -lSDL2
You might be tempted to copy and paste those options into a Makefile, but there’s a better and more portable way use pkg-config.
We can ask make to run pkg-config for us by including this at the top of our Makefile:
CC := clang
CFLAGS := -g $(shell pkg-config sdl2 SDL2_image --cflags)
LDFLAGS := $(shell pkg-config sdl2 SDL2_image --libs)
Then later in the Makefile, we can compile a program that uses SDL2 with this rule:
demo: demo.c
$(CC) $(CFLAGS) -o demo demo.c $(LDFLAGS)
Using pkg-config this way makes our code more portable.
As long as the current machine has SDL2 installed, make will be able to use pkg-config to get the right compiler options to use it.
Now that we have the pieces in place to build programs with SDL2, let’s look at a demo program that uses SDL2 with a game loop.
This reading will discuss fragments of a larger demo program, but you can see the whole program over at https://git.cs.grinnell.edu/csc161-s25/SDL2-demo.
We’ll start with the example in demo1.c, and move on to demo2.c after we’ve covered the basics.
This demo will use SDL2, so we have to include this line near the top of the file:
#include <SDL.h>
You’ll notice we’re using many different functions and constants from the SDL library in this demo. You can find descriptions of each of these in the SDL 2.0 API. Most of the parts we’re using will come from the initialization and shutdown; display and window management; and the surfce creation and simple drawing categories.
Next, let’s look at setting up SDL2.
As with ncurses, there are a few steps we’ll need to take to initialize SDL2.
The first step is to initialize the library itself.
We’ll do this by calling the SDL_Init function, and we’ll ask SDL to initialize its support for video (graphics).
We’ll run this at the top of the main function:
// Initialize SDL
if (SDL_Init(SDL_INIT_VIDEO) != 0) {
// Something went wrong. Log an error and exit.
fprintf(stderr, "Failed to initialize SDL: %s\n", SDL_GetError());
exit(EXIT_FAILURE);
}
Once SDL itself is initialized, we can create a window.
We’ll need to #define some constants to set the window size above the main function:
#define SCREEN_WIDTH 640
#define SCREEN_HEIGHT 480
Once that’s done, we can add this code to main:
// Create an SDL window (centered on screen, 640x480)
SDL_Window* window = SDL_CreateWindow("SDL2 Demo",
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
SCREEN_WIDTH, SCREEN_HEIGHT,
SDL_WINDOW_SHOWN);
if (window == NULL) {
// Something went wrong. Log an error, shut down SDL, and then exit
fprintf(stderr, "Failed to create SDL window: %s\n", SDL_GetError());
SDL_Quit();
exit(EXIT_FAILURE);
}
This code will try to create a window with the title “SDL2 Demo”. The window is centered horizontally and vertically, and uses the sizes we defined. We also ask SDL to make the window visible.
If anything goes wrong when creating the window, the error handling code will print a message, clean up, and exit.
Notice that we have to call SDL_Quit() in this function to clean up the SDL library itself.
Now that we have a window created and visible, we’ll need to get a surface from the window. SDL uses surfaces to represent image data stored in the computer’s main memory. In this case we’re going to ask for the surface displayed in the window we just created:
// Get the surface associated with our new window
SDL_Surface* screen = SDL_GetWindowSurface(window);
if (screen == NULL) {
// Something went wrong. Log and error, shut down SDL, remove the window, and exit
fprintf(stderr, "Failed to get surface from window: %s\n", SDL_GetError());
SDL_DestroyWindow(window);
SDL_Quit();
exit(EXIT_FAILURE);
}
As before, most of the code here is related to error handling. Notice that now the cleanup process also destroys the window we created earlier. The further you get into the initialization process, the more there is to clean up.
That’s all the initialization code we’ll cover for now. There will be more
Once we’ve initialized SDL we can write a game loop, much like we did with ncurses.
The game loop structure will be the same, but we’ll use different functions for getting input and updating the display.
Here’s a complete game loop in SDL that we’ll discuss step-by-step:
// Start the game loop
bool running = true;
while (running) {
// Handle events
SDL_Event event;
while (SDL_PollEvent(&event)) {
if (event.type == SDL_QUIT) {
// When the user clicks the "X" in the window we get an SDL_QUIT event
running = false;
}
}
// Fill the screen with white pixels
SDL_Rect screen_rect = {.x = 0, .y = 0, .w = SCREEN_WIDTH, .h = SCREEN_HEIGHT};
SDL_FillRect(screen, &screen_rect, SDL_MapRGB(screen->format, 255, 255, 255));
// Update the display
SDL_UpdateWindowSurface(window);
// Pause before drawing the next frame
SDL_Delay(1000 / FRAME_RATE);
}
The first part of the game loop handles input events.
We did this with the getch function in ncurses, but we’ll use SDL_PolLEvent in SDL.
This function fills in the event variable (a structure) with information about each event in the queue.
We call SDL_PollEvent repeatedly until it returns false, meaning it has no more events for us to process.
The only event we handle in this example is SDL_QUIT, which happens when the user closes the program’s window.
When that happens we’ll set running to false.
Next, we’ll see some code to update the window’s content.
The SDL_FillRect function takes in a surface, an SDL_Rect* that describes the area we’d like to fill with color, and a value that represents the color we want to fill the rectangle with.
The first step is to describe the rectangle we want to fill, which in this case is the full screen. Here’s the relevant line again for reference:
SDL_Rect screen_rect = {.x = 0, .y = 0, .w = SCREEN_WIDTH, .h = SCREEN_HEIGHT};
This code uses a feature of C called designated initializers, which are helpful for filling in structures.
Inside the curly braces we can write .fieldname = ... to specify a value for each of the fields in the struct.
Any fields we leave out will be initialized to zero.
You’ll find this shorthand useful for SDL, since many of SDL’s functions take structs or pointers to structs as parameters.
Next, we use the SDL_FillRect function to fill in that rectangle in the window’s surface.
The tricky detail with this function is that we have to encode the color we want in a format that the window is using.
Different computers, operating systems, and even image formats can use very different ways of representing color, and we have to convert between them when we use SDL.
Luckily, calling the SDL_MapRGB function does this for us.
We pass in the desired color format (screen->format) and the red, green, and blue color values (0–255), and the function returns a color in the appropriate representation.
Finally, we can ask SDL to update the window content by calling SDL_UpdateWindowSurface at the bottom of the game loop.
Just like with ncurses, we often want our graphics to display a predictable frame rate instead of just generating updates as fast as possible.
We can do this by pausing at the end of each frame using the SDL_Delay function:
// Pause before drawing the next frame
SDL_Delay(1000 / FRAME_RATE);
This code relies on the FRAME_RATE constant, which is set to 60 at the top of demo1.c.
Our program will leave the game loop when the user closes the window. Before we exit, we should shut down SDL and free any resources we used during the program’s run. We’ll do this with the same functions we called in the error handling code above:
// Shut down and exit
SDL_DestroyWindow(window);
SDL_Quit();
If you haven’t already done so, check out and run the first demo from https://git.cs.grinnell.edu/csc161-s25/SDL2-demo.
After you run make, run ./demo1 to start the program.
The program will occupy your terminal as usual, but you should see a new window appear after a second or two.
You should also be able to quit the program by clicking the “X” in the window’s corner.
You should be able to run the demo on MathLAN. If you want to run the demo on your own computer you are welcome to do that as well, but you’ll need to install SDL first. Follow the installation instructions linked near the beginning of this reading to get started.
The second demo program contains all the elements from the first demo, along with some interesting additions:
The code initializes the SDL_image library, which we can use to load image files.
The code loads an image file (the SDL logo).
The game loop handles arrow keys, which is uses to update the logo_x and logo_y variables.
The game loop also displays the logo at the coordinates stored in logo_x and logo_y.
You should be able to make sense of the new additions by reading the code, comments, and potentially the documentation from the SDL project.
Note that the term “blit” is a verb used in computer graphics to describe the process of copying image data from one image to another.
It is common to copy just part of a source surface, and to copy it to just part of a destination surface, which is why SDL_BlitScaled takes in four parameters: source surface, source rect, destination surface, and destination rect.
We’ll spend more time with the image handling code in lab. For now, try making these modifications to the second demo to practice working with SDL code:
Change the background color from white to something else.
Move the logo image faster or slower with each key press.
Add code to prevent the logo from moving outside the screen area.
Change the size of the logo displayed on the screen.
Display the left half of the logo image instead of the full image (which is 640x322 pixels).