How to render fonts and text with SDL2 efficiently?
Asked Answered
G

2

30

Saw this post here about using SDL_ttf to render text in a game. However that approach requires calling SDL_CreateTextureFromSurface(), along with the SDL_FreeSurface() and SDL_DestroyTexture() every single frame.

Is creating textures (and probably subsequently having to send them to the GPU) every frame something that can significally impact my performance?

would it be wiser to use SDL_ttf only to create a texture with my whole rendered charset and then to blit from there myself, character by character?

Edit: I'm looking to render simple monospace fonts in US-English (basic ASCII) only.

Gman answered 15/3, 2015 at 19:6 Comment(5)
maybe, but remember that "drawing text" with a modern font is nothing like "getting the letters, and then putting them next to each other" (that how fonts worked in 1984. We moved quite a lot along since then). Modern fonts put letters together, correct spacing between them, perform subtle replacements where necessary, smooth outlines based on pointsize chosen, and a lot of other things that you lose by treating the font as, basically, a bitmap.Affinitive
Agreed, but for the sake of this discussion let's assume we're restricting ourselves to simple monospace fonts and ASCII characters only. I'm trying to get a rough idea of what all this texture creation and deletion will entail in terms of CPU/GPU usageGman
fair point (worth adding to the post)Affinitive
Just render your text one time, keep this texture until the text value has changed and redraw your texture when the text is not the same. With this method, you will not redraw your text at each frame and you will save a little bit of render time.Aeromechanic
I've come across this question while looking for a solution to this problem. How I want to solve it, is to use a buffer surface where I would move all text on given frame with BlitSurface and then, after all the text is added to it, create a texture of it and draw it. Would that be a good solution or would it still be performance hitting loop to do every frame? My knowledge of how exactly SDL2 works on lower levels isn't so good, so I would be glad if you could explain to me why or why not this is a good solution. Thanks.Bascom
N
39

Yes, creating textures every frame can affect performance. Also, rasterizing TrueType fonts to SDL_Surfaces (as SDL_ttf does) every frame can affect performance.

I recommend SDL_FontCache (full disclosure: I'm the author). It uses SDL_ttf and caches the resulting glyphs in textures so you don't have to do it all yourself:
https://github.com/grimfang4/SDL_FontCache

Naima answered 19/4, 2015 at 4:20 Comment(2)
Does this only work for SDL? Not SDL2? Also, I heard a rumor that SDL_ttf source has a SetFontSize() which allows to change size of your TTF_Font *. Should that effect the structures you keep around in your FontCache?Dmso
It actually assumes SDL2 and would not work with SDL1 without some modification. If SetFontSize() were exposed publicly, I don't believe it should affect the sturctures in SDL_FontCache. Just don't pull the rug out from under the library and it'll be fine.Naima
U
7

OpenGL text methods

You are more likely to find an efficient implementation by using OpenGL, since it is more widely used than SDL, see: How to draw text using only OpenGL methods?

Currently, I'd go for freetype-gl: https://github.com/rougier/freetype-gl which supports texture atlas https://en.wikipedia.org/wiki/Texture_atlas out of the box.

SDL supports OpenGL well, and you can even use both GL and SDL textures in a single program, if you are already using SDL textures in your program, e.g.:

#include <SDL2/SDL.h>
#define GLEW_STATIC
#include <GL/glew.h>

int main(void) {
    SDL_GLContext gl_context;
    SDL_Event event;
    SDL_Renderer *renderer = NULL;
    SDL_Texture *texture = NULL;
    SDL_Window *window = NULL;
    Uint8 *base;
    const unsigned int
        WINDOW_WIDTH = 500,
        WINDOW_HEIGHT = WINDOW_WIDTH
    ;
    int pitch;
    unsigned int x, y;
    void *pixels = NULL;

    /* Window setup. */
    SDL_Init(SDL_INIT_TIMER | SDL_INIT_VIDEO);
    window = SDL_CreateWindow(
        __FILE__, 0, 0,
        WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_OPENGL
    );
    renderer = SDL_CreateRenderer(window, 0, 0);
    gl_context = SDL_GL_CreateContext(window);

    /* GL drawing. */
    glClearColor(1.0, 0.0, 1.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);

    /* Wrapped texture drawing. */
    texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888,
        SDL_TEXTUREACCESS_STREAMING, WINDOW_WIDTH, WINDOW_HEIGHT);
    SDL_LockTexture(texture, NULL, &pixels, &pitch);
    for (x = 0; x < WINDOW_WIDTH; x++) {
        for (y = 0; y < WINDOW_HEIGHT; y++) {
            base = ((Uint8 *)pixels) + (4 * (x * WINDOW_WIDTH + y));
            base[0] = 0;
            base[1] = 0;
            base[2] = 255;
            base[3] = 255;
        }
    }
    SDL_UnlockTexture(texture);
    SDL_Rect rect;
    rect.x = 0;
    rect.y = 0;
    rect.w = WINDOW_WIDTH / 2;
    rect.h = WINDOW_HEIGHT / 2;
    SDL_RenderCopy(renderer, texture, NULL, &rect);
    SDL_GL_SwapWindow(window);

    /* Main loop. */
    while (1) {
        if (SDL_PollEvent(&event) && event.type == SDL_QUIT)
            break;
    }

    /* Cleanup. */
    SDL_GL_DeleteContext(gl_context);
    SDL_DestroyTexture(texture);
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();

    return EXIT_SUCCESS;
}

Compile and run:

gcc -std=c99 main.c -lSDL2 -lGL
./a.out

Tested in Ubuntu 17.10.

GitHub upstream: https://github.com/cirosantilli/cpp-cheat/blob/d36527fe4977bb9ef4b885b1ec92bd0cd3444a98/sdl/texture_and_opengl.c

Undersized answered 28/12, 2016 at 12:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.