如何在XNA中正确计算FPS?

时间:2010-03-24 22:07:28

标签: xna frame-rate

我写了一个组件来显示当前的FPS 其中最重要的部分是:

    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);
    }

在大多数情况下它运作正常,但最近我遇到了问题 当我把下面的代码放入游戏的更新方法时,奇怪的东西开始发生。

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

此代码的主要思想是始终在不同的线程中运行pathFinding算法。

奇怪的是,我的意思是有时FPS急剧下降,这是显而易见的,但显示FPS变化的频率超过每秒一次。如果我理解这段代码,FPS的变化不会超过每秒一次。

有人可以解释一下发生了什么吗?

编辑26.03.2010
我也发布了Draw方法的代码。

编辑31.03.2010对Venesectrix问题的回答
1)您是在运行固定或可变时间步吗?
IsFixedTimeStep和SynchronizeWithVerticalRetrace设置为true 2)当发生这种情况时,您是否正在移动XNA窗口?
没有
3)XNA窗口是否有焦点?
是的
4)它是多么引人注目(即,更新速度如此之快以至于无法读取,或者只是几乎不会更新超过一秒钟)?
我能够阅读更新,FPS每秒更新~3次 5)所有这些只发生在那里的线程代码?

6 个答案:

答案 0 :(得分:12)

Shawn Hargreaves关于这个here的帖子很棒。我在他的代码和你的代码之间看到的第一个区别是你每次将你的elapseTime重置为0,这将失去一些时间,而Shawn只是从他的elapsedTime减去1秒。此外,Shawn使用ElapsedGameTime而不是ElapsedRealTime。他在Draw函数中更新了frameCounter,而不是Update函数。

至于他使用ElapsedRealTime的原因,他在帖子后的评论中解释了这一点:

  

>肯定是1 / gameTime.ElapsedRealTime.TotalSeconds

     

>因此将给出当前的帧速率。

     

这会告诉你它有多长   自上次调用Update以来,但是   这跟你的不一样   帧率!

     

a)如果游戏正在丢帧,   将更频繁地调用更新   为了赶上来。你想要时间   实际绘制的数量   发生,而不仅仅是这些额外的   追赶逻辑框架。

     

b)单次更新的时间可以   波动很大,所以数字你   摆脱它将太过火花   易于阅读。

我会尝试他的组件,看看它是否适合你。帖子很老了,我认为你必须将LoadGraphicsContent更改为LoadContent,将UnloadGraphicsContent更改为UnloadContent,正如另一条评论所指出的那样。

答案 1 :(得分:2)

以下是我使用此方法的方法:

  • 你平均超过n帧
  • 您可以将其与您选择的任何初始化方法一起使用
  • 应该很容易阅读并遵循
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);
    }
}
}

至于: “但是为什么显示FPS的变化频率超过每秒一次仍然是开放的”

ElapsedGameTime和ElapsedRealTime之间的区别在于“ElapsedGameTime”是自您上次进入Update或Draw语句以来的时间量(取决于您使用的“gameTime” - 来自Update的那个或来自绘制)。

ElapsedRealTime是游戏开始以来的时间。因此,随着游戏的继续运行,它会线性增加。实际上,1秒后,您将更新每一帧,因为您的逻辑看起来像这样:

(假设您为了便于说明而运行4 fps):

  • 第1帧:ElapsedRealTime:0.25。现在运行总计:0.25
  • 第2帧:ElapsedRealTime:0.5现在运行总计:0.75
  • 第3帧:ElapsedRealTime:0.75现在总计:1.5
  • 跑步总数大于1 !!!显示FPS!
  • 设置运行总计= 0
  • 第4帧:ElapsedRealTime:1.00现在总计:1.0
  • 跑步总数大于1 !!!显示FPS!

现在您已经修复了计数器,现在您应该只获得稳定的0.25经过的游戏时间变化,以便现在进展:

  • 第1帧:ElapsedGameTime:0.25。现在运行总计:0.25
  • 第2帧:ElapsedGameTime:0.25现在总计:0.50
  • 第3帧:ElapsedGameTime:0.25现在总计:0.75
  • 第4帧:ElapsedGameTime:0.25现在总计:1.00
  • 跑步总数大于1 !!!显示FPS!
  • 设置运行总计= 0

  • 第5帧:ElapsedGameTime:0.25。现在运行总计:0.25

  • 第6帧:ElapsedGameTime:0.25现在总计:0.50
  • 第7帧:ElapsedGameTime:0.25现在总计:0.75
  • 第8帧:ElapsedGameTime:0.25现在总计:1.00
  • 跑步总数大于1 !!!显示FPS!
  • 设置运行总计= 0

这是你所期待的。简而言之,既然您已经纠正了第一个问题,那么您应该更正第二个问题,并在上​​面解释“为什么”。

答案 2 :(得分:1)

顺便说一下......你应该避免设置线程优先级。通过将最高线程优先级分配给应该是后台线程,你可能最终会使cpu时间的主线程挨饿,因为调度程序将优先考虑threadPath

答案 3 :(得分:0)

您是否主动检查IsRunningSlowly是否正在更改?即使IsFixedTimeStep为true,如果您的程序无法执行预期的更新,它也会更频繁地调用它。

我以前通过直接调用ResetElapsedTime()而不是自己跟踪它来解决这个问题。

不确定这对你是否有用。我注意到,当我调试上一个问题时,它不会调用额外的更新,可能是调试时的“功能”。

答案 4 :(得分:0)

你应该在draw方法下运行你的fps计数器

答案 5 :(得分:0)

大家好,如果你想展示你真正的帧率,你必须实现方法Draw的帧率计数器,因为XNA这样做: &#34;如果您的计算机无法提供Update方法,则会暂停Draw方法,而不是提供Update方法&#34;