Высокая точность сна или Как вывести процессор и поддерживать точную частоту кадров - PullRequest
5 голосов
/ 29 января 2011

Я работаю над движком 2D-игры, в котором есть функция LimitFrameRate, чтобы гарантировать, что игра не запускается так быстро, что пользователь не может играть в игру.В этом игровом движке скорость игры привязана к частоте кадров.Поэтому обычно требуется ограничить частоту кадров до 60 кадров в секунду.Код этой функции относительно прост: вычислите количество времени, оставшееся до того, как мы должны начать работу над следующим кадром, преобразуйте его в миллисекунды, спите для этого количества миллисекунд (которое может быть 0), повторяйте, пока не наступит точное время, затем выход.Вот код:

public virtual void LimitFrameRate(int fps)
{
  long freq;
  long frame;
  freq = System.Diagnostics.Stopwatch.Frequency;
  frame = System.Diagnostics.Stopwatch.GetTimestamp();
  while ((frame - previousFrame) * fps < freq)
  {
     int sleepTime = (int)((previousFrame * fps + freq - frame * fps) * 1000 / (freq * fps));
     System.Threading.Thread.Sleep(sleepTime);
     frame = System.Diagnostics.Stopwatch.GetTimestamp();
  }
  previousFrame = frame;
}

Конечно, я обнаружил, что из-за неточной природы функции сна в некоторых системах частота кадров получается совершенно иной, чем ожидалось.Точность функции сна составляет всего около 15 миллисекунд, поэтому вы не можете ждать меньше этого.Странно то, что некоторые системы достигают идеальной частоты кадров с помощью этого кода и могут идеально достичь диапазона частот кадров.Но другие системы этого не делают.Я могу удалить функцию сна, и тогда другие системы достигнут частоты кадров, но затем они загружают процессор.

Я читал другие статьи о функции сна:

Что делать кодеру?Я не прошу гарантированную частоту кадров (или гарантированное время сна, другими словами), просто общее поведение.Я хотел бы иметь возможность поспать (например) 7 миллисекунд, чтобы выделить некоторый процессор для ОС, и чтобы он обычно возвращал управление за 7 миллисекунд или меньше (при условии, что он возвращает часть своего процессорного времени назад), и если это занимаетбольше иногда это нормально.Итак, у меня следующие вопросы:

  1. Почему сон работает идеально и точно в одних средах Windows, а не в других?(Есть ли способ получить одинаковое поведение во всех средах?)
  2. Как добиться точной частоты кадров , как правило, , не перегружая процессор из кода C #?

Ответы [ 3 ]

5 голосов
/ 29 января 2011

Вы можете использовать timeBeginPeriod , чтобы увеличить точность таймера / сна.Обратите внимание, что это глобально влияет на систему и может увеличить энергопотребление.Вы можете позвонить timeBeginPeriod(1) в начале вашей программы.В системах, где вы наблюдали более высокую точность таймера, другая работающая программа, вероятно, сделала это.И я не стал бы подсчитывать время ожидания и просто использовать sleep(1) в цикле.

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

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

Редактировать: Основываясь на идеях и комментариях в этом ответе,принятый ответ был сформулирован.

2 голосов
/ 30 января 2011

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

Хотя предложение CodeInChaos отвечает на ваш вопрос,и может работать частично в некоторых сценариях, это просто плохая практика.

Ограничение частоты кадров до желаемых 60 кадров в секунду будет работать только тогда, когда компьютер работает быстрее.Однако, во-вторых, если фоновое задание израсходует некоторую мощность процессора (например, запускается virusscanner), и ваша игра падает ниже 60 кадров в секунду, все будет идти намного медленнее.Даже при том, что ваша игра может быть идеально играема на скорости 35 кадров в секунду, это сделает невозможным играть в нее, потому что все идет наполовину быстрее.

Такие вещи, как сон, не помогут, потому что они останавливают ваш процесс в пользу другого процесса., этот процесс должен быть сначала остановлен, sleep (1ms) просто означает, что после 1ms ваш процесс возвращается в очередь, ожидающую разрешения на выполнение, поэтому sleep (1ms) может легко занять 15ms в зависимости от других запущенных процессов и их приоритетов.

Поэтому я предлагаю вам как можно быстрее использовать переменную "elapsedSeconds", которую вы используете во всех ваших методах обновления, чем раньше вы ее встроили, тем меньше будет работы, это также обеспечит совместимость.с МОНО.

Если у вас есть 2 части вашего движка, скажем, физика и движок рендеринга, и вы хотите запустить их на разных частотах кадров, просто посмотрите, сколько времени прошло с последнего кадра, а затем решите обновитьфизический движок или нет, если вы включите время с момента последнего обновления в свои вычисления, у вас все будет хорошо.

Также никогда не рисуйте чаще, чем двигаете.Рисовать в 2 раза один и тот же экран бесполезно, поэтому если единственный способ что-то изменить на экране, это обновить физический движок, а затем синхронизировать обновления движка рендеринга и физического движка.

0 голосов
/ 05 ноября 2015

Основываясь на идеях и комментариях к ответу CodeInChaos, это был последний код, к которому я пришел. Первоначально я отредактировал этот ответ, но CodeInChaos предложил, чтобы это был отдельный ответ.

public virtual void LimitFrameRate(int fps)
{
   long freq;
   long frame;
   freq = System.Diagnostics.Stopwatch.Frequency;
   frame = System.Diagnostics.Stopwatch.GetTimestamp();
   while ((frame - fpsStartTime) * fps < freq * fpsFrameCount)
   {
      int sleepTime = (int)((fpsStartTime * fps + freq * fpsFrameCount - frame * fps) * 1000 / (freq * fps));
      if (sleepTime > 0) System.Threading.Thread.Sleep(sleepTime);
      frame = System.Diagnostics.Stopwatch.GetTimestamp();
   }
   if (++fpsFrameCount > fps)
   {
      fpsFrameCount = 0;
      fpsStartTime = frame;
   }
}
...