Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to correctly calculate FPS in XNA?

Tags:

xna

frame-rate

I wrote a component to display current FPS.
The most important part of it is:

    public override void Update(GameTime gameTime)
    {
        elapseTime += (float)gameTime.ElapsedRealTime.TotalSeconds;
        frameCounter++;

        if (elapseTime > 1)
        {
            FPS = frameCounter;
            frameCounter = 0;
            elapseTime = 0;
        }
        base.Update(gameTime);
    }


    public override void Draw(GameTime gameTime)
    {
        spriteBatch.Begin();

        spriteBatch.DrawString(font, "FPS " + ((int)FPS).ToString(), position, color, 0, origin, scale, SpriteEffects.None, 0);

        spriteBatch.End();

        base.Draw(gameTime);
    }

In most cases it works ok, but recently I had a problem.
When I put following code into Update method of game strange thing starts to happen.

       if (threadPath == null || threadPath.ThreadState != ThreadState.Running)
        {
            ThreadStart ts = new ThreadStart(current.PathFinder.FindPaths);
            threadPath = new Thread(ts);
            threadPath.Priority = ThreadPriority.Highest;
            threadPath.Start();
        }

Main idea of this code is to run pathFinding algorithm in different thread all the time.

By strange things I mean that sometimes FPS drasticly decreases, this is obvious, but displayed FPS changes more often than once a second. If I understand this code FPS can't change more often than once a second.

Can someone explain me what's going on?

Edit 26.03.2010
I've posted also code of Draw method.

Edit 31.03.2010 Answers to Venesectrix questions
1) are you running with a fixed or variable time step?
IsFixedTimeStep and SynchronizeWithVerticalRetrace is set to true.
2)Were you moving the XNA window around when this occurred?
No
3)Did the XNA window have focus?
Yes
4) How noticeable was it (ie, updating so fast you can't read it, or just barely updating more than a second)?
I was able to read updates, FPS was updating ~3 times a second.
5) And all of this only happens with the thread code in there?
Yes

like image 646
Tomek Tarczynski Avatar asked Mar 24 '10 22:03

Tomek Tarczynski


2 Answers

Shawn Hargreaves has a great post about this here. The first difference I see between his code and yours is the fact that you reset your elapseTime to 0 each time, which will lose some time, whereas Shawn just subtracts 1 second from his elapsedTime. Also, Shawn uses ElapsedGameTime instead of ElapsedRealTime. He updates his frameCounter in the Draw function instead of the Update function as well.

As far as why he uses ElapsedRealTime, he explains it in a comment after the post:

> Surely 1 / gameTime.ElapsedRealTime.TotalSeconds

> will therefore give the current framerate.

That will tell you how long it was since the previous call to Update, but that is not the same thing as your framerate!

a) If the game is dropping frames, Update will be called more frequently in order to catch up. You want to time the number of actual draws that are taking place, not just these extra catch-up logic frames.

b) The time for a single Update can fluctuate widely, so the figure you get out of that will be too flickery to be easily readable.

I would try his component, and see if it works for you. The post is pretty old, and I think you will have to change LoadGraphicsContent to LoadContent and UnloadGraphicsContent to UnloadContent, as another one of the comments points out.

like image 152
Venesectrix Avatar answered Sep 28 '22 17:09

Venesectrix


