2012-08-31 198 views
4

最近我一直在研究一些SDL包裝以供我自己使用,而且自從我首次開始使用SDL庫以來,我遇到了一直存在的問題。C++ SDL幀速率脈衝

看到,像許多其他人一樣,我一直在使用一個類似於這個http://lazyfoo.net/SDL_tutorials/lesson14/index.php的計時器來調節我的幀率,並且運動永遠不會平滑。它似乎不是一個雙緩衝或vsync問題,而是運動平穩但週期性地跳過和斷斷續續(實際上它有一定的脈衝和節奏)。無論何時只要調整幀率,脈衝消失 - 當然,一切都變得無法使用),所以我確信它必須處理它。

我已經把一個只包含計時器和一個紅色正方形的小應用程序組合在一起。我將把代碼放在這篇文章的末尾。代碼是垃圾,不會使用任何類型的精靈或任何東西:只要知道它無論施加多少壓力(比如說,添加更多移動的東西或僅更新屏幕的一部分)就可以結束。我嘗試過不同的幀速率設置,並且脈衝總是出現(只是具有不同的「速度」)。不用說我已經在幾臺機器(linux機器上,都是這樣)上試過了。

無論如何,我一直在閱讀和看到其他人有這個問題,但沒有真正的答案。其中之一就是關於三角洲計時技術,並樂意嘗試,但我的問題在於計時器本身就是這個非常簡單的應用程序。它爲什麼口吃? SDL_GetTicks的精度和準確性有問題嗎?我能做些什麼來改善它?

所以,這裏是爲那些誰想要嘗試它的代碼:

#include <SDL/SDL.h> 

class fps_control 
{ 
    private: 

    bool apply; 
    Uint32 ticks_frame_count; 
    Uint32 ticks_frame_end; 
    Uint32 ticks_frame_begin; 
    Uint32 diff; 
    unsigned int frame_count; //Visible to the outside 
    unsigned int frame_count_inner; //Keeps count until a second has passed, then overwrites former to give the elapsed frames in a second. 
    unsigned int frame_length; 
    unsigned int desired_framerate; 

    private: 

    void calculate_frame_length() 
    { 
     this->frame_length=1000/this->desired_framerate; 
    } 

    public: 

    fps_control(unsigned int p_f=30):desired_framerate(p_f), apply(true), ticks_frame_count(0), ticks_frame_end(0), ticks_frame_begin(0), diff(0), frame_count(0), frame_count_inner(0), frame_length(0) 
    { 
     this->calculate_frame_length(); 
    } 

    unsigned int get_frame_count() const {return this->frame_count;} 
    unsigned int get_desired_framerate() const {return this->desired_framerate;} 
    void framerate_increase(){this->set_framerate(this->desired_framerate+1);} 
    void framerate_decrease(){this->set_framerate(this->desired_framerate+1);} 
    void set_framerate(unsigned int p_param) 
{ 
this->desired_framerate=p_param; 
this->calculate_frame_length(); 
} 
    void toggle_apply() {this->apply=!this->apply;} 

    void init() 
    { 
     //Call this once before starting, to set the beginning frame count to the initial values. 

     this->ticks_frame_count=SDL_GetTicks(); 
     this->ticks_frame_begin=this->ticks_frame_count; 
     this->frame_count_inner=0; 
     this->frame_count=0; 
    } 

    void turn() 
    { 
     //Call this when all drawing and logic is done. 

     if(!this->apply) return; //Only apply when asked for. 

     //Ask for time before drawing and logic to calculate the difference. Add a frame. 

     this->ticks_frame_end=SDL_GetTicks(); 
     this->frame_count_inner++; 

     //Whenever a second has passed, update the visible frame count. 
     if((this->ticks_frame_end - this->ticks_frame_count) > 1000) 
     { 
      this->frame_count=this->frame_count_inner; 
      this->frame_count_inner=0; 
      this->ticks_frame_count=SDL_GetTicks(); 
     } 

     //Calculate difference and apply delay when needed. 

     this->diff=this->ticks_frame_end - this->ticks_frame_begin; 

     if(this->diff < this->frame_length) SDL_Delay(this->frame_length-this->diff); 


     //Get the beginning time and start again. 
     this->ticks_frame_begin=SDL_GetTicks(); 
    } 
}; 

class Box 
{ 
    private: 

    SDL_Rect position; 
    int movement_x; 
    int movement_y; 

    public: 

    Box():movement_x(4), movement_y(4) 
    { 
     this->position.x=100; 
     this->position.y=100; 
     this->position.w=30; 
     this->position.h=30; 
    } 

    SDL_Rect get_position() {return this->position;} 

    void move_around() 
    { 
     //Won't touch the edges, but doesn't really matter. 

     if(this->position.x<=0 || this->position.x>=800-this->position.w) this->movement_x=-this->movement_x; 
     if(this->position.y<=0 || this->position.y>=600-this->position.h) this->movement_y=-this->movement_y; 

     this->position.x+=this->movement_x; 
     this->position.y+=this->movement_y; 
    } 
}; 

bool init_sdl(){return SDL_Init(SDL_INIT_VIDEO) >= 0;} 
void quit_sdl(){SDL_Quit();} 
void fill_screen(SDL_Surface * screen) 
{ 
    SDL_FillRect(screen, NULL, SDL_MapRGB(screen->format,0,0,0)); 
} 

void update_screen(SDL_Surface * screen, Box& box) 
{ 
    SDL_Rect b=box.get_position(); 
    SDL_FillRect(screen, &b, SDL_MapRGB(screen->format, 200, 20, 20)); 
    SDL_Flip(screen); 
} 

