FpsManager – A C++ helper class for framerate independent movement

Update – September 2013: Fixed an issue whereby the enforceFPS function only returned the time it took to run the enforceFPS function itself because I forgot to add the frameDuration. Fixed another issue where I reset the frameCount to 1 when it should have been reset to 0. Oops…


I wrote only a few months back that I didn’t want to write another piece of FPS code, ever. But this was before I started taking framerate independent movement seriously. In my past coding I’ve just enabled VSync and been done with it – as long as the machine had enough processing capacity to perform at 60fps everything was fine, and nearly everything I wrote was so simple that it didn’t task the box too much.

However, as I’ve been working on a lot of Android code recently where the processing capacity of the device can easily vary by orders of magnitude, I’ve started thinking more that I really need to be able to cater to framerate changes gracefully. And for this, I’ve reinvented the wheel – if only to be absolutely sure in no uncertain terms about how the wheel frickn’ works.

So to put this to the test, I wrote the FpsManager class, and rewrote the camera from my old post on Simple OpenGL FPS Controls into a proper class suitable for reuse in multiple projects and capable of working in a framerate independent manner. That’s the next post…

…first things first: The FpsManager! ;-)

FpsManager.hpp

  1. #include <string>
  2. #include <sstream>
  3.  
  4. #ifndef __glfw_h_
  5.     #include <GL/glfw.h> // We need GLFW for this, so let's check for it- although it'd be a doddle to convert to non-GLFW using code.
  6. #endif
  7.  
  8. /** The FpsManager class is designed to work with GLFW and enforces a specified framerate on an application.
  9.   * It can also display the current framerate at user-specified intervals, and in addition returns the time
  10.   * duration since the last frame, which can be used to implement framerate independent movement.
  11.   *
  12.   * Author: r3dux
  13.   * Revision: 0.3
  14.   * Date: 1st September 2013
  15.   *
  16.   * ---- Creation examples (it's most useful to create your fpsManager object globally in your Main.cpp file): ----
  17.   *
  18.   *     FpsManager fpsManager(60.0);                    // Lock to 60fps, no reporting of framerate
  19.   *
  20.   *     FpsManager fpsManager(85.0, 3.0);               // Lock to 85fps, output FPS to console once every three seconds
  21.   *
  22.   *     FpsManager fpsManager(30.0, 0.5, "My App");     // Lock to 30fps, output FPS to console & window title every half second
  23.   *
  24.   *
  25.   * ---- Using the fpsManager in your main loop: ----
  26.   *
  27.   * bool running     = true;
  28.   * double deltaTime = 0.0;
  29.   *
  30.   * while (running)
  31.   * {
  32.   *     // Calculate our camera movement
  33.   *     cam->move(deltaTime);
  34.   *
  35.   *     // Draw our scene
  36.   *     drawScene();
  37.   *
  38.   *     // Exit if ESC was pressed or window was closed
  39.   *     running = !glfwGetKey(GLFW_KEY_ESC) && glfwGetWindowParam(GLFW_OPENED);
  40.   *
  41.   *     // Call our fpsManager to limit the FPS and get the frame duration to pass to the cam->move method
  42.   *     deltaTime = fpsManager.enforceFPS();
  43.   * }
  44.   *
  45.   * That's it! =D
  46.   */
  47.  
  48. class FpsManager
  49. {
  50.  
  51.     private:
  52.         double frameStartTime;         // Frame start time
  53.         double frameEndTime;           // Frame end time
  54.         double frameDuration;          // How many milliseconds between the last frame and this frame
  55.  
  56.         double targetFps;              // The desired FPS to run at (i.e. maxFPS)
  57.         double currentFps;             // The current FPS value
  58.         int    frameCount;             // How many frames have been drawn s
  59.  
  60.         double targetFrameDuration;    // How many milliseconds each frame should take to hit a target FPS value (i.e. 60fps = 1.0 / 60 = 0.016ms)
  61.         double sleepDuration;          // How long to sleep if we're exceeding the target frame rate duration
  62.  
  63.         double lastReportTime;         // The timestamp of when we last reported
  64.         double reportInterval;         // How often to update the FPS value
  65.  
  66.         std::string windowTitle;       // Window title to update view GLFW
  67.  
  68.         bool verbose;                  // Whether or not to output FPS details to the console or update the window
  69.  
  70.         // Limit the minimum and maximum target FPS value to relatively sane values
  71.         static const double MIN_TARGET_FPS = 20.0;
  72.         static const double MAX_TARGET_FPS = 60.0; // If you set this above the refresh of your monitor and enable VSync it'll break! Be aware!
  73.  
  74.         // Private method to set relatively sane defaults. Called by constructors before overwriting with more specific values as required.
  75.         void init(double theTargetFps, bool theVerboseSetting)
  76.         {
  77.             setTargetFps(theTargetFps);
  78.  
  79.             frameCount     = 0;
  80.             currentFps     = 0.0;
  81.             sleepDuration  = 0.0;
  82.             frameStartTime = glfwGetTime();
  83.             frameEndTime   = frameStartTime + 1;
  84.             frameDuration  = 1;
  85.             lastReportTime = frameStartTime;
  86.             reportInterval = 1.0f;
  87.             windowTitle    = "NONE";
  88.             verbose        = theVerboseSetting;
  89.         }
  90.  
  91.     public:
  92.  
  93.         // Single parameter constructor - just set a desired framerate and let it go.
  94.         // Note: No FPS reporting by default, although you can turn it on or off later with the setVerbose(true/false) method
  95.         FpsManager(int theTargetFps)
  96.         {
  97.             init(theTargetFps, false);
  98.         }
  99.  
  100.         // Two parameter constructor which sets a desired framerate and a reporting interval in seconds
  101.         FpsManager(int theTargetFps, double theReportInterval)
  102.         {
  103.             init(theTargetFps, true);
  104.  
  105.             setReportInterval(theReportInterval);
  106.         }
  107.  
  108.         // Three parameter constructor which sets a desired framerate, how often to report, and the window title to append the FPS to
  109.         FpsManager(int theTargetFps, float theReportInterval, std::string theWindowTitle)
  110.         {
  111.             init(theTargetFps, true); // If you specify a window title it's safe to say you want the FPS to update there ;)
  112.  
  113.             setReportInterval(theReportInterval);
  114.  
  115.             windowTitle = theWindowTitle;
  116.         }
  117.  
  118.         // Getter and setter for the verbose property
  119.         bool getVerbose()
  120.         {
  121.             return verbose;
  122.         }
  123.         void setVerbose(bool theVerboseValue)
  124.         {
  125.             verbose = theVerboseValue;
  126.         }
  127.  
  128.         // Getter and setter for the targetFps property
  129.         int getTargetFps()
  130.         {
  131.             return targetFps;
  132.         }
  133.  
  134.         void setTargetFps(int theFpsLimit)
  135.         {
  136.             // Make at least some attempt to sanitise the target FPS...
  137.             if (theFpsLimit < MIN_TARGET_FPS)
  138.             {
  139.                 theFpsLimit = MIN_TARGET_FPS;
  140.                 std::cout << "Limiting FPS rate to legal minimum of " << MIN_TARGET_FPS << " frames per second." << std::endl;
  141.             }
  142.             if (theFpsLimit > MAX_TARGET_FPS)
  143.             {
  144.                 theFpsLimit = MAX_TARGET_FPS;
  145.                 std::cout << "Limiting FPS rate to legal maximum of " << MAX_TARGET_FPS << " frames per second." << std::endl;
  146.             }
  147.  
  148.             // ...then set it and calculate the target duration of each frame at this framerate
  149.             targetFps = theFpsLimit;
  150.             targetFrameDuration = 1.0 / targetFps;
  151.         }
  152.  
  153.         double getFrameDuration() { return frameDuration; } // Returns the time it took to complete the last frame in milliseconds
  154.  
  155.         // Setter for the report interval (how often the FPS is reported) - santises input.
  156.         void setReportInterval(float theReportInterval)
  157.         {
  158.             // Ensure the time interval between FPS checks is sane (low cap = 0.1s, high-cap = 10.0s)
  159.             // Negative numbers are invalid, 10 fps checks per second at most, 1 every 10 secs at least.
  160.             if (theReportInterval < 0.1)
  161.             {
  162.                 theReportInterval = 0.1;
  163.             }
  164.             if (theReportInterval > 10.0)
  165.             {
  166.                 theReportInterval = 10.0;
  167.             }
  168.             reportInterval = theReportInterval;
  169.         }
  170.  
  171.         // Method to force our application to stick to a given frame rate and return how long it took to process a frame
  172.         double enforceFPS()
  173.         {
  174.             // Get the current time
  175.             frameEndTime = glfwGetTime();
  176.  
  177.             // Calculate how long it's been since the frameStartTime was set (at the end of this method)
  178.             frameDuration = frameEndTime - frameStartTime;
  179.  
  180.             if (reportInterval != 0.0f)
  181.             {
  182.  
  183.                 // Calculate and display the FPS every specified time interval
  184.                 if ((frameEndTime - lastReportTime) > reportInterval)
  185.                 {
  186.                     // Update the last report time to be now
  187.                     lastReportTime = frameEndTime;
  188.  
  189.                     // Calculate the FPS as the number of frames divided by the interval in seconds
  190.                     currentFps =  (double)frameCount / reportInterval;
  191.  
  192.                     // Reset the frame counter to 1 (and not zero - which would make our FPS values off)
  193.                     frameCount = 1;
  194.  
  195.                     if (verbose)
  196.                     {
  197.                         std::cout << "FPS: " << currentFps << std::endl;
  198.  
  199.                         // If the user specified a window title to append the FPS value to...
  200.                         if (windowTitle != "NONE")
  201.                         {
  202.                             // Convert the fps value into a string using an output stringstream
  203.                             std::ostringstream stream;
  204.                             stream << currentFps;
  205.                             std::string fpsString = stream.str();
  206.  
  207.                             // Append the FPS value to the window title details
  208.                             std::string tempWindowTitle = windowTitle + " | FPS: " + fpsString;
  209.  
  210.                             // Convert the new window title to a c_str and set it
  211.                             const char* pszConstString = tempWindowTitle.c_str();
  212.                             glfwSetWindowTitle(pszConstString);
  213.                         }
  214.  
  215.                     } // End of if verbose section
  216.  
  217.                 }
  218.                 else // FPS calculation time interval hasn't elapsed yet? Simply increment the FPS frame counter
  219.                 {
  220.                     ++frameCount;
  221.                 }
  222.  
  223.             } // End of if we specified a report interval section
  224.  
  225.             // Calculate how long we should sleep for to stick to our target frame rate
  226.             sleepDuration = targetFrameDuration - frameDuration;
  227.  
  228.             // If we're running faster than our target duration, sleep until we catch up!
  229.             if (sleepDuration > 0.0)
  230.                 glfwSleep(targetFrameDuration - frameDuration);
  231.  
  232.             // Reset the frame start time to be now - this means we only need put a single call into the main loop
  233.             frameStartTime = glfwGetTime();
  234.  
  235.             // Pass back our total frame duration (including any sleep and the time it took to run this function) to be used as our deltaTime value
  236.             return frameDuration + (frameStartTime - frameEndTime);
  237.  
  238.         } // End of our enforceFPS method
  239.  
  240. };

