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

ActionScript 3.0: A Dynamic Frame Rate Switching Class to Lower CPU Usage

Flash gets a lot of negative press because it’s seen as using a heap of CPU time and bogging everything down. And it’s a fair cop. Most flash will eat up your CPU cycles even when it’s sitting there doing nothing. But this isn’t a fault of flash, but rather of flash developers. Let me explain…

When you start a piece of flash work, you assign it a frame rate at which you want it to run, so it’ll update the screen, say, 30 times a second. This is all fine and good for when you’re animating things on the stage. But what about when you’re not? Well, it’s still running at 30 frames per second and chewing up your CPU like a crazy melon farmer. This is Not A Good Thing. So, anyhow, I’m watching this video about SWF Framerate Optimisation, and the guy’s showing how you can modify your code to lower the frame rate when there’s not a lot happening, and bring it back up when you’re animating. So I had a crack at it, and lo & behold, it works fine for the specific piece of flash I’d coded it into, so I wondered if I couldn’t just go and make a RateController class. This way, I could add a RateController object to any project to dynamically change the project’s frame rate depending on whether the mouse was over the stage or not.

And after much swearing about not having global access to the stage properties, I found that I COULD!!!

Here’s a working example placed into the attracting particles code I wrote yesterday:

Note: The animation starts at full speed for two seconds on startup. It’ll drop to the sleeping rate (5 fps) two seconds after the mouse leaves the stage, and then ramps back up to its waking rate (30 fps) instantly when the cursor is back over the stage. The FPSCounter shows intermediate numbers because it’s based on an average.

To add a RateController to any flash project, you can just use something like:

import RateController;
 
// Add a new RateController, uses the root stage, runs at 5fps when sleeping, 30fps 
// when active (i.e. when mouse is over the stage), and uses a 2000 millisecond
// delay after the mouse leaves the stage before dropping the FPS to the sleeping rate.
addChild(new RateController(stage, 5, 30, 2000));

Not bad, eh?

Full class code & file downloads after the jump…

Continue reading ActionScript 3.0: A Dynamic Frame Rate Switching Class to Lower CPU Usage