Here is how I do it and with this method:

  • You average over n Frames
  • You can use it with any initialization method you choose
  • It should be easy to read and follow
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace _60fps
{
public class Game1 : Microsoft.Xna.Framework.Game
{
    GraphicsDeviceManager graphics;
    SpriteBatch spriteBatch;
    SpriteFont OutputFont;
    float Fps = 0f;
    private const int NumberSamples = 50; //Update fps timer based on this number of samples
    int[] Samples = new int[NumberSamples];
    int CurrentSample = 0;
    int TicksAggregate = 0;
    int SecondSinceStart = 0;

    public Game1()
    {
        graphics = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";
    }

    protected override void Initialize()
    {
        base.Initialize();
        graphics.SynchronizeWithVerticalRetrace = false;
        int DesiredFrameRate = 60;
        TargetElapsedTime = new TimeSpan(TimeSpan.TicksPerSecond / DesiredFrameRate);
    }

    protected override void LoadContent()
    {
        spriteBatch = new SpriteBatch(GraphicsDevice);
        OutputFont = Content.Load<SpriteFont>("MessageFont");
    }

    protected override void UnloadContent()
    {/* Nothing to do */}

    protected override void Update(GameTime gameTime)
    {
        if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState(PlayerIndex.One).IsKeyDown(Keys.Escape))
            this.Exit();

        base.Update(gameTime);
    }

    private float Sum(int[] Samples)
    {
        float RetVal = 0f;
        for (int i = 0; i < Samples.Length; i++)
        {
            RetVal += (float)Samples[i];
        }
        return RetVal;
    }

    private Color ClearColor = Color.FromNonPremultiplied(20, 20, 40, 255);
    protected override void Draw(GameTime gameTime)
    {
        Samples[CurrentSample++] = (int)gameTime.ElapsedGameTime.Ticks;
        TicksAggregate += (int)gameTime.ElapsedGameTime.Ticks;
        if (TicksAggregate > TimeSpan.TicksPerSecond)
        {
            TicksAggregate -= (int)TimeSpan.TicksPerSecond;
            SecondSinceStart += 1;
        }
        if (CurrentSample == NumberSamples) //We are past the end of the array since the array is 0-based and NumberSamples is 1-based
        {
            float AverageFrameTime = Sum(Samples) / NumberSamples;
            Fps = TimeSpan.TicksPerSecond / AverageFrameTime;
            CurrentSample = 0;
        }

        GraphicsDevice.Clear(ClearColor);
        spriteBatch.Begin();
        if (Fps > 0)
        {
            spriteBatch.DrawString(OutputFont, string.Format("Current FPS: {0}\r\nTime since startup: {1}", Fps.ToString("000"), TimeSpan.FromSeconds(SecondSinceStart).ToString()), new Vector2(10,10), Color.White);
        }
        spriteBatch.End();
        base.Draw(gameTime);
    }
}
}

As for: "but the question why displayed FPS was changing more often than once a second is still open"

The difference between ElapsedGameTime and ElapsedRealTime is that "ElapsedGameTime" is the amount of time since the last time you entered the Update or Draw statement (depending on which "gameTime" you're using - the one from Update or the one from Draw).

ElapsedRealTime is the time since the game started. Because of this, it increases linearly as the game continues to run. Indeed, after 1 second, you'll update every frame because your logic looked like this:

(Let's assume you were running 4 fps for the sake of easy explanation):

  • Frame 1: ElapsedRealTime: 0.25. Running total now: 0.25
  • Frame 2: ElapsedRealTime: 0.5 Running total now: 0.75
  • Frame 3: ElapsedRealTime: 0.75 Running total now: 1.5
  • Running total greater than 1!!! Show FPS!
  • Set Running total = 0
  • Frame 4: ElapsedRealTime: 1.00 Running total now: 1.0
  • Running total greater than 1!!! Show FPS!

Now that you've fixed the counter, you should now only be getting Elapsed Game Time changes of a steady 0.25 so the progression now moves:

  • Frame 1: ElapsedGameTime: 0.25. Running total now: 0.25
  • Frame 2: ElapsedGameTime: 0.25 Running total now: 0.50
  • Frame 3: ElapsedGameTime: 0.25 Running total now: 0.75
  • Frame 4: ElapsedGameTime: 0.25 Running total now: 1.00
  • Running total greater than 1!!! Show FPS!
  • Set Running total = 0

  • Frame 5: ElapsedGameTime: 0.25. Running total now: 0.25

  • Frame 6: ElapsedGameTime: 0.25 Running total now: 0.50
  • Frame 7: ElapsedGameTime: 0.25 Running total now: 0.75
  • Frame 8: ElapsedGameTime: 0.25 Running total now: 1.00
  • Running total greater than 1!!! Show FPS!
  • Set Running total = 0

Which is what you're expecting. In short, now that you corrected the first problem, you should have corrected the second too and "why" is explained above.

like image 39
Dracorat Avatar answered Sep 28 '22 16:09

Dracorat