开发者

How to correctly calculate FPS in XNA?

开发者 https://www.devze.com 2022-12-24 05:42 出处:网络
I wrote a component to display current FPS. The most important part of it is: public override void Update(GameTime gameTime)

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 update开发者_JAVA技巧s, FPS was updating ~3 times a second.

5) And all of this only happens with the thread code in there?

Yes


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.


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.


As an aside ... you should avoid setting the thread priority. By assigning the highest thread priority to what should be a background thread, you could end up starving the main thread of cpu time because the scheduler would give priority to threadPath


Are you actively checking if IsRunningSlowly is being changed? Even with IsFixedTimeStep to true, if your program isn't able to do as many Updates as it expects, it will call it more frequently.

A way I have mitigated this before is by directly calling ResetElapsedTime() instead of keeping track of it yourself.

Not sure if that'll work for you though. I did notice that when i was debugging the previous issue I had, it wouldn't call the extra Updates, probably a 'feature' when debugging.


You should run your fps counter under the draw method


Hi guys if you want to show your real framerate you have to implement framerate counter to method Draw, because XNA do it like this: "If your computer can't serve the method Update it suspend Draw method and instead of that it serve the Update method"

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号