Tutorial de SDL – Parte 25 – Definindo o Frame Rate manualmente

Continuando nossa série de artigo traduzido do site lazyfoo, agora vamos ver outra coisa que podemos fazer com timers no SDL, que é definir manualmente o frame rate. Para isso, desativaremos o vsync e manteremos o frame rate no máximo.

//Screen dimension constants
const int SCREEN_WIDTH = 640;
const int SCREEN_HEIGHT = 480;
const int SCREEN_FPS = 60;
const int SCREEN_TICKS_PER_FRAME = 1000 / SCREEN_FPS;

No exemplo que veremos aqui, iremos renderizar nosso frame normalmente, mas no final do frame iremos esperar até que o frame seja completado. Por exemplo, quando você quer renderizar à 60 fps precisa gastar 16 e 2/3 de milissegundos por frame (1000ms / 60 frames).  Por isso  calculamos a quantidade de tempo por frame em milissegundos.

 //Create renderer for window
 gRenderer = SDL_CreateRenderer( gWindow, -1, SDL_RENDERER_ACCELERATED );
Como você pode ver, nós desativamos o VSync para esse exemplo pois estaremos definindo manualmente o frame rate.
 //Main loop flag
 bool quit = false;
 //Event handler
 SDL_Event e;
 //Set text color as black
 SDL_Color textColor = { 0, 0, 0, 255 };
 //The frames per second timer
 LTimer fpsTimer;
 //The frames per second cap timer
 LTimer capTimer;
 //In memory text stream
 std::stringstream timeText;
 //Start counting frames per second
 int countedFrames = 0;
 fpsTimer.start();

Neste programa não apenas precisaremos de um timer para calcular o frame rate, mas também de um timer para definir os frames por segundo. Aqui, antes de entrarmos no loop principal, declaramos algumas variáveis e iniciamos o calculador de fps do timer.

 //While application is running
 while( !quit )
 {
 //Start cap timer
 capTimer.start();

Para limitar o FPS precisamos saber quanto tempo o frame irá levar para ser renderizado, e é por isso que iniciamos um timer no inicio de cada frame.

 //Handle events on queue
 while( SDL_PollEvent( &e ) != 0 )
 {
 //User requests quit
 if( e.type == SDL_QUIT )
 {
 quit = true;
 }
 }
 //Calculate and correct fps
 float avgFPS = countedFrames / ( fpsTimer.getTicks() / 1000.f );
 if( avgFPS > 2000000 )
 {
 avgFPS = 0;
 }
 //Set text to be rendered
 timeText.str( "" );
 timeText << "Average Frames Per Second (With Cap) " << avgFPS;
 //Render text
 if( !gFPSTextTexture.loadFromRenderedText( timeText.str().c_str(), textColor ) )
 {
 printf( "Unable to render FPS texture!\n" );
 }
 //Clear screen
 SDL_SetRenderDrawColor( gRenderer, 0xFF, 0xFF, 0xFF, 0xFF );
 SDL_RenderClear( gRenderer );
 //Render textures
 gFPSTextTexture.render( ( SCREEN_WIDTH - gFPSTextTexture.getWidth() ) / 2, ( SCREEN_HEIGHT - gFPSTextTexture.getHeight() ) / 2 );
 //Update screen
 SDL_RenderPresent( gRenderer );
 ++countedFrames;

Aqui temos a renderização do frame e o cálculo do fps de antes.

 //If frame finished early
 int frameTicks = capTimer.getTicks();
 if( frameTicks < SCREEN_TICKS_PER_FRAME )
 {
 //Wait remaining time
 SDL_Delay( SCREEN_TICKS_PER_FRAME - frameTicks );
 }
 }
Finalmente, temos o código para definir o frame rate. Em primeiro lugar, descobrimos quanto tempo o frame levou para ser renderizado. Se a quantidade de tempo que o frame levou para executar a renderização é menor do que a quantidade de tempo necessária por frame, então nós adicionamos um atraso no tempo restante para evitar que a aplicação seja executada muito rápido.
Existe uma razão de estarmos usando o VSync em todos esses tutoriais ao invés de definirmos o frame rate manualmente. Ao rodar essa aplicação, você irá notar que elá é executada um pouco mais rápido. Como estamos usando inteiros (por números em ponto flutuante não serem precisos), o tempo de renderização por frame seŕá 16 ms ao invés de exatamente 16 2/3 ms. Essa solução é mais como um intervalo de parada para você ter que lidar com hardware que não suporte VSync.
Baixe os arquivos de mídia e de código fonte desse artigo aqui.