I’m back!
I finally came up with an idea and figured out how to implement it. It’s simpler than I expected. I implemented a test solution and it works perfectly, the framerate counters can be refreshed more often than once per second, and changing the framerate allows you to display changes smoothly, just like FRAPS does. Below is a description of how to implement it.
Samples as timestamps
If a given counter is to count the frequency of performing certain activities per second (e.g. logic updates, renders, etc.), but is to be refreshed more often than once per second, then they cannot be counted in the classic way, i.e. adding 1
to some accumulator. This is because if you multiply the accumulated number to get the overall number of activities per second, the precision of the result will be very poor.
Instead, you should collect timestamps, retrieved just after performing a given action. These timestamps should be added to a list/queue so as to accumulate them over an interval equal or greater than a second. Timestamps can be milliseconds (taken using the SDL_GetTicks64 function), or in the form of hardware counter ticks, if we want the highest precision (the SDL_GetPerformanceCounter function is used for this).
Millisecond timestamps are sufficient to create typical update counters and renders per second.
Calculating the result
Having filled the list with timestamps, you now need to calculate the final result from them. If the list is empty or contains only one item, the result is 0
. This should be checked first to avoid dividing by 0
(more on this in a moment).
If there are more items, you need to get the value of the first and last item of the list and then calculate their difference — last - first
. If the result is less than 1000
, it means that not enough timestamps have been collected yet (the game runs for less than a second), so the result is simply the number of items in the list.
However, if the difference between the last and the first item is equal to or greater than 1000
, the game runs for at least a second and additional calculations must be performed. You should remove all timestamps that are too old, i.e. those at the beginning of the list. Enough of them should be removed so that after deletion, the difference between the last and the first item on the list is not less than 1000
. Finally, the number of list items is our framerate, in integer form.
To calculate framerate in floating-point form, you need to take into account not only the number of items, but also the period of time — and this period is the difference between the last and first timestamp of the list. Pseudocode calculating framerate in floating point form:
framerate = 1000.0 / (timestamp_last - timestamp_first) * timestamps_list.count;
The result will be an integer only if the difference of the last and first timestamp is perfectly 1000
— otherwise the result will be integer, e.g. 59.7
. For debugging, it may be useful to round it to, for example, two decimal places, while for the player the result can be rounded to one decimal place or even to a whole integer.
Summary
The above allows you to calculate the number of actions performed per second, more often than once per second. In this way, you can display, for example, a framerate counter that will refresh its value, e.g. 5 times per second, and it will actually be possible to obtain 5 different values per second. If the framerate drops sharply (e.g. from 60fps to 20fps), the counter will show 5 different values in one second, gradually decreasing from ~60
to ~20
.
Thanks to the solution proposed above, it is possible to refresh the framerate counter even in every frame of the game and this is the highest possible refresh rate of the counter. The lowest refresh rate is not specified — it could be once per 250ms, once per second, but could also be once per minute. However, it must be taken into account that the less often the counter is refreshed, the more timestamps will be added to the sample list, so the more memory the list will take up and the longer it will take to remove too old samples.
In my engine, I have specified that the range of available frequencies is from once per second (slowest) to ten times per second (fastest). More often it doesn’t make sense, it won’t be practical.
If I find time, I will try to provide complete pseudocode illustrating how to do the whole thing. The main loop in my engine is very complex and has a lot of features, so pasting its code into the post is pointless.