KP's Homepage

Quick and Dirty Text Rendering with SDL2

SDL2 is a popular library used by developers of games, real-time simulation software, cross-platform applications and more. It's a lovely library for game jams and prototyping game mechanics before implementing them in your engine of choice. You can use it with OpenGL, DirectX, Vulkan, etc. but it also comes with its own renderer.

The built-in rendering routines are fine, but the library doesn't take care of loading or drawing images and text out-of-the-box. The suggested guidance for this is to use SDL_image and SDL_ttf respectively, though I generally prefer stb_image.h and FreeType for these purposes. In any case, in the context of writing prototypes and development tools, I really don't want to have to include yet another library for something which ought to be trivial (assuming you don't care about UTF-8 support, anti-aliasing, hinting, etc.), so here's a simple 28-line function I wrote for those of you using SDL2 who just want to get some damn text on the screen:

void DrawString(SDL_Renderer* renderer, const char* txt, int x, int y) {
    static uint64_t font[] = {0x7cfc3cfcfefe3cc6,0x7e7ec6c0c6c67cfc,0x7cfc7cfcc6c6c6c6,0xccfe187c0000600c,0x0000c6c666c6c0c0,
           0x66c61818ccc0eee6,0xc6c6c6c6c630c6c6,0xc6c6cc0618c60000,0x30180000c6c6c0c6,0xc0c0c0c61818d8c0,0xfef6c6c6c6c6c030,
           0xc6c6c66ccc0c1806,0x00001830fc00fefc,0xc0c6f8f8c0fe1818,0xf0c0d6dec6c6c6c6,0x7030c6c6c6387818,0x180c00000c600000,
           0xc6c6c0c6c0c0cec6,0x1818d8c0c6cec6fc,0xc6fc1c30c6c6d66c,0x3030181800000c60,0x0000c6c6c0c6c0c0,0xc2c61818ccc0c6c6,
           0xc6c0c6c60630c6c6,0xfec6306018300000,0x1830fc00c6c666c6,0xc0c066c61818c6c0,0xc6c6c6c0ccc6c630,0xc66ceec630c00000,
           0x180830180000c6fc,0x3cfcfec03cc67e70,0xc6fec6c67cc07ac6,0x7c307c38c6c630fe,0x18301818600c0000,0x0c0000007c187c7c,
           0xccfe7cfe7c7c7c30,0x467c6c000000d8c0,0xe0e060c030c03060,0x3c3c0c000000c638,0xc6c6ccc0c606c6c6,0xc678e6d66c00c0c0,
           0xd8c0c060c0606060,0x30604242183000cc,0xc6180606ccc0c006,0xc6c6c6cc4cd0fe00,0xc0c04840c060c060,0x6060303099991830,
           0x00ccd6180c1cfe7c,0xfc0c7c7e6c001870,0x6c6c00009080c060,0xc060c0303030a1a5,0x30fcfc30d6181806,0x0c06c618c6063000,
           0x301c6cd800000000,0xc060c06060603018,0xa1a5303000ccc618,0x30060c06c630c606,0xd8006416fe00c040,0x0000c060c0606060,
           0x3018999d603000cc,0xc61860c60cc6c630,0xc6c6cc00ced66c00,0xc0c00000c060c060,0x6060300c42466000,0x00007c3cfe7c0c7c,
           0x7c307c7c7600c47c,0x6c0000000000e0e0,0x60c030c0300c3c3c};
    static const char*  font_mapping = "abcdefghijklmnopqrstuvwxyz!?.,><= /+-*0123456789&^%$#~:;\"'[](){}|\\`@";
    static SDL_Surface* font_surface = NULL;
    static SDL_Texture* font_texture = NULL;
    if (!font_surface) {
        font_surface = SDL_CreateRGBSurfaceWithFormat(0, 272, 16, 32, SDL_PIXELFORMAT_ABGR8888);
        for (int i = 0; i < 272 * 16; i++)
            ((uint32_t*)font_surface->pixels)[i] = ((font[i >> 6] >> (63 - (i & 63))) & 1) * 0xFFFFFFFF;
        font_texture = SDL_CreateTextureFromSurface(renderer, font_surface);
    }
    for (int i = 0; i < strlen(txt); i++) {
        const char* pos = strchr(font_mapping, tolower(txt[i]));
        size_t ci = pos ? pos - font_mapping : 0;
        SDL_Rect src_rect = {(ci % 34) * 8, (ci / 34) * 8, 8, 8};
        SDL_Rect dst_rect = {x + i * 8, y, 8, 8};
        SDL_RenderCopy(renderer, font_texture, &src_rect, &dst_rect);
    }
}

This is one function with no dependencies besides SDL2 itself.

How it works

The uint64_t "font" array contains the image data for the atlas pictured below with each 64 pixels packed into each element. The routine unpacks the data into an SDL_Surface, which is then converted to an SDL_Texture for use by the renderer.

We look up the index of the current character being rendered in the "font_mapping" string and use that to offset which part of the atlas will be blit. It's pretty simple really, it just looks psychotic because of the unpacking process.

Image of the font texture atlas
Image of the font texture atlas

In any case, hopefully this helps anybody out there trying to write minimal applications with SDL2 and SDL2 only.

Here's what it looks like in practice:

Screenshot of a pathfinding utility I wrote
Screenshot of this rendering technique used in my A* pathfinding test application

Last updated July 14th, 2024 at 10:50 AM CST