Expert Topics

Graphics Programming in C++


Graphics Libraries Overview

Popular Options

Library Purpose Level Best For
SFML 2D games/graphics Beginner 2D games, multimedia apps
SDL2 2D games/graphics Beginner Cross-platform, game engines
Raylib 2D/3D games Beginner Learning, prototyping
OpenGL 2D/3D graphics Advanced Custom rendering, 3D
Vulkan High-perf graphics Expert AAA games, performance

Installation (SFML on Ubuntu/Debian)

# Install SFML development libraries
sudo apt install libsfml-dev

# Compile with SFML
g++ -std=c++17 main.cpp -lsfml-graphics -lsfml-window -lsfml-system

The Game Loop

Every graphical application runs a continuous loop that processes input, updates state, and renders graphics.

The Classic Game Loop

┌─────────────────────────────────────────────────────────────────────────────┐
│                         THE GAME LOOP                                        │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│                          ┌─────────────────┐                                 │
│                          │  INITIALIZATION │                                 │
│                          │  - Create window│                                 │
│                          │  - Load assets  │                                 │
│                          └────────┬────────┘                                 │
│                                   │                                          │
│                                   ▼                                          │
│    ┌─────────────────────────────────────────────────────────────────┐      │
│    │                        MAIN LOOP                                 │      │
│    │  ┌───────────────────────────────────────────────────────────┐  │      │
│    │  │                                                           │  │      │
│    │  │   ┌──────────────┐                                        │  │      │
│    │  │   │ 1. PROCESS   │ Poll keyboard, mouse, window events    │  │      │
│    │  │   │    INPUT     │ Handle user actions                    │  │      │
│    │  │   └──────┬───────┘                                        │  │      │
│    │  │          │                                                │  │      │
│    │  │          ▼                                                │  │      │
│    │  │   ┌──────────────┐                                        │  │      │
│    │  │   │ 2. UPDATE    │ Move objects, apply physics            │  │      │
│    │  │   │    STATE     │ Check collisions, game logic           │  │      │
│    │  │   └──────┬───────┘                                        │  │      │
│    │  │          │                                                │  │      │
│    │  │          ▼                                                │  │      │
│    │  │   ┌──────────────┐                                        │  │      │
│    │  │   │ 3. RENDER    │ Clear screen                           │  │      │
│    │  │   │    GRAPHICS  │ Draw all objects                       │  │      │
│    │  │   │              │ Display to screen                      │  │      │
│    │  │   └──────┬───────┘                                        │  │      │
│    │  │          │                                                │  │      │
│    │  └──────────┴────── Loop continues ─────────────────────────┘  │      │
│    │                                                                 │      │
│    └────────────────────────────────── Until window closed ──────────┘      │
│                                                                              │
│                          ┌─────────────────┐                                 │
│                          │    CLEANUP      │                                 │
│                          │  - Free memory  │                                 │
│                          │  - Close window │                                 │
│                          └─────────────────┘                                 │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

Frame Rate and Delta Time

┌─────────────────────────────────────────────────────────────────────────────┐
│                       DELTA TIME EXPLAINED                                   │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  Problem: Different computers run at different speeds!                       │
│                                                                              │
│  Fast Computer (120 FPS):    Slow Computer (30 FPS):                        │
│  ─────────────────────────   ─────────────────────────                      │
│  Frame 1: move 1 pixel       Frame 1: move 1 pixel                          │
│  Frame 2: move 1 pixel       Frame 2: move 1 pixel                          │
│  ...                         ...                                             │
│  120 frames = 120 pixels     30 frames = 30 pixels                          │
│                                                                              │
│  SAME TIME, DIFFERENT DISTANCE! ❌                                          │
│                                                                              │
│  ─────────────────────────────────────────────────────────────────────────  │
│                                                                              │
│  Solution: Use DELTA TIME (dt) = time since last frame                      │
│                                                                              │
│  movement = velocity * dt                                                    │
│                                                                              │
│  Fast Computer (dt = 0.0083s):    Slow Computer (dt = 0.033s):              │
│  ─────────────────────────────    ─────────────────────────                 │
│  Frame 1: 100 * 0.0083 = 0.83px   Frame 1: 100 * 0.033 = 3.3px              │
│  Frame 2: 100 * 0.0083 = 0.83px   Frame 2: 100 * 0.033 = 3.3px              │
│  ...                               ...                                       │
│  120 frames = ~100 pixels          30 frames = ~100 pixels                   │
│                                                                              │
│  SAME TIME, SAME DISTANCE! ✅                                               │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

