DEV Community

Diego Crespo
Diego Crespo

Posted on

SDL Tutorial in C Part 2: Displaying Text

This is part 2 of my SDL series using C. If you missed part 1 you can find it here.๐Ÿ“Œ

SDL window shoing hello world

Displaying Text Cheat Sheet

  1. TTF_Init
  2. TTF_OpenFont.
  3. TTF_RenderText_Solid
  4. SDL_CreateTextureFromSurface
  5. SDL_RenderCopy

Tutorial

Displaying Text in SDL2 requires a very similar set of operations to how we handled creating a window and renderer in part 1. But before we get to displaying text, we need to include the appropriate header. Since SDL2 is designed to be modular, the libraries that work with text are in their own separate headers/files. To work with text, you will need SDL_ttf.h. You can either include it and the appropriate files directly in your project, linking them at compile time, or if you are on Linux like I am, use your package manager to install them globally. While we wonโ€™t be going over the specifics for how to set up SDL in this tutorial, the SDL Wiki provides a page on how to properly set up SDL that is pretty thorough. Now, for those of you who just want to see the code, here is the entire source

#include <SDL2/SDL.h>
#include <SDL2/SDL_ttf.h>
#include <stdio.h>
#include <stdlib.h>

#define SCREEN_WIDTH 1280
#define SCREEN_HEIGHT 720
#define MY_FONT "/usr/share/fonts/truetype/freefont/FreeSans.ttf"

int main(){
  if (SDL_Init(SDL_INIT_VIDEO) < 0){
    printf("Couldn't initialize SDL: %s\n", SDL_GetError());
    return EXIT_FAILURE;
  }
    // Initialize SDL_ttf
    if (TTF_Init() < 0) {
        printf("SDL_ttf could not initialize! TTF_Error: %s\n", TTF_GetError());
        SDL_Quit();
        return EXIT_FAILURE;
    }

  SDL_Window *window = SDL_CreateWindow("Drawing Text", SDL_WINDOWPOS_UNDEFINED,
                    SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, 0);

  if (!window){
    printf("Failed to open %d x %d window: %s\n", SCREEN_WIDTH, SCREEN_HEIGHT, SDL_GetError());
    return EXIT_FAILURE;
  }

  SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, 0);

  if (!renderer){
    printf("Failed to create renderer: %s\n", SDL_GetError());
    return EXIT_FAILURE;
  }

  SDL_SetRenderDrawColor(renderer, 255,255,255,255);
  SDL_RenderClear(renderer);

  TTF_Font *font = TTF_OpenFont(MY_FONT, 64); // specify the path to your font file and font size

  if (!font){
    printf("Failed to load font: %s\n", TTF_GetError());
    return EXIT_FAILURE;
  }

  // Create surface with rendered text
  SDL_Color textColor = {0, 0, 0, 255}; // black color
  SDL_Surface *textSurface = TTF_RenderText_Solid(font, "Hello World!", textColor);

  if (!textSurface) {
    printf("Failed to create text surface: %s\n", TTF_GetError());
    return EXIT_FAILURE;
  }

  // Create texture from surface
  SDL_Texture *textTexture = SDL_CreateTextureFromSurface(renderer, textSurface);

  if (!textTexture){
    printf("Failed to create text texture: %s\n", SDL_GetError());
    return EXIT_FAILURE;
  }

  // Render text
  SDL_Rect textRect = {50, 50, textSurface->w, textSurface->h}; // rectangle where the text is drawn 
  SDL_RenderCopy(renderer, textTexture, NULL, &textRect);

  SDL_RenderPresent(renderer);
  SDL_Delay(2000);
  SDL_DestroyWindow(window);
  SDL_DestroyRenderer(renderer);
  SDL_Quit();

  return EXIT_SUCCESS;
}
Enter fullscreen mode Exit fullscreen mode

Starting off we #include <SDL2/SDL_ttf.h> which gives us access to functions that help us display text. Since the ttf module works with fonts, which are normally, .ttf files, we need a font to properly display the text in our Window. You can either download a font from the internet, or use a .ttf file that you have on your computer. To keep it simple, Iโ€™ll just use one of the default ones that comes on my computer. At the top of my program, I've created a define that simply points to a default font

 #define MY_FONT "/usr/share/fonts/truetype/freefont/FreeSans.ttf"
Enter fullscreen mode Exit fullscreen mode

On macOS you should be able to find some .ttf files located at /Library/Fonts and on Windows C:\Windows\Fonts

Following the same paradigm we used to initialize our SDL video module, we have to initialize our ttf library. The same pattern is used, where we check to see if the return value of the TTF_Init function is less than zero, and if it is, we display an error to the user

    // Initialize SDL_ttf
    if (TTF_Init() < 0) {
        printf("SDL_ttf could not initialize! TTF_Error: %s\n", TTF_GetError());
        SDL_Quit();
        return EXIT_FAILURE;
    }
