Getting Started with SDL and C

SDL REctanglesThis is the 3rd in a series of tutorials about programming with the SDL library in C. This tutorial is the one that gets you started. The two previous tutorials showed you how to install SDL2 on Linux and in Visual Studio (on Windows).

All source code published here is portable; I put in code so that it will compile immediately in MSVC (the compiler that comes with Visual Studio) and Clang for Linux using Visual Studio Code (aka VS Code). You can of course use Visual Studio Code on Windows but I’ve not setup SDL with that.

Also GCC should do as well as Clang. The only difference is how you invoke it and that affects a JSON file Tasks.json. I’ve included all four JSON files in the zip file for this tutorial. You only need those JSON files if you are using VS Code.

If you need to setup SDL2, follow the appropriate tutorial below.

Note I use the term SDL and SDL2 interchangeably. Yes it’s kind of sloppy. The library is known as SDL but the current version is SDL2 and it is significantly different from SDL1. That was popular about ten years ago but now we use SDL2.

Anatomy of a SDL2 Program

Here’s a listing of the sdltest.c program. It’s in the sdltest.zip on GitHub if you want to download that, link near the end of this article.

// sdltest.c
#ifdef _WIN32a
#include "SDL.h"   /* All SDL App's need this */
#else
#include<linux/time.h>
#define __timespec_defined 1
#define __timeval_defined 1
#define __itimerspec_defined 1
#include "SDL2/SDL.h"
#endif
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include "hr_time.h"


#define QUITKEY SDLK_ESCAPE
#define WIDTH 1024
#define HEIGHT 768

SDL_Window* screen = NULL;
SDL_Renderer* renderer;
SDL_Event event;
SDL_Rect source, destination, dst;

int errorCount = 0;
int keypressed;
int rectCount = 0;
stopWatch s;

/* returns a number between 1 and max */
int Random(int max) {
	return (rand() % max) + 1;
}

void LogError(char* msg) {
	printf("%s\n", msg);
	errorCount++;
}

/* Sets Window caption according to state - eg in debug mode or showing fps */
void SetCaption(char* msg) {
	SDL_SetWindowTitle(screen, msg);
}

/* Initialize all setup, set screen mode, load images etc */
void InitSetup() {
	srand((int)time(NULL));
	SDL_Init(SDL_INIT_EVERYTHING);
	SDL_CreateWindowAndRenderer(WIDTH, HEIGHT, SDL_WINDOW_SHOWN, &screen, &renderer);
	if (!screen) {
		LogError("InitSetup failed to create window");
	}
	SetCaption("Example One");
}

/* Cleans up after game over */
void FinishOff() {
	SDL_DestroyRenderer(renderer);
	SDL_DestroyWindow(screen);
	//Quit SDL
	SDL_Quit();
	exit(0);
}

/* read a character */
char getaChar() {
	int result = -1;

	while (SDL_PollEvent(&event)) {
		if (event.type == SDL_KEYDOWN)
		{
			result = event.key.keysym.sym;
			break;
		}
	}
	return result;
}

void DrawRandomRectangle() {
	char buff[20];
	SDL_Rect rect;
	SDL_SetRenderDrawColor(renderer, Random(256) - 1, Random(256) - 1, Random(256) - 1, 255);
	rect.h = 120;// Random(100) + 20;
	rect.w = 120;// Random(100) + 20;
	rect.y = Random(HEIGHT - rect.h - 1);
	rect.x = Random(WIDTH - rect.w - 1);
	SDL_RenderFillRect(renderer, &rect);

	rectCount++;
	if (rectCount % 100000 == 0) {
		SDL_RenderPresent(renderer);
		stopTimer(&s);
#ifdef _WIN32
		sprintf_s(buff, sizeof(buff), "%10.6f", getElapsedTime(&s));
#else
		snprintf(buff, sizeof(buff),"%10.6f",diff(&s));
#endif
		SetCaption(buff);
		startTimer(&s);
	}
}