Coordinate Systems

Screen Coordinate System

┌─────────────────────────────────────────────────────────────────────────────┐
│                    SCREEN COORDINATE SYSTEM                                  │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  (0,0) ────────────────────────────────────────────────► X (width)          │
│    │                                                                         │
│    │     ┌─────────────────────────────────────────────────────┐            │
│    │     │                     WINDOW                           │            │
│    │     │                                                      │            │
│    │     │   (100, 50) ●                                        │            │
│    │     │                                                      │            │
│    │     │                  (400, 300) ●                        │            │
│    │     │                                                      │            │
│    │     │                                   (700, 500) ●       │            │
│    │     │                                                      │            │
│    │     └─────────────────────────────────────────────────────┘            │
│    ▼                                                                         │
│   Y (height)                                                                 │
│                                                                              │
│  ⚠️ NOTE: Y increases DOWNWARD (opposite of math coordinates!)              │
│                                                                              │
│  Window Size: 800 x 600                                                      │
│  • Top-left corner:     (0, 0)                                              │
│  • Top-right corner:    (799, 0)                                            │
│  • Bottom-left corner:  (0, 599)                                            │
│  • Bottom-right corner: (799, 599)                                          │
│  • Center:              (400, 300)                                          │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

Shape Positioning and Origin

┌─────────────────────────────────────────────────────────────────────────────┐
│                    SHAPE ORIGIN AND POSITIONING                              │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  By Default: Origin is at TOP-LEFT of shape                                 │
│                                                                              │
│      setPosition(100, 50)                                                    │
│              │                                                               │
│              ▼                                                               │
│    (100,50) ●─────────────┐                                                 │
│             │ ████████████ │   Shape drawn to the RIGHT and DOWN            │
│             │ ████████████ │   from position                                 │
│             │ ████████████ │                                                 │
│             └──────────────┘                                                 │
│                                                                              │
│  ─────────────────────────────────────────────────────────────────────────  │
│                                                                              │
│  With setOrigin(): Origin can be anywhere                                    │
│                                                                              │
│      setOrigin(width/2, height/2)  // Center origin                         │
│      setPosition(100, 50)                                                    │
│                                                                              │
│             ┌──────────────┐                                                 │
│             │ ████████████ │                                                 │
│             │ ████●████████ │   ← Position (100,50) is now at CENTER        │
│             │ ████████████ │                                                 │
│             └──────────────┘                                                 │
│                                                                              │
│  Centering is useful for:                                                    │
│  • Rotation (rotates around origin)                                          │
│  • Scaling (scales from origin)                                              │
│  • Positioning objects by center                                             │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

SFML Basics

Window Creation and Game Loop

#include <SFML/Graphics.hpp>

int main() {
    // Create a window (width, height, title)
    sf::RenderWindow window(sf::VideoMode(800, 600), "My Game");

    // Optional: Set frame rate limit
    window.setFramerateLimit(60);

    // Clock for delta time
    sf::Clock clock;

    // Main game loop
    while (window.isOpen()) {
        // 1. PROCESS INPUT (events)
        sf::Event event;
        while (window.pollEvent(event)) {
            if (event.type == sf::Event::Closed)
                window.close();
            if (event.type == sf::Event::KeyPressed) {
                if (event.key.code == sf::Keyboard::Escape)
                    window.close();
            }
        }

        // 2. UPDATE (game logic)
        float dt = clock.restart().asSeconds();
        // Update game objects here using dt

        // 3. RENDER (drawing)
        window.clear(sf::Color::Black);  // Clear with color
        // Draw all objects here
        window.display();                 // Show on screen
    }

    return 0;
}

The Rendering Pipeline

