├── LICENSE ├── README.md └── frame_timer.cpp /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Tyler Glaiel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FrameTimingControl 2 | Sample code for frame timing control 3 | 4 | This is taken from my engine, stripped of unnecessary stuff, and commented 5 | Read this blog post for more information 6 | https://medium.com/@tglaiel/how-to-make-your-game-run-at-60fps-24c61210fe75 7 | -------------------------------------------------------------------------------- /frame_timer.cpp: -------------------------------------------------------------------------------- 1 | //these are loaded from Settings in production code 2 | double update_rate = 60; 3 | int update_multiplicity = 1; 4 | bool unlock_framerate = true; 5 | 6 | //compute how many ticks one update should be 7 | int64_t clocks_per_second = SDL_GetPerformanceFrequency(); 8 | double fixed_deltatime = 1.0 / update_rate; 9 | int64_t desired_frametime = clocks_per_second / update_rate; 10 | 11 | //these are to snap deltaTime to vsync values if it's close enough 12 | int64_t vsync_maxerror = clocks_per_second * .0002; 13 | 14 | //get the refresh rate of the display (you should detect which display the window is on in production) 15 | int display_framerate = 60; 16 | SDL_DisplayMode current_display_mode; 17 | if(SDL_GetCurrentDisplayMode(0, ¤t_display_mode)==0) { 18 | display_framerate = current_display_mode.refresh_rate; 19 | } 20 | int64_t snap_hz = display_framerate; 21 | if(snap_hz <= 0) snap_hz = 60; 22 | 23 | //these are to snap deltaTime to vsync values if it's close enough 24 | int64_t snap_frequencies[8] = {}; 25 | for(int i = 0; i<8; i++) { 26 | snap_frequencies[i] = (clocks_per_second / snap_hz) * (i+1); 27 | } 28 | 29 | //this is for delta time averaging 30 | //I know you can and should use a ring buffer for this, but I didn't want to include dependencies in this sample code 31 | const int time_history_count = 4; 32 | int64_t time_averager[time_history_count] = {desired_frametime, desired_frametime, desired_frametime, desired_frametime}; 33 | int64_t averager_residual = 0; 34 | 35 | //these are stored in my Application class and are not local variables in production code 36 | bool running = true; 37 | bool resync = true; 38 | int64_t prev_frame_time = SDL_GetPerformanceCounter(); 39 | int64_t frame_accumulator = 0; 40 | 41 | while (running){ 42 | //frame timer 43 | int64_t current_frame_time = SDL_GetPerformanceCounter(); 44 | int64_t delta_time = current_frame_time - prev_frame_time; 45 | prev_frame_time = current_frame_time; 46 | 47 | //handle unexpected timer anomalies (overflow, extra slow frames, etc) 48 | if(delta_time > desired_frametime*8){ //ignore extra-slow frames 49 | delta_time = desired_frametime; 50 | } 51 | if(delta_time < 0){ 52 | delta_time = 0; 53 | } 54 | 55 | 56 | //vsync time snapping 57 | for(int64_t snap : snap_frequencies){ 58 | if(std::abs(delta_time - snap) < vsync_maxerror){ 59 | delta_time = snap; 60 | break; 61 | } 62 | } 63 | 64 | //delta time averaging 65 | for(int i = 0; i desired_frametime*8){ 84 | resync = true; 85 | } 86 | 87 | //timer resync if requested 88 | if(resync) { 89 | frame_accumulator = 0; 90 | delta_time = desired_frametime; 91 | resync = false; 92 | } 93 | 94 | // process system events 95 | ProcessEvents(); 96 | 97 | if(unlock_framerate){ //UNLOCKED FRAMERATE, INTERPOLATION ENABLED 98 | int64_t consumedDeltaTime = delta_time; 99 | 100 | while(frame_accumulator >= desired_frametime){ 101 | game.fixed_update(fixed_deltatime); 102 | if(consumedDeltaTime > desired_frametime){ //cap variable update's dt to not be larger than fixed update, and interleave it (so game state can always get animation frames it needs) 103 | game.variable_update(fixed_deltatime); 104 | consumedDeltaTime -= desired_frametime; 105 | } 106 | frame_accumulator -= desired_frametime; 107 | } 108 | 109 | game.variable_update((double)consumedDeltaTime / clocks_per_second); 110 | game.render((double)frame_accumulator / desired_frametime); 111 | display(); //swap buffers 112 | 113 | } else { //LOCKED FRAMERATE, NO INTERPOLATION 114 | while(frame_accumulator >= desired_frametime*update_multiplicity){ 115 | for(int i = 0; i