SDL2 Cheat Sheet
Overview
SDL (Simple DirectMedia Layer) is a cross-platform development library designed to provide low-level access to audio, keyboard, mouse, joystick, and graphics hardware via OpenGL, Vulkan, Metal, and Direct3D. SDL 2 is written in C and works natively with C++, and has bindings for many other languages including Python (pygame), Rust, Go, and C#. It is widely used for game development, emulators, media players, and any application requiring direct hardware access.
SDL2 handles the platform-specific details of creating windows, processing input events, playing audio, and setting up rendering contexts, while remaining thin enough to give developers fine-grained control. Major games and engines built on SDL include Valve’s Source engine ports, Unreal Engine (for Linux/macOS), countless indie games, and emulators like RetroArch. The library supports Windows, macOS, Linux, iOS, Android, and several other platforms. Companion libraries SDL_image, SDL_ttf, SDL_mixer, and SDL_net extend its capabilities.
Installation
# macOS
brew install sdl2 sdl2_image sdl2_ttf sdl2_mixer sdl2_net
# Ubuntu/Debian
sudo apt install libsdl2-dev libsdl2-image-dev libsdl2-ttf-dev \
libsdl2-mixer-dev libsdl2-net-dev
# Fedora
sudo dnf install SDL2-devel SDL2_image-devel SDL2_ttf-devel \
SDL2_mixer-devel SDL2_net-devel
# Arch Linux
sudo pacman -S sdl2 sdl2_image sdl2_ttf sdl2_mixer sdl2_net
# Windows (vcpkg)
vcpkg install sdl2 sdl2-image sdl2-ttf sdl2-mixer
# Verify
sdl2-config --version
sdl2-config --cflags --libs
Basic Window and Renderer
// main.c
#include <SDL2/SDL.h>
#include <stdio.h>
#include <stdbool.h>
#define SCREEN_WIDTH 800
#define SCREEN_HEIGHT 600
int main(int argc, char* argv[])
{
// Initialize SDL
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) {
printf("SDL Init failed: %s\n", SDL_GetError());
return 1;
}
// Create window
SDL_Window* window = SDL_CreateWindow(
"My SDL2 Game",
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
SCREEN_WIDTH, SCREEN_HEIGHT,
SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE
);
if (!window) {
printf("Window creation failed: %s\n", SDL_GetError());
SDL_Quit();
return 1;
}
// Create renderer
SDL_Renderer* renderer = SDL_CreateRenderer(
window, -1,
SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC
);
if (!renderer) {
printf("Renderer creation failed: %s\n", SDL_GetError());
SDL_DestroyWindow(window);
SDL_Quit();
return 1;
}
// Game loop
bool running = true;
SDL_Event event;
Uint32 lastTime = SDL_GetTicks();
while (running) {
// Delta time
Uint32 currentTime = SDL_GetTicks();
float deltaTime = (currentTime - lastTime) / 1000.0f;
lastTime = currentTime;
// Event handling
while (SDL_PollEvent(&event)) {
if (event.type == SDL_QUIT) {
running = false;
}
if (event.type == SDL_KEYDOWN) {
if (event.key.keysym.sym == SDLK_ESCAPE) {
running = false;
}
}
}
// Update game state
// (game logic here)
// Render
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderClear(renderer);
// Draw stuff here
SDL_RenderPresent(renderer);
}
// Cleanup
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}
Drawing Primitives
// Set draw color (RGBA)
SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255); // Red
// Draw rectangle
SDL_Rect rect = { 100, 100, 200, 150 };
SDL_RenderFillRect(renderer, &rect); // Filled
SDL_RenderDrawRect(renderer, &rect); // Outline
// Draw line
SDL_RenderDrawLine(renderer, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
// Draw point
SDL_RenderDrawPoint(renderer, 400, 300);
// Draw multiple lines
SDL_Point points[] = {
{ 100, 100 }, { 200, 50 }, { 300, 100 }, { 200, 150 }, { 100, 100 }
};
SDL_RenderDrawLines(renderer, points, 5);
// Set blend mode for transparency
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
SDL_SetRenderDrawColor(renderer, 0, 0, 255, 128); // Semi-transparent blue
SDL_RenderFillRect(renderer, &rect);
Textures and Images
#include <SDL2/SDL_image.h>
// Initialize SDL_image
IMG_Init(IMG_INIT_PNG | IMG_INIT_JPG);
// Load texture from file
SDL_Texture* texture = IMG_LoadTexture(renderer, "sprites/player.png");
if (!texture) {
printf("Texture load failed: %s\n", IMG_GetError());
}
// Get texture dimensions
int texW, texH;
SDL_QueryTexture(texture, NULL, NULL, &texW, &texH);
// Draw texture
SDL_Rect destRect = { x, y, texW, texH };
SDL_RenderCopy(renderer, texture, NULL, &destRect);
// Draw portion of texture (spritesheet)
SDL_Rect srcRect = { frameX * 32, frameY * 32, 32, 32 }; // Source region
SDL_Rect dstRect = { playerX, playerY, 64, 64 }; // Destination (scaled)
SDL_RenderCopy(renderer, spritesheet, &srcRect, &dstRect);
// Draw with rotation and flip
SDL_RenderCopyEx(renderer, texture, &srcRect, &dstRect,
angle, // Rotation in degrees
NULL, // Center of rotation (NULL = center)
SDL_FLIP_HORIZONTAL // Flip mode
);
// Set texture color/alpha modulation
SDL_SetTextureColorMod(texture, 255, 128, 128); // Tint
SDL_SetTextureAlphaMod(texture, 200); // Transparency
SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND);
// Create texture from surface
SDL_Surface* surface = SDL_CreateRGBSurface(0, 100, 100, 32, 0, 0, 0, 0);
SDL_FillRect(surface, NULL, SDL_MapRGB(surface->format, 255, 0, 0));
SDL_Texture* tex = SDL_CreateTextureFromSurface(renderer, surface);
SDL_FreeSurface(surface);
// Cleanup
SDL_DestroyTexture(texture);
IMG_Quit();
Input Handling
// Event-based input
SDL_Event event;
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_QUIT:
running = false;
break;
case SDL_KEYDOWN:
switch (event.key.keysym.sym) {
case SDLK_SPACE: jump(); break;
case SDLK_ESCAPE: running = false; break;
case SDLK_F11: toggleFullscreen(); break;
}
break;
case SDL_KEYUP:
if (event.key.keysym.sym == SDLK_SPACE)
stopJumping();
break;
case SDL_MOUSEBUTTONDOWN:
if (event.button.button == SDL_BUTTON_LEFT)
shoot(event.button.x, event.button.y);
break;
case SDL_MOUSEMOTION:
mouseX = event.motion.x;
mouseY = event.motion.y;
break;
case SDL_MOUSEWHEEL:
zoom += event.wheel.y;
break;
case SDL_WINDOWEVENT:
if (event.window.event == SDL_WINDOWEVENT_RESIZED) {
screenW = event.window.data1;
screenH = event.window.data2;
}
break;
}
}
// Polling-based keyboard state (for continuous movement)
const Uint8* keystate = SDL_GetKeyboardState(NULL);
if (keystate[SDL_SCANCODE_LEFT] || keystate[SDL_SCANCODE_A])
playerX -= speed * deltaTime;
if (keystate[SDL_SCANCODE_RIGHT] || keystate[SDL_SCANCODE_D])
playerX += speed * deltaTime;
if (keystate[SDL_SCANCODE_UP] || keystate[SDL_SCANCODE_W])
playerY -= speed * deltaTime;
if (keystate[SDL_SCANCODE_DOWN] || keystate[SDL_SCANCODE_S])
playerY += speed * deltaTime;
// Mouse state polling
int mx, my;
Uint32 buttons = SDL_GetMouseState(&mx, &my);
if (buttons & SDL_BUTTON_LMASK) {
// Left button held
}
Audio
#include <SDL2/SDL_mixer.h>
// Initialize audio
Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048);
// Load sound effect
Mix_Chunk* jumpSound = Mix_LoadWAV("sounds/jump.wav");
Mix_Chunk* explosionSound = Mix_LoadWAV("sounds/explosion.wav");
// Play sound effect
Mix_PlayChannel(-1, jumpSound, 0); // -1 = first free channel, 0 = no loop
Mix_PlayChannel(1, explosionSound, 0); // Play on channel 1
// Set volume (0-128)
Mix_VolumeChunk(jumpSound, 64); // Half volume
// Load and play music
Mix_Music* music = Mix_LoadMUS("music/background.mp3");
Mix_PlayMusic(music, -1); // -1 = loop forever
Mix_VolumeMusic(32); // Set music volume
// Music controls
Mix_PauseMusic();
Mix_ResumeMusic();
Mix_HaltMusic();
Mix_FadeOutMusic(2000); // Fade out over 2 seconds
// Cleanup
Mix_FreeChunk(jumpSound);
Mix_FreeChunk(explosionSound);
Mix_FreeMusic(music);
Mix_CloseAudio();
Text Rendering
#include <SDL2/SDL_ttf.h>
// Initialize
TTF_Init();
// Load font
TTF_Font* font = TTF_OpenFont("fonts/arial.ttf", 24);
TTF_Font* titleFont = TTF_OpenFont("fonts/bold.ttf", 48);
// Render text to surface then texture
SDL_Color white = { 255, 255, 255, 255 };
SDL_Surface* textSurface = TTF_RenderText_Blended(font, "Score: 100", white);
SDL_Texture* textTexture = SDL_CreateTextureFromSurface(renderer, textSurface);
int texW = textSurface->w;
int texH = textSurface->h;
SDL_FreeSurface(textSurface);
// Draw text
SDL_Rect textRect = { 10, 10, texW, texH };
SDL_RenderCopy(renderer, textTexture, NULL, &textRect);
// Render methods:
// TTF_RenderText_Solid() - Fast, no anti-aliasing
// TTF_RenderText_Shaded() - Anti-aliased with background color
// TTF_RenderText_Blended() - Anti-aliased with alpha (best quality)
// Cleanup
SDL_DestroyTexture(textTexture);
TTF_CloseFont(font);
TTF_Quit();
Gamepad/Joystick
// Initialize
SDL_Init(SDL_INIT_GAMECONTROLLER | SDL_INIT_JOYSTICK);
// Open first gamepad
SDL_GameController* controller = NULL;
for (int i = 0; i < SDL_NumJoysticks(); i++) {
if (SDL_IsGameController(i)) {
controller = SDL_GameControllerOpen(i);
printf("Controller: %s\n", SDL_GameControllerName(controller));
break;
}
}
// Read gamepad in update loop
if (controller) {
// Analog sticks (-32768 to 32767)
int leftX = SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_LEFTX);
int leftY = SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_LEFTY);
// Apply deadzone
float lx = (abs(leftX) > 8000) ? leftX / 32767.0f : 0;
float ly = (abs(leftY) > 8000) ? leftY / 32767.0f : 0;
// Buttons
if (SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_A))
jump();
if (SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_START))
pause();
// Triggers (0 to 32767)
int rightTrigger = SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_TRIGGERRIGHT);
}
// Cleanup
SDL_GameControllerClose(controller);
Configuration (Build)
# CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(mygame LANGUAGES C CXX)
set(CMAKE_C_STANDARD 11)
find_package(SDL2 REQUIRED)
find_package(SDL2_image REQUIRED)
find_package(SDL2_ttf REQUIRED)
find_package(SDL2_mixer REQUIRED)
add_executable(mygame
src/main.c
src/game.c
src/player.c
src/renderer.c
)
target_link_libraries(mygame PRIVATE
SDL2::SDL2
SDL2_image::SDL2_image
SDL2_ttf::SDL2_ttf
SDL2_mixer::SDL2_mixer
)
# Build with CMake
mkdir build && cd build
cmake ..
cmake --build .
# Build with gcc directly
gcc -o mygame main.c $(sdl2-config --cflags --libs) -lSDL2_image -lSDL2_ttf -lSDL2_mixer -lm
# Build with pkg-config
gcc -o mygame main.c $(pkg-config --cflags --libs sdl2 SDL2_image SDL2_ttf SDL2_mixer)
Advanced Usage
// Render to texture (framebuffer)
SDL_Texture* target = SDL_CreateTexture(renderer,
SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, 256, 256);
SDL_SetRenderTarget(renderer, target);
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);
SDL_RenderClear(renderer);
// Draw to texture...
SDL_SetRenderTarget(renderer, NULL); // Reset to screen
SDL_RenderCopy(renderer, target, NULL, &destRect);
// Fullscreen toggle
void toggleFullscreen(SDL_Window* window) {
Uint32 flags = SDL_GetWindowFlags(window);
if (flags & SDL_WINDOW_FULLSCREEN_DESKTOP) {
SDL_SetWindowFullscreen(window, 0);
} else {
SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN_DESKTOP);
}
}
// Pixel manipulation
SDL_Surface* surface = SDL_CreateRGBSurface(0, 256, 256, 32,
0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000);
SDL_LockSurface(surface);
Uint32* pixels = (Uint32*)surface->pixels;
for (int y = 0; y < 256; y++) {
for (int x = 0; x < 256; x++) {
pixels[y * 256 + x] = SDL_MapRGBA(surface->format, x, y, 128, 255);
}
}
SDL_UnlockSurface(surface);
// High-performance timing
Uint64 perfFreq = SDL_GetPerformanceFrequency();
Uint64 startTime = SDL_GetPerformanceCounter();
// ... work ...
Uint64 endTime = SDL_GetPerformanceCounter();
double elapsed = (double)(endTime - startTime) / (double)perfFreq;
// Clipboard
SDL_SetClipboardText("Copied text");
char* text = SDL_GetClipboardText();
SDL_free(text);
Troubleshooting
| Issue | Solution |
|---|---|
| Black window / no rendering | Check SDL_RenderPresent() called; verify renderer created |
| ”SDL_Init failed” | Check SDL2 installed; verify shared libraries in path |
| Texture loading fails | Check file path; ensure SDL_image initialized with correct formats |
| No audio output | Verify Mix_OpenAudio() succeeded; check audio device availability |
| Tearing / stuttering | Enable VSync: SDL_RENDERER_PRESENTVSYNC; use delta time |
| High CPU usage | Use SDL_Delay() or VSync to cap frame rate |
| Controller not detected | Call SDL_Init(SDL_INIT_GAMECONTROLLER); check controller database |
| Memory leak | Destroy all textures, surfaces, fonts; call SDL_Quit() subsystems |
| Blurry rendering | Set SDL_HINT_RENDER_SCALE_QUALITY to “0” for pixel art |
| Window not responding | Always call SDL_PollEvent() in main loop to process OS events |