int get_input() 
{ 
    SDL_Event event; 

    while(SDL_PollEvent(&event)) 
    { 
     if(event.type==SDL_QUIT) return 1; 
     else if(event.type==SDL_KEYDOWN) 
     { 
      switch(event.key.keysym.sym) 
      { 
       case SDLK_ESCAPE: return 1; break; 
       case SDLK_SPACE: return 2; break; 
       case SDLK_UP: return 3; break; 
       case SDLK_DOWN: return 4; break; 
       default: break; 
      } 
     } 
    } 

    return 0; 
} 

int main(int argc, char **argv) 
{ 
    if(!init_sdl()) 
    { 
     return 1; 
    } 
    else 
    { 
     //Init things... 

//  SDL_Surface * screen=SDL_SetVideoMode(800, 600, 16, SDL_DOUBLEBUF | SDL_HWSURFACE); /*SDL_SWSURFACE | SDL_ANYFORMAT);*/ 
     SDL_Surface * screen=SDL_SetVideoMode(800, 600, 16, SDL_SWSURFACE | SDL_ANYFORMAT); 
     Box box=Box(); 
     fps_control fps=fps_control(60); //Framerate is set to 60. 
     bool run=true; 
     int input=0; 

     SDL_FillRect(screen, NULL, SDL_MapRGB(screen->format,255,0,0)); 

     //Main loop 

     fps.init(); 

     while(run) 
     { 
      input=get_input(); 

      switch(input) 
      { 
       case 1: run=false; break; 
       case 2: fps.toggle_apply(); break; 
       case 3: fps.framerate_increase(); break; 
       case 4: fps.framerate_decrease(); break; 
       default: break; 
      } 

      box.move_around(); 
      fill_screen(screen); 
      update_screen(screen, box); 
      fps.turn(); 
     } 

     quit_sdl(); 
    } 
} 

它是自包含的(並再次,純垃圾),所以正好與SDL鏈接,並嘗試...你在這裏看到任何口吃脈衝?

我會嘗試應用增量計時以查看問題是否消失。我只是想知道爲什麼會發生這種簡單的應用程序。非常感謝。

編輯:最後一件事,我知道我可能不應該去SDL_Delay(關於系統睡至少問值),但我真的很困惑這種看似強大的機器上的行爲。

編輯:修正了一下「set_framerate」,感謝Yno對它的評論。

編輯2:因爲我改變了所有的代碼,使用增量時間值而不是設置幀率,所有的東西都變得更好了。我仍然得到一些週期性的減速(這次每次四十秒),但我可以責怪那些在系統上,因爲根據我使用的Linux桌面(比如Gnome,Unity,Gnome2d ...),幀率和減速變化很大。

回答

4

你的課fps_control根本就不做這項工作,它完全是越野車。該framerate_decrease()功能失常:

void framerate_decrease(){this->set_framerate(this->desired_framerate+1);} 

應該-1這裏。calculate_frame_length()應該每次叫你想要的FPS改變,即進入set_framerate()

void set_framerate (unsigned int p_param) { 
    desired_framerate = p_param; 
    calculate_frame_length(); 
} 

另外要小心你的師calculcate_frame_length(),你可能會被零除:從這些問題

void framerate_decrease() { 
    if (desired_framerate > 1) 
     set_framerate (desired_framerate - 1); 
} 

除了,你的班級看起來無用複雜。如果你想限制幀率,你必須瞭解它是如何計算的。通常的方法去了解它是計算使用SDL_GetTicks()您的幀所需的時間量:

int t; 
while (run) { 
    t = SDL_GetTicks(); 
    // ... 
    t = SDL_GetTicks() - t; 
} 

在循環結束時,t爲ms你的框架了,完成的數量。因此,1000/t是您的每秒幀數。如果幀率太高(即t太小),則必須填寫當前幀時間和所需幀時間之間的差距,撥打SDL_Delay()。說FPS是你的FPS限制,每幀應該採取的時間是1000/FPS

int t; 
while (run) { 
    t = SDL_GetTicks(); 
    // ... 
    t = SDL_GetTicks() - t; 

    // if the framerate is too high 
    if (t < 1000/FPS) { 
     // compute the difference to have a total frame time of 1000/FPS 
     SDL_Delay ((1000/FPS) - t); 
    } 
} 

希望這會有所幫助。

+0

感謝您的回答......其實,幀率降低爲錯誤的,因爲它得到(有些錯字我有沒有),但calculate_frame_length()在構造函數中,對嗎?該代碼實際上是西班牙文原件的翻譯,因此可能會有一些不一致之處。至於其他方面,我會說這是全部,只有其他形式,對吧? [繼續,打破「輸入鍵」] –

+0

即使有些東西可能是越野車或奇怪的(比如第一個t(ticks_frame_begin是在最後計算的),我不認爲我應該期待這個脈衝.. 。除非SDL_Delay有什麼錯誤,當然,一切都會有幫助,非常感謝:)。 –

+0

'calculate_frame_length()'應當每當所需的幀率發生變化時調用,即在'set_framerate()'中。我將編輯我的帖子。 – Yno

3

我不知道SDL代碼是怎麼回事,但如果它有任何用處,我剛寫了一個使用GLFW的FpsManager類,該類限制爲高端值,並提供時間增量以保持移動一致的幀率。原則將是相同的。

這不是有史以來最好的代碼,但它非常清晰地記錄下來,並且在用按鍵更改目標FPS +/-時在10fps和1000fps之間完全平滑。

如果是使用任何給你,你可以找到它: FpsManager - A C++ helper class for framerate independent movement

+0

非常感謝。我一定會看看它:)。順便說一句:好的頭像。 –