在SDL中实现恒定的帧率

18

我正试图制作一个SDL程序,该程序具有恒定的帧率。但是,即使我的程序运行在低帧率下且没有渲染很多东西,我仍然发现它非常卡顿并跳过很多帧。

你们有什么建议可以让我的程序运行更流畅吗?

#include "SDL.h"
#include "SDL/SDL_ttf.h"

//in milliseconds
const int FPS = 24;
const int SCREENW = 400;
const int SCREENH = 300;
const int BPP = 32;

void apply_surface(int x, int y, SDL_Surface* source, SDL_Surface* destination) {

    SDL_Rect offset;

    offset.x = x;

    offset.y = y;



    if(SDL_BlitSurface(source, NULL, destination, &offset) < 0) {
        printf("%s\n", SDL_GetError());
    }

}

int main(int argc, char* argv[]) {
    //calculate the period
    double period = 1.0 / (double)FPS;
    period = period * 1000;
    int milliPeriod = (int)period;
    int sleep;

    SDL_Init(SDL_INIT_EVERYTHING);
    TTF_Init();

    TTF_Font* font = TTF_OpenFont("/usr/share/fonts/truetype/freefont/FreeMono.ttf", 24);
    SDL_Color textColor = { 0x00, 0x00, 0x00 };

    SDL_Surface* screen = SDL_SetVideoMode(SCREENW, SCREENH, BPP, SDL_SWSURFACE);
    SDL_Surface* message = NULL;

    Uint32 white = SDL_MapRGB(screen->format, 0xFF, 0xFF, 0xFF);

    SDL_Event event;

    char str[15];

    Uint32 lastTick;
    Uint32 currentTick;
    while(1) {
        while(SDL_PollEvent(&event)) {
            if(event.type == SDL_QUIT) {
                return 0;
            }
            else {
                lastTick = SDL_GetTicks();

                sprintf(str, "%d", lastTick);
                message = TTF_RenderText_Solid(font, str, textColor);
                if(message == NULL) { printf("%s\n", SDL_GetError()); return 1; }

                //the actual blitting
                SDL_FillRect(screen, &screen->clip_rect, white);
                apply_surface(SCREENW / 2, SCREENH / 2, message, screen);

                currentTick = SDL_GetTicks();

                //wait the appropriate amount of time
                sleep = milliPeriod - (currentTick - lastTick);
                if(sleep < 0) { sleep = 0; }
                SDL_Delay(sleep);

                SDL_Flip(screen);
            }
        }
    }

    TTF_CloseFont(font);
    TTF_Quit();
    SDL_Quit();

    return 0;
}
4个回答

10

这里有一个如何做到这一点的小例子,位于http://www.libsdl.org/release/SDL-1.2.15/docs/html/guidetimeexamples.html

#define TICK_INTERVAL    30

static Uint32 next_time;

Uint32 time_left(void)
{
    Uint32 now;

    now = SDL_GetTicks();
    if(next_time <= now)
        return 0;
    else
        return next_time - now;
}


/* main game loop */

    next_time = SDL_GetTicks() + TICK_INTERVAL;
    while ( game_running ) {
        update_game_state();
        SDL_Delay(time_left());
        next_time += TICK_INTERVAL;
    }

@Jjpx:不再是了,但我猜那可能是一个仅有链接的回答,所以我想我会在这里粘贴它... - SamB

9
不要睡觉。相反,使用线性插值函数,根据主循环中的当前时间计算您的位置。这样做将确保无论硬件如何,宇宙飞船都会在相同的时间到达其目的地(尽管在快速的机器上,您会看到更多中间步骤)。还有其他插值/混合函数(例如线性缓入、缓出、二次缓入/缓出、三次等)可用于其他酷炫效果。请查看此链接:帧率独立的线性插值

23
我认为让设备进入睡眠状态以节省电量可能是个不错的主意。插值确实有些好处,而且在有些游戏中使用它也是不错的选择,但如果我的游戏可以达到60FPS,而同时只需使用20%的CPU,那么就绝对没有必要再运行得更快了,因为显示器很可能只是以60Hz的频率运行,所以这只会浪费笔记本电脑和其他设备的电量。 - Tomas Andrle
@TomA SDL 不是做帧同步 (VSync) 去限制帧率的吗? - Jonathan Baldwin
你可以在SDL 2.x中选择3种类型的V-Sync。请参阅SDL_GL_SetSwapInterval()。 - Michaelangel007
1
这样做可以确保无论硬件如何,太空船都能在同一时间到达目的地 - 如果浮点计算具有无限分辨率,那么这将是真实的。事实上,在不同的帧速率下,物体会以稍微(或者不那么稍微 - 这取决于情况)不同的速度移动。在浮点数学中,0.1 * 10 != 10。这种技术可能很有用,但需要小心处理。 - jacwah

4
正如dicroce所说,不要睡觉。为什么?因为大多数桌面操作系统不是硬实时系统,因此sleep(10)并不意味着“在确切的10毫秒后叫醒我”,而是意味着“确保我至少睡眠10毫秒”。这意味着有时你会比想象中更长时间地睡眠。这很少是你想要的。如果流畅的游戏玩法很重要,那么通常你只需要尽可能频繁地渲染-SDL_Flip会为你处理,前提是你的视频驱动程序没有禁用VSync。

除了睡眠,dicroce建议使用线性插值算法来计算任何给定时间内实体的正确位置。虽然这对于许多游戏来说是一个合理的策略,也是我自己经常使用的策略之一,但如果不小心处理,它可能会在某些情况下引起问题: "Integration Basics"文章解释了其中一些问题。同一作者在下一篇文章中提供了另一种选择,名为 "Fix Your Timestep".


1
我尝试过的方法似乎是没有延迟,但使用类似以下方式的东西:
int desired_fps = 60; 
int last_ticks = SDL_GetTicks();
while (true) {
     if (SDL_GetTicks() - last_ticks < 1000/desired_fps) {
         continue;
     } 
     last_ticks = SDL_GetTicks();
     // ... render frame...
}

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