如何使用SDL2高效地渲染字体和文本?

29

在这篇文章中看到了有关在游戏中使用SDL_ttf渲染文本的内容。但是,这种方法需要在每一帧都调用SDL_CreateTextureFromSurface()以及SDL_FreeSurface()和SDL_DestroyTexture()。

每帧都创建纹理(并且可能随后必须将它们发送到GPU)是否会对我的性能产生显着影响?

使用SDL_ttf仅创建一个包含整个渲染字符集的纹理,然后自己逐个字符地从该纹理上进行位块传送,是否更明智?

编辑:我正在寻找渲染基本ASCII字符集下简单等宽字体的方法。


5
也许可以,但要记住,用现代字体“绘制文本”与“获取字母,然后将它们放在一起”完全不同(这是1984年的字体工作方式。我们从那时起已经有了很大进步)。现代字体会把字母组合在一起,正确地调整它们之间的间距,在必要时进行微小的替换,根据所选的点大小平滑轮廓,并且还有许多其他的事情,如果将字体基本上视为位图,则会失去这些东西。 - Mike 'Pomax' Kamermans
2
同意,但为了讨论方便,让我们假定只使用简单的等宽字体和 ASCII 字符。我试图大致了解所有这些纹理创建和删除将涉及多少 CPU/GPU 使用。 - introiboad
公正的观点(值得添加到帖子中) - Mike 'Pomax' Kamermans
4
只需渲染您的文本一次,保留此纹理直到文本值更改,并在文本不相同时重新绘制您的纹理。使用此方法,您将不会在每帧重新绘制文本,并节省一些渲染时间。 - jordsti
我在寻找解决问题的方法时遇到了这个问题。我想要解决它的方法是使用缓冲表面,在给定帧上移动所有文本,并在添加所有文本后创建其纹理并绘制它。这是否是一个好的解决方案,还是每帧执行会影响性能?我对SDL2在更低层次上的工作原理并不是很了解,所以如果您能向我解释这是否是一个好的解决方案,我将不胜感激。谢谢。 - Maroš Beťko
2个回答

37

是的,每帧创建纹理会影响性能。此外,每帧将TrueType字体转化为SDL_Surfaces(如SDL_ttf所做)也会影响性能。

我推荐使用SDL_FontCache(完整披露:我是作者)。它使用SDL_ttf并将结果缓存在纹理中,因此您不必自己处理所有内容:
https://github.com/grimfang4/SDL_FontCache


这只适用于SDL吗?不适用于SDL2吗?另外,我听说SDL_ttf源代码有一个SetFontSize()函数,可以改变你的TTF_Font *字体大小。那么这是否会影响你在FontCache中保留的结构? - activedecay
它实际上假定了SDL2,并且没有进行一些修改就无法与SDL1一起使用。如果将SetFontSize()公开,我认为它不应该影响SDL_FontCache中的结构。只要不破坏库,它就会很好。 - Jonny D

6

OpenGL文本方法

使用OpenGL可以更容易地找到高效的实现,因为它比SDL更广泛地使用,详见:如何仅使用OpenGL方法绘制文本?

目前,我建议使用freetype-glhttps://github.com/rougier/freetype-gl,该库支持开箱即用的纹理图集https://en.wikipedia.org/wiki/Texture_atlas

SDL对OpenGL支持良好,如果你已经在程序中使用了SDL纹理,甚至可以在单个程序中同时使用GL和SDL纹理,例如:

#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;
}

编译并运行:

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

已在Ubuntu 17.10中测试。

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接