我正在编写一个小游戏来教我自己的OpenGL渲染,因为它是我尚未解决的问题之一。我之前使用过SDL,同样的功能,虽然仍然表现不佳,但并不像现在那样过顶。
基本上,我的游戏中没有太多进展,只是一些基本的动作和背景绘图。当我切换到OpenGL时,它看起来好像是方式太快了。我的每秒帧数超过2000,此功能耗尽了大部分处理能力。
有趣的是,它的SDL版本中的程序使用了100%的CPU但运行顺畅,而OpenGL版本只使用了大约40% - 60%的CPU,但似乎以我整个桌面的方式对我的显卡征税变得反应迟钝。坏。
这不是一个太复杂的功能,它根据玩家的X和Y坐标呈现1024x1024背景图块,以给出移动的印象,同时玩家图形本身保持锁定在中心。因为它是用于更大屏幕的小瓷砖,所以我必须多次渲染它以将瓷砖拼接在一起以获得完整的背景。下面代码中的两个for循环迭代12次,结合起来,所以我可以看到为什么在每秒调用2000次时这是无效的。
所以要明白这一点,这就是邪恶的行为者:
void render_background(game_t *game)
{
int bgw;
int bgh;
int x, y;
glBindTexture(GL_TEXTURE_2D, game->art_background);
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &bgw);
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &bgh);
glBegin(GL_QUADS);
/*
* Start one background tile too early and end one too late
* so the player can not outrun the background
*/
for (x = -bgw; x < root->w + bgw; x += bgw)
{
for (y = -bgh; y < root->h + bgh; y += bgh)
{
/* Offsets */
int ox = x + (int)game->player->x % bgw;
int oy = y + (int)game->player->y % bgh;
/* Top Left */
glTexCoord2f(0, 0);
glVertex3f(ox, oy, 0);
/* Top Right */
glTexCoord2f(1, 0);
glVertex3f(ox + bgw, oy, 0);
/* Bottom Right */
glTexCoord2f(1, 1);
glVertex3f(ox + bgw, oy + bgh, 0);
/* Bottom Left */
glTexCoord2f(0, 1);
glVertex3f(ox, oy + bgh, 0);
}
}
glEnd();
}
如果我在游戏循环中通过调用SDL_Delay(1)
人为地限制速度,我将FPS降低到~660±20,我没有“性能过度杀伤”。但我怀疑这是继续这个问题的正确方法。
为了完成,这些是我的一般渲染和游戏循环功能:
void game_main()
{
long current_ticks = 0;
long elapsed_ticks;
long last_ticks = SDL_GetTicks();
game_t game;
object_t player;
if (init_game(&game) != 0)
return;
init_player(&player);
game.player = &player;
/* game_init() */
while (!game.quit)
{
/* Update number of ticks since last loop */
current_ticks = SDL_GetTicks();
elapsed_ticks = current_ticks - last_ticks;
last_ticks = current_ticks;
game_handle_inputs(elapsed_ticks, &game);
game_update(elapsed_ticks, &game);
game_render(elapsed_ticks, &game);
/* Lagging stops if I enable this */
/* SDL_Delay(1); */
}
cleanup_game(&game);
return;
}
void game_render(long elapsed_ticks, game_t *game)
{
game->tick_counter += elapsed_ticks;
if (game->tick_counter >= 1000)
{
game->fps = game->frame_counter;
game->tick_counter = 0;
game->frame_counter = 0;
printf("FPS: %d\n", game->fps);
}
render_background(game);
render_objects(game);
SDL_GL_SwapBuffers();
game->frame_counter++;
return;
}
根据gprof分析,即使我使用SDL_Delay()
限制执行,它仍会花费大约50%的时间渲染我的背景。
答案 0 :(得分:6)
开启VSYNC。这样你就可以像显示器一样快地计算图形数据给用户,并且你不会浪费CPU或GPU周期来计算中间的额外帧,因为显示器仍在忙着显示前一帧
答案 1 :(得分:3)
首先,您不需要渲染平铺x * y次 - 您可以为它应覆盖的整个区域渲染一次,并使用GL_REPEAT让OpenGL覆盖整个区域。您需要做的就是计算一次正确的纹理坐标,这样瓷砖就不会变形(拉伸)。为了使它看起来像是在移动,每帧增加一个小边距的纹理坐标。
现在限制速度。你想要做的不是只在那里插入一个sleep()调用,而是测量渲染一个完整帧所需的时间:
function FrameCap (time_t desiredFrameTime, time_t actualFrameTime)
{
time_t delay = 1000 / desiredFrameTime;
if (desiredFrameTime > actualFrameTime)
sleep (desiredFrameTime - actualFrameTime); // there is a small imprecision here
}
time_t startTime = (time_t) SDL_GetTicks ();
// render frame
FrameCap ((time_t) SDL_GetTicks () - startTime);
有一些方法可以使这更精确(例如,在Windows 7上使用性能计数器功能,或在Linux上使用微秒分辨率),但我认为你得到了一般的想法。这种方法还具有独立于驱动程序的优点 - 与耦合到V-Sync不同 - 允许任意帧速率。
答案 2 :(得分:1)
在2000 FPS时,渲染整个帧只需0.5 ms。如果你想获得60 FPS,那么每帧应该花费大约16 ms。要做到这一点,首先渲染你的帧(大约0.5毫秒),然后使用SDL_Delay()
来消耗剩余的16毫秒。
此外,如果您有兴趣分析您的代码(如果您获得2000 FPS则不需要!),那么您可能想要使用High Resolution Timers。通过这种方式,您可以准确判断任何代码块需要多长时间,而不仅仅是程序花费的时间。