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
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Beryllium.FrameRateCounter" Version="3.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Beryllium.FrameRateCounter" Version="3.0.0" />
                    
Directory.Packages.props
<PackageReference Include="Beryllium.FrameRateCounter" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Beryllium.FrameRateCounter --version 3.0.0
                    
#r "nuget: Beryllium.FrameRateCounter, 3.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Beryllium.FrameRateCounter@3.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Beryllium.FrameRateCounter&version=3.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Beryllium.FrameRateCounter&version=3.0.0
                    
Install as a Cake Tool

Beryllium.FrameRateCounter

NuGet

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() in Draw, not in Update. With a fixed time step (MonoGame's default), Game.Update can 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .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