/* main game loop. Handles demo mode, high score and game play */
void GameLoop() {
	int gameRunning = 1;
	startTimer(&s);
	while (gameRunning)
	{
		DrawRandomRectangle();

		while (SDL_PollEvent(&event)) {
			switch (event.type) {
			case SDL_KEYDOWN:
				keypressed = event.key.keysym.sym;
				if (keypressed == QUITKEY)
				{
					gameRunning = 0;
					break;
				}

				break;
			case SDL_QUIT: /* if mouse click to close window */
			{
				gameRunning = 0;
				break;
			}
			case SDL_KEYUP: {
				break;
			}
			} /* switch */

		} /* while SDL_PollEvent */
	}
}

int main(int argc, char* args[])
{
	InitSetup();
	GameLoop();
	FinishOff();
	return 0;
}

Starting at the end, the function InitSetup() is called to create a Window and renderer. The Window is needed for all SDL functions to wok into and the renderer is a struct that lets functions work with this window.  This also initialises the Random Number Generator srand() passing in a time value and sets the Window caption with a call to SetCaption().

Next is the GameLoop(). All games have one of these.  It’s just a while loop that continues as long as the variable gameRunning is non-zero.  Press the escape key to exit. Inside the game loop, the keyboard is scanned for key presses and if a key is pressed down and that key happens to be the QUITKEY (defined as SDLKESCAPE) gameRunning is set to 0 and the game loop finishes, and FinishOff() is called to tidy up by closing the SDL system down, that was setup in InitSetup.

Also inside the GameLoop() function is a call to DrawRandomRectangle(). This draws a rectangle that is 120 x `120 but you can uncomment the Random(100)  call to have it draw different sized rectangles.  This does a call to SDL_SetRenderDrawColor() to set a random colour then picks random coordinates in the Window and calls SDL_RenderFillRect() to draw a filled in rectangle using the coordinates and width/height in the rect rectangle.

After drawing 100,000 rectangles, it draws the elapsed time which is how long it took to draw all 100,000 rectangles) into a text buffer and puts this in the Windows caption. It then calls SDL_RenderPresent() to toggle the display video RAM.

What is Video RAM?

This is RAM contained inside the graphic’s card in your computer. It is very fast at displaying anything in this video RAM, on the screen. To prevent it flickering or tearing, the video RAM (called hereafter VRAM) is split into two. It is big enough to hold two screens. One is off-screen and the other is displayed.

When you call functions like SDL_RenderFillRect() this always outputs into the VRAM that is off-screen. When you call SDL_RenderPresent() the VRAMs are toggled. What was off-screen is now displayed and the one that was onscreen is now off-screen. This toggling can happen as fast as 60 times per second but here it is only happening after 100,000 rectangles are drawn. This is roughly once every second on my PC.

Notes

There are two places where things vary between the Linux and Windows C compilers. I use a compile directive _WIN32. This code below is compiled according to the platform that you compiled it on.

#ifdef _WIN32
		sprintf_s(buff, sizeof(buff), "%10.6f", getElapsedTime(&s));
#else
		snprintf(buff, sizeof(buff),"%10.6f",diff(&s));
#endif
		SetCaption(buff);
		startTimer(&s);

The _WIN32 is a compiler directive that is true on Windows PCs so the sprintf_s function call is compiled. On non-WIndows PCs, the snprintf function call is compiled instead. You’ll also see this #ifdef used at the top of the file where the timing code varies and also the include path to SDL.h as well.

The timing method varies between Windows and Linux. The main files hr_time.h/.c in the folder are Windows code but the Linux folder contains the Linux equivalents. So if you are compiling on Linux make sure you copy the two files from the Linux folder into the folder above so they are compiled.

Compiling for Linux

I used VS Code and clang to compile the program. The project file included has the .sln and .vcxproj files but these are for Windows only. To compile in Linux you need to setup a folder in VS Code and copy the .vscode folder into it. Note the .at the start of .vscode means the folder will be invisible in Linux but you can toggle a flag to see invisible folders and files in the Files utility.

Code on GitHub

The file sdltest.zip is in the LearnCCode repository on GitHub.

(Visited 632 times, 7 visits today)