┌─────────────────────────────────────────────────────────────────────────────┐
│                     SFML RENDERING PIPELINE                                  │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  window.clear()                                                              │
│       │                                                                      │
│       ▼                                                                      │
│  ┌─────────────────────────────────────────────────────────────────────┐    │
│  │                         BACK BUFFER                                  │    │
│  │                   (invisible, being drawn to)                        │    │
│  │   ┌───────────────────────────────────────────────────────────┐     │    │
│  │   │                                                           │     │    │
│  │   │   Cleared to black (or specified color)                   │     │    │
│  │   │                                                           │     │    │
│  │   └───────────────────────────────────────────────────────────┘     │    │
│  └─────────────────────────────────────────────────────────────────────┘    │
│       │                                                                      │
│       ▼                                                                      │
│  window.draw(shape1)                                                         │
│  window.draw(shape2)  ← Drawing happens HERE (to back buffer)               │
│  window.draw(sprite)                                                         │
│       │                                                                      │
│       ▼                                                                      │
│  ┌─────────────────────────────────────────────────────────────────────┐    │
│  │                         BACK BUFFER                                  │    │
│  │   ┌───────────────────────────────────────────────────────────┐     │    │
│  │   │     ●         ▲                                           │     │    │
│  │   │    shape1    sprite                                       │     │    │
│  │   │              ■ shape2                                     │     │    │
│  │   └───────────────────────────────────────────────────────────┘     │    │
│  └─────────────────────────────────────────────────────────────────────┘    │
│       │                                                                      │
│       ▼                                                                      │
│  window.display()  ← SWAP buffers (back → front)                            │
│       │                                                                      │
│       ▼                                                                      │
│  ┌─────────────────────────────────────────────────────────────────────┐    │
│  │                         FRONT BUFFER                                 │    │
│  │                      (visible on screen!)                            │    │
│  └─────────────────────────────────────────────────────────────────────┘    │
│                                                                              │
│  This double-buffering prevents screen tearing and flickering!              │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

Color

// Predefined colors
sf::Color red = sf::Color::Red;
sf::Color green = sf::Color::Green;
sf::Color blue = sf::Color::Blue;
sf::Color white = sf::Color::White;
sf::Color black = sf::Color::Black;
sf::Color yellow = sf::Color::Yellow;
sf::Color cyan = sf::Color::Cyan;
sf::Color magenta = sf::Color::Magenta;
sf::Color transparent = sf::Color::Transparent;

// Custom RGB color
sf::Color purple(128, 0, 255);         // RGB

// Custom RGBA color (with transparency)
sf::Color semiTransparent(255, 0, 0, 128);  // 50% transparent red
//                        R    G  B  Alpha
//                                   0=invisible, 255=opaque

Drawing Shapes

Rectangle

sf::RectangleShape rect(sf::Vector2f(100, 50));  // width, height
rect.setPosition(200, 150);
rect.setFillColor(sf::Color::Blue);
rect.setOutlineColor(sf::Color::White);
rect.setOutlineThickness(2);

window.draw(rect);

Circle

sf::CircleShape circle(50);  // radius
circle.setPosition(300, 200);
circle.setFillColor(sf::Color::Green);
circle.setPointCount(100);   // smoothness

window.draw(circle);

Convex Shapes

sf::ConvexShape triangle;
triangle.setPointCount(3);
triangle.setPoint(0, sf::Vector2f(0, 0));
triangle.setPoint(1, sf::Vector2f(100, 0));
triangle.setPoint(2, sf::Vector2f(50, 100));
triangle.setFillColor(sf::Color::Yellow);

window.draw(triangle);

Lines

sf::Vertex line[] = {
    sf::Vertex(sf::Vector2f(10, 10), sf::Color::Red),
    sf::Vertex(sf::Vector2f(200, 200), sf::Color::Blue)
};

window.draw(line, 2, sf::Lines);

Textures and Sprites

Loading Textures

sf::Texture texture;
if (!texture.loadFromFile("player.png")) {
    // Handle error
    return -1;
}

sf::Sprite sprite;
sprite.setTexture(texture);
sprite.setPosition(100, 100);
sprite.setScale(2.0f, 2.0f);  // 2x size

window.draw(sprite);

Texture Regions

// Use part of texture (sprite sheet)
sprite.setTextureRect(sf::IntRect(0, 0, 32, 32));  // x, y, w, h

Transform

sprite.setOrigin(16, 16);       // Center of rotation
sprite.setRotation(45);         // Degrees
sprite.setScale(1.5f, 1.5f);
sprite.move(10, 0);             // Relative movement

Animation