Enter fullscreen mode Exit fullscreen mode

If the ttf library was installed and linked correctly then we can attempt to load a font using TTF_OpenFont. Again we want to check that the font was actually loaded and we do that with our if statement

  TTF_Font *font = TTF_OpenFont(MY_FONT, 64); // specify the path to your font file and font size

  if (!font){
    printf("Failed to load font: %s\n", TTF_GetError());
    return EXIT_FAILURE;
  }
Enter fullscreen mode Exit fullscreen mode

Now that we've loaded our font we need to create a text surface using TTF_RenderText_Solid This surface is essentially an image containing the rendered text. Since this is an operation that can fail, we want to make sure that we check this using an if statement like we've done before

 // Create surface with rendered text
  SDL_Color textColor = {0, 0, 0, 255}; // black color
  SDL_Surface *textSurface = TTF_RenderText_Solid(font, "Hello World!", textColor);

  if (!textSurface) {
    printf("Failed to create text surface: %s\n", TTF_GetError());
    return EXIT_FAILURE;
  }
Enter fullscreen mode Exit fullscreen mode

The next step uses SDL_CreateTextureFromSurface to convert our SDL_Surface (your rendered text) into an SDL_Texture. This is necessary because modern SDL2 uses the GPU for rendering, and textures are the GPU-friendly way of handling images. This conversion process uploads the surface data to the GPU memory, enabling efficient rendering. This operation can also fail, so it is important to check that it doesn't before doing anything with the texture

  // Create texture from surface
  SDL_Texture *textTexture = SDL_CreateTextureFromSurface(renderer, textSurface);

  if (!textTexture){
    printf("Failed to create text texture: %s\n", SDL_GetError());
    return EXIT_FAILURE;
  }
Enter fullscreen mode Exit fullscreen mode

Finally, we call SDL_RenderCopy. This function call tells SDL2 to copy the text texture to the renderer. The SDL_Rect textRect that is defined is used to position and size the text on the screen.

It is important to understand that nothing will be drawn on the screen yet. It's more like preparing what needs to be drawn. The actual drawing happens when you call SDL_RenderPresent

  // Render text
  SDL_Rect textRect = {50, 50, textSurface->w, textSurface->h}; // rectangle where the text is drawn 
  SDL_RenderCopy(renderer, textTexture, NULL, &textRect);
Enter fullscreen mode Exit fullscreen mode

If all goes well then when you run the code, you should see some text on the screen. With that, you are well on your way to learning the core components of using the SDL2 library. With just the information that youโ€™ve learned from part 1 and part 2, you should be able to make a graphical version of the guess my number game, a common beginner programming task, but with a SDL flavored twist. You could even create a text adventure, if you were so inclined.

One final note. Between this and the last tutorial, you may be seeing a lot of repetitive code that might be better off abstracted away in functions. Thatโ€™s good, and means you are getting familiar with the patterns in the library. Opinions about the structure of code should be based off the needs of the code base. While Iโ€™m keeping it simple for these tutorials, eventually we should abstract away some common patterns.

FAQ

Why Not Draw Directly to the Renderer?

SDL2 uses two main types of objects for drawing: SDL_Surface and SDL_Texture. SDL_Surface is a CPU-based bitmap, useful for manipulating pixel data directly. SDL_Texture, on the other hand, is GPU-based. Modern SDL2 applications use the GPU for rendering (via SDL_Renderer), which is faster and more efficient but requires textures, not surfaces.

The reason you can't directly render the font to the renderer is that SDL2_ttf is designed to work with surfaces, as historically SDL handled rendering with surfaces before textures and GPU rendering were introduced.

Does that mean that SDL2 can render just the surface instead of the texture if I don't want to use the GPU?

Yes, SDL2 can render using just an SDL_Surface without converting it to an SDL_Texture if you prefer or need to render without using the GPU. This approach is known as "software rendering" and it's done entirely by the CPU, bypassing the GPU.

Software rendering with SDL_Surface was the primary method used in SDL before SDL2 introduced hardware-accelerated rendering with SDL_Texture and SDL_Renderer. While software rendering is less efficient and slower than hardware accelerated rendering, it can be useful in certain scenarios, like on systems with limited or no GPU capabilities. Instead of using a renderer like we are using you would instead create a SDL_Surface with SDL_GetWindowSurface

SDL_Surface *windowSurface = SDL_GetWindowSurface(window);
Enter fullscreen mode Exit fullscreen mode

Call To Action ๐Ÿ“ฃ

Hi ๐Ÿ‘‹ my name is Diego Crespo and I like to talk about technology, niche programming languages, and AI. I have a Twitter and a Mastodon, if youโ€™d like to follow me on other social media platforms. If you liked the article, consider checking out my Substack. And if you havenโ€™t why not check out another article of mine listed below! Thank you for reading and giving me a little of your valuable time. A.M.D.G

Top comments (0)