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 );
//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 ); } }
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.