Frame-based Animation

class Animation {
    sf::Sprite& sprite;
    sf::IntRect frameRect;
    int currentFrame = 0;
    int frameCount;
    float frameTime;
    float elapsedTime = 0;

public:
    Animation(sf::Sprite& s, int frames, float duration,
              int frameWidth, int frameHeight)
        : sprite(s), frameCount(frames),
          frameTime(duration / frames),
          frameRect(0, 0, frameWidth, frameHeight) {}

    void update(float dt) {
        elapsedTime += dt;
        if (elapsedTime >= frameTime) {
            elapsedTime = 0;
            currentFrame = (currentFrame + 1) % frameCount;
            frameRect.left = currentFrame * frameRect.width;
            sprite.setTextureRect(frameRect);
        }
    }
};

Delta Time

sf::Clock clock;

while (window.isOpen()) {
    float dt = clock.restart().asSeconds();

    // Update with delta time
    position += velocity * dt;
}

Input Handling

Keyboard

// Real-time (continuous)
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left)) {
    player.move(-speed * dt, 0);
}

// Event-based (single press)
if (event.type == sf::Event::KeyPressed) {
    if (event.key.code == sf::Keyboard::Space) {
        jump();
    }
}

Mouse

// Position
sf::Vector2i mousePos = sf::Mouse::getPosition(window);

// Buttons
if (sf::Mouse::isButtonPressed(sf::Mouse::Left)) {
    shoot();
}

// Events
if (event.type == sf::Event::MouseButtonPressed) {
    if (event.mouseButton.button == sf::Mouse::Left) {
        click(event.mouseButton.x, event.mouseButton.y);
    }
}

Game Loop

Fixed Timestep

int main() {
    sf::RenderWindow window(sf::VideoMode(800, 600), "Game");

    const float TIMESTEP = 1.0f / 60.0f;
    float accumulator = 0;
    sf::Clock clock;

    while (window.isOpen()) {
        float dt = clock.restart().asSeconds();
        accumulator += dt;

        // Handle events
        sf::Event event;
        while (window.pollEvent(event)) {
            if (event.type == sf::Event::Closed)
                window.close();
        }

        // Fixed update
        while (accumulator >= TIMESTEP) {
            update(TIMESTEP);
            accumulator -= TIMESTEP;
        }

        // Render
        window.clear();
        render(window);
        window.display();
    }

    return 0;
}

Simple Player Class

class Player {
    sf::Sprite sprite;
    sf::Texture texture;
    float speed = 200.0f;

public:
    bool init(const string& file) {
        if (!texture.loadFromFile(file)) return false;
        sprite.setTexture(texture);
        return true;
    }

    void update(float dt) {
        if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left))
            sprite.move(-speed * dt, 0);
        if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right))
            sprite.move(speed * dt, 0);
        if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up))
            sprite.move(0, -speed * dt);
        if (sf::Keyboard::isKeyPressed(sf::Keyboard::Down))
            sprite.move(0, speed * dt);
    }

    void draw(sf::RenderWindow& window) {
        window.draw(sprite);
    }
};

Text Rendering

sf::Font font;
if (!font.loadFromFile("arial.ttf")) {
    // Handle error
}

sf::Text text;
text.setFont(font);
text.setString("Hello World!");
text.setCharacterSize(24);
text.setFillColor(sf::Color::White);
text.setPosition(100, 100);

window.draw(text);

Cheat Sheet

// Window
sf::RenderWindow window(sf::VideoMode(w, h), "Title");
window.clear();
window.draw(shape);
window.display();

// Shapes
sf::RectangleShape rect(sf::Vector2f(w, h));
sf::CircleShape circle(radius);
shape.setPosition(x, y);
shape.setFillColor(color);

// Sprites
sf::Texture tex; tex.loadFromFile("img.png");
sf::Sprite sprite; sprite.setTexture(tex);

// Input
sf::Keyboard::isKeyPressed(sf::Keyboard::Space);
sf::Mouse::getPosition(window);

// Time
sf::Clock clock;
float dt = clock.restart().asSeconds();

Compile & Run

# Install SFML first: sudo apt install libsfml-dev
g++ -std=c++17 -Wall examples.cpp -lsfml-graphics -lsfml-window -lsfml-system -o examples
./examples