Как правильно рассчитать FPS в XNA?

Я написал компонент для отображения текущего 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) И все это происходит только с кодом потока?
Да

Ответы

Ответ 1

У Шон Харгривз есть отличный пост об этом здесь. Первое отличие, которое я вижу между его кодом и вашим, - это то, что вы reset ваш elapseTime до 0 каждый раз, что потеряет некоторое время, тогда как Шон просто вычитает 1 секунду из своего прошедшего времени. Кроме того, Shawn использует ElapsedGameTime вместо ElapsedRealTime. Он обновляет свой frameCounter в функции Draw вместо функции Update.

Насколько он использует ElapsedRealTime, он объясняет это в комментарии после публикации:

> Конечно 1/gameTime.ElapsedRealTime.TotalSeconds

Таким образом,

> даст текущую частоту кадров.

Это скажет вам, как долго это было с предыдущего вызова Update, но это не то же самое, что битопоток!

a) Если игра отбрасывает кадры, Обновление будет вызываться чаще чтобы догнать. Вы хотите время количество фактических ничьей, которые а не только эти дополнительные догоняющие логические кадры.

b) Время для одного обновления может колеблются широко, поэтому цифра вы выйти из этого будет слишком мерцать легко читаемым.

Я бы попробовал его компонент и посмотрел, работает ли он на вас. Сообщение довольно старое, и я думаю, вам придется изменить LoadGraphicsContent на LoadContent и UnloadGraphicsContent на UnloadContent, как указывает другой комментарий.

Ответ 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 или один из Draw).

ElapsedRealTime - это время, прошедшее с начала игры. Из-за этого он увеличивается линейно по мере продолжения игры. Действительно, через 1 секунду вы обновите каждый кадр, потому что ваша логика выглядит так:

(Предположим, вы использовали 4 fps для удобства объяснения):

  • Кадр 1: ElapsedRealTime: 0,25. Сейчас всего: 0.25
  • Кадр 2: ElapsedRealTime: 0.5 Запуск всего сейчас: 0.75
  • Кадр 3: ElapsedRealTime: 0.75 Запуск всего сейчас: 1.5
  • Общее количество больше 1!!! Показать FPS!
  • Set Running total = 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!
  • Set Running total = 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!
  • Set Running total = 0

Это то, что вы ожидаете. Короче говоря, теперь, когда вы исправили первую проблему, вы также должны были скорректировать вторую и "почему" объясняется выше.

Ответ 3

В стороне... вам следует избегать установки приоритета потока. Назначая приоритет наивысшего потока тому, что должно быть фоновым потоком, вы можете в конечном итоге голодать на главный поток времени процессора, потому что планировщик будет отдавать приоритет threadPath

Ответ 4

Вы активно проверяете, изменяется ли IsRunningSlowly? Даже если IsFixedTimeStep соответствует true, если ваша программа не в состоянии выполнить столько обновлений, сколько она ожидает, она будет называть ее более часто.

Способ, которым я это смягчил, заключается в прямом вызове ResetElapsedTime() вместо того, чтобы отслеживать его самостоятельно.

Не уверен, что это сработает для вас. Я заметил, что когда я отлаживал предыдущую проблему, я бы не вызвал дополнительные обновления, возможно, "функцию" при отладке.

Ответ 5

Вы должны запустить счетчик fps по методу рисования

Ответ 6

Привет, ребята, если вы хотите показать свою реальную частоту кадров, вам нужно реализовать счетчик частоты кадров для метода Draw, потому что XNA делает это так: "Если ваш компьютер не может обслуживать метод Update, он приостанавливает метод Draw, а вместо него он служит методу Update"