Comments? Suggestions? Think I’ve designed it badly, or quite well? Know why it works just fine (from a usability standpoint) but provides a framerate just under that requested?

Feel free to let me know in the comments below! Cheers! =D

7 thoughts on “FpsManager – A C++ helper class for framerate independent movement”

  1. Pretty cool implementation! I prefer less variables (smaller memory impact), and only use GL.h+GLU.h (With WinAPI)…
    Though this is VERY readable, and would be easy to debug (if as unlikely as it sounds, and error does occur)!

    Though counting the frames passed bothers me often, it really is the only way to accurately force a specific FPS
    (one that isn’t divisible without a remainder by 1000 – the number of milliseconds in a second)!

    This is how I usually do it (unless I need 100% accuracy):

    for (;;) { // Unconditional “for”!

    const DWORD StartTime = GetTickCount();

    if (PeekMessage(&Msg, NULL, 0, 0, PM_REMOVE)) {

    if (Msg.message == WM_QUIT) break;

    TranslateMessage(&Msg);
    DispatchMessage(&Msg);

    }

    Render();

    while ((GetTickCount()-StartTime) < 17) // (16 = floor(1000/60) – 1000 MS – 1 SEC)
    Sleep(1); // Make the game run at 60 FPS (iterations a sec)

    }

    Thank you very much for publishing this :)

  2. Hi Mitch and thanks for your post!

    I like your method, it’s minimal yet functional =D

    One of the problems I’ve been finding with fps limiting though is that calls to glfwSleep cause a sleep time which is amazingly innacurate! It’ll oversleep or undersleep like crazy! Although the time is very, very accurate the sleep period is really very ballpark… For example, I might ask to sleep for around 2ms – and then I sleep for like 19ms (more than a frame over!) – it’s killing me!

    So in an effort to see what I can do about it I’ve been playing with code that compensate for oversleeping.

    For example, let’s say I want to run at 50fps (25ms per frame) just to keep the numbers simple:
    – If a frame takes 12ms to complete, I should sleep for 13ms (i.e. 25 – 12 = 13ms),
    – So I attempt to sleep for 13ms, but I actually sleep for 19ms, so I’ve overslept by 6ms
    – Next frame, lets say it takes 11ms to complete, then I should sleep for 14ms (25 -11 = 14ms),
    – BUT I already overslept by 6ms, so I should really only sleep for 8ms to compensate (14 – 6 = 8ms)
    – and so on!

    I’ll post something up on it soon once I’ve had more time to try different ways of addressing / working-around the issue.

    Cheers!

    1. Yup, that’s a problem alright. There’s a lot of nice things about GLFW3, but I feel timing helpers to assist in running your main loop should be available for use.

      Instead of using glfwSleep, maybe try using usleep, or nanosleep or use Mitch’s method via getTickCount() in the comments above.

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.