Beryllium.FrameRateCounter
3.0.0
Prefix Reserved
dotnet add package Beryllium.FrameRateCounter --version 3.0.0
NuGet\Install-Package Beryllium.FrameRateCounter -Version 3.0.0
<PackageReference Include="Beryllium.FrameRateCounter" Version="3.0.0" />
<PackageVersion Include="Beryllium.FrameRateCounter" Version="3.0.0" />
<PackageReference Include="Beryllium.FrameRateCounter" />
paket add Beryllium.FrameRateCounter --version 3.0.0
#r "nuget: Beryllium.FrameRateCounter, 3.0.0"
#:package Beryllium.FrameRateCounter@3.0.0
#addin nuget:?package=Beryllium.FrameRateCounter&version=3.0.0
#tool nuget:?package=Beryllium.FrameRateCounter&version=3.0.0
Beryllium.FrameRateCounter
A simple FPS counter for .NET games. Call it once per frame, and it keeps track of:
- Frame rate — averaged over a short window (0.5 s by default) or recalculated every frame.
- Frame time — how long the last frame took.
- Worst frame time — the longest frame in the window, so a single stutter doesn't hide behind a good average.
It has no dependencies and works with MonoGame or any other game loop.
Installation
dotnet add package Beryllium.FrameRateCounter
Targets .NET Standard 2.0 and .NET 8, so it works on .NET 6 and later (including MonoGame 3.8.1 projects), .NET Framework 4.6.2 and later, and Mono.
Quick start (MonoGame)
Create a counter, call Update once per frame, and draw FrameRate:
using Beryllium;
public class Game1 : Game
{
private readonly FrameRateCounter _fps = new();
private string _fpsText = "FPS: 0";
// ...
protected override void Draw(GameTime gameTime)
{
if (_fps.Update())
_fpsText = $"FPS: {_fps.FrameRate}";
GraphicsDevice.Clear(Color.CornflowerBlue);
_spriteBatch.Begin();
_spriteBatch.DrawString(_font, _fpsText, new Vector2(10, 10), Color.White);
_spriteBatch.End();
base.Draw(gameTime);
}
}
_spriteBatch comes from the MonoGame template, and _font is any SpriteFont you load in LoadContent.
Update() measures the time since its previous call with a Stopwatch and returns true whenever FrameRate and WorstFrameTimePerRefresh have been recalculated (twice a second by default). Rebuilding the text only then avoids allocating a new string every frame.
Call
_fps.Update()inDraw, not inUpdate. With a fixed time step (MonoGame's default),Game.Updatecan run several times per rendered frame, so a counter updated there reports the update rate, not the number of frames actually drawn.
The counter keeps its own clock rather than reading gameTime.ElapsedGameTime: with a fixed time step, MonoGame hands Draw a whole number of TargetElapsedTime steps (capped at 500 ms) instead of the real frame duration, so frame times would come in 16.7 ms steps and long stalls would be under-reported.
Measuring the uncapped frame rate
By default, MonoGame caps the frame rate: the fixed time step limits it to 60 FPS, and VSync to the monitor's refresh rate. To see how fast your game can actually run, turn both off:
// In the Game1 constructor, after creating the GraphicsDeviceManager:
_graphics.SynchronizeWithVerticalRetrace = false;
IsFixedTimeStep = false;
Showing frame times
Frame times are in seconds; multiply by 1000 for milliseconds. At 60 FPS a frame takes about 16.7 ms, so a worst frame time well above that means the game stuttered, even if the average looks fine:
if (_fps.Update())
{
_statsText =
$"{_fps.FrameRate} FPS\n" +
$"Frame: {_fps.FrameTime * 1000:F1} ms\n" +
$"Worst: {_fps.WorstFrameTimePerRefresh * 1000:F1} ms";
}
_spriteBatch.DrawString(_font, _statsText, new Vector2(10, 10), Color.White);
Configuration
Averaging window
By default, FrameRate and WorstFrameTimePerRefresh are recalculated every 0.5 s from the frames in that window. A longer window gives steadier numbers; a shorter one reacts faster:
var fps = new FrameRateCounter { RefreshRateInSeconds = 1f };
Zero, negative, NaN, and infinite values are ignored.
Per-frame mode
Set UpdateEveryFrame to recalculate FrameRate from each frame's time alone. It reacts instantly but jumps around from frame to frame:
fps.UpdateEveryFrame = true;
In this mode RefreshRateInSeconds is not used, WorstFrameTimePerRefresh equals FrameTime, and Update returns true on every frame. Switching modes discards the frames collected for the current window, so the next averaged reading only covers frames recorded after the switch.
Resetting
Call Reset to start measuring from scratch, for example when a benchmark scene starts. It clears all readings and collected frames; UpdateEveryFrame and RefreshRateInSeconds keep their values. The next Update() call only restarts the clock, so time spent before the reset, such as loading the scene, doesn't count as a frame.
fps.Reset();
Other game loops
Update() keeps its own clock, so in a hand-written loop you only need to call it once per frame:
using Beryllium;
var fps = new FrameRateCounter();
while (running)
{
fps.Update();
RenderFrame();
}
If your engine already measures real frame times, pass the time since the previous frame, in seconds, to Update(double elapsedSeconds) instead.
API
| Member | Description |
|---|---|
bool Update() |
Records one frame, measuring the time since the previous Update() call with a Stopwatch. The first call, and the first after Reset, only starts the clock. Returns true if FrameRate and WorstFrameTimePerRefresh were recalculated. |
bool Update(double elapsedSeconds) |
Records one frame that took elapsedSeconds seconds. Returns true if FrameRate and WorstFrameTimePerRefresh were recalculated. |
void Reset() |
Clears all readings and collected frames, and restarts the Update() clock. Settings are kept. |
int FrameRate |
Frames per second, rounded to the nearest whole number: averaged over the last window, or based on the last frame alone if UpdateEveryFrame is true. |
float FrameTime |
Duration of the last recorded frame, in seconds. |
float WorstFrameTimePerRefresh |
Longest frame of the last window, in seconds. Equals FrameTime if UpdateEveryFrame is true. |
bool UpdateEveryFrame |
false (default): average over a window. true: recalculate every frame. Changing it discards the current window. |
float RefreshRateInSeconds |
Length of the averaging window, in seconds. Default 0.5. Zero, negative, NaN, and infinite values are ignored. |
In the default averaged mode, FrameRate and WorstFrameTimePerRefresh read 0 until the first window has passed.
License
Released under the MIT License.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- No dependencies.
-
net8.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 3.0.0 | 101 | 9/25/2026 |