回答:
平滑化された平均が必要です。最も簡単な方法は、現在の回答(最後のフレームを描画する時間)を取得し、それを前の回答と組み合わせる方法です。
// eg.
float smoothing = 0.9; // larger=more smoothing
measurement = (measurement * smoothing) + (current * (1.0-smoothing))
0.9 / 0.1の比率を調整することで、「時定数」を変更できます。これは、数値が変化にどのくらい速く応答するかを示します。古い回答を支持する大きな割合はゆっくりとした滑らかな変化を与え、新しい回答を支持する大きな割合はより速い変化値を与えます。明らかに、2つの要素を1つに追加する必要があります。
last_frame
を意味するものではない(または少なくともべきではない平均値)は、前フレームの期間; time
最後のフレームで計算した値を意味するはずです。このようにして、以前のすべてのフレームが含まれ、最新のフレームが最も大きく重み付けされます。
これは私が多くのゲームで使用したものです。
#define MAXSAMPLES 100
int tickindex=0;
int ticksum=0;
int ticklist[MAXSAMPLES];
/* need to zero out the ticklist array before starting */
/* average will ramp up until the buffer is full */
/* returns average ticks per frame over the MAXSAMPLES last frames */
double CalcAverageTick(int newtick)
{
ticksum-=ticklist[tickindex]; /* subtract value falling off */
ticksum+=newtick; /* add new value */
ticklist[tickindex]=newtick; /* save new value so it can be subtracted later */
if(++tickindex==MAXSAMPLES) /* inc buffer index */
tickindex=0;
/* return average */
return((double)ticksum/MAXSAMPLES);
}
tickindex = (tickindex + 1) % MAXSAMPLES;
まあ、確かに
frames / sec = 1 / (sec / frame)
ただし、ご指摘のとおり、単一のフレームのレンダリングにかかる時間にはさまざまなバリエーションがあり、UIの観点からは、フレームレートでのfps値の更新はまったく使用できません(数値が非常に安定している場合を除く)。
あなたが望むのは、おそらく移動平均か、ある種のビニング/リセットカウンターです。
たとえば、最後の30、60、100、または使用済みフレームのそれぞれのレンダリング時間を保持するキューデータ構造を維持できます(実行時に制限を調整できるように設計することもできます)。適切なfpsの近似を決定するには、キュー内のすべてのレンダリング時間から平均fpsを決定できます。
fps = # of rendering times in queue / total rendering time
新しいフレームのレンダリングが終了したら、新しいレンダリング時間をエンキューし、古いレンダリング時間をデキューします。あるいは、レンダリング時間の合計が事前に設定された値(1秒など)を超えた場合にのみ、デキューできます。「最終fps値」と最終更新タイムスタンプを維持できるので、必要に応じて、fps数値を更新するタイミングをトリガーできます。一貫したフォーマットを使用している場合は移動平均を使用しますが、各フレームに「瞬時の平均」fpsを印刷することはおそらく問題ありません。
別の方法は、リセットカウンターを使用することです。正確な(ミリ秒)タイムスタンプ、フレームカウンター、およびfps値を維持します。フレームのレンダリングが終了したら、カウンターを増分します。カウンターがあらかじめ設定された制限(100フレームなど)に達したとき、またはタイムスタンプからの時間があらかじめ設定された値(たとえば1秒)を経過したときに、fpsを計算します。
fps = # frames / (current time - start time)
次に、カウンターを0にリセットし、タイムスタンプを現在の時刻に設定します。
それには、少なくとも2つの方法があります。
1つ目は、他の人が私の前でここで言及したものです。私はそれが最も簡単で好ましい方法だと思います。あなただけを追跡する
この場合のfpsの計算は、この式を評価するのと同じくらい簡単です。
次に、いつか使用したくなる超クールな方法があります:
考慮すべき「i」フレームがあるとしましょう。私はこの表記を使用します:f [0]、f [1]、...、f [i-1]は、フレーム0、フレーム1、...、フレーム(i-1 )それぞれ。
Example where i = 3
|f[0] |f[1] |f[2] |
+----------+-------------+-------+------> time
次に、iフレーム後のfpsの数学的定義は次のようになります。
(1) fps[i] = i / (f[0] + ... + f[i-1])
同じ式ですが、i-1フレームのみを考慮しています。
(2) fps[i-1] = (i-1) / (f[0] + ... + f[i-2])
ここでの秘訣は、式(1)の右辺を変更して、式(2)の右辺が含まれるようにし、それを左辺に置き換えることです。
そうです(紙に書けばもっとはっきり見えるはずです):
fps[i] = i / (f[0] + ... + f[i-1])
= i / ((f[0] + ... + f[i-2]) + f[i-1])
= (i/(i-1)) / ((f[0] + ... + f[i-2])/(i-1) + f[i-1]/(i-1))
= (i/(i-1)) / (1/fps[i-1] + f[i-1]/(i-1))
= ...
= (i*fps[i-1]) / (f[i-1] * fps[i-1] + i - 1)
したがって、この式に従って(私の数学の導出スキルは少し錆びていますが)、前のフレームからのfps、最後のフレームのレンダリングにかかった時間、およびフレーム数を知る必要がある新しいfpsを計算するにはレンダリングされました。
これはほとんどの人にとってやり過ぎかもしれません。そのため、実装したときに投稿していませんでした。しかし、それは非常に堅牢で柔軟です。
キューには最後のフレーム時間を格納するため、最後のフレームを考慮するだけでなく、平均FPS値を正確に計算できます。
また、そのフレームの時間を人為的に台無しにすることがわかっていることを実行している場合、1つのフレームを無視することもできます。
また、実行時にキューに保存するフレーム数を変更できるため、最適な値をその場でテストできます。
// Number of past frames to use for FPS smooth calculation - because
// Unity's smoothedDeltaTime, well - it kinda sucks
private int frameTimesSize = 60;
// A Queue is the perfect data structure for the smoothed FPS task;
// new values in, old values out
private Queue<float> frameTimes;
// Not really needed, but used for faster updating then processing
// the entire queue every frame
private float __frameTimesSum = 0;
// Flag to ignore the next frame when performing a heavy one-time operation
// (like changing resolution)
private bool _fpsIgnoreNextFrame = false;
//=============================================================================
// Call this after doing a heavy operation that will screw up with FPS calculation
void FPSIgnoreNextFrame() {
this._fpsIgnoreNextFrame = true;
}
//=============================================================================
// Smoothed FPS counter updating
void Update()
{
if (this._fpsIgnoreNextFrame) {
this._fpsIgnoreNextFrame = false;
return;
}
// While looping here allows the frameTimesSize member to be changed dinamically
while (this.frameTimes.Count >= this.frameTimesSize) {
this.__frameTimesSum -= this.frameTimes.Dequeue();
}
while (this.frameTimes.Count < this.frameTimesSize) {
this.__frameTimesSum += Time.deltaTime;
this.frameTimes.Enqueue(Time.deltaTime);
}
}
//=============================================================================
// Public function to get smoothed FPS values
public int GetSmoothedFPS() {
return (int)(this.frameTimesSize / this.__frameTimesSum * Time.timeScale);
}
ここで良い答え。どのように実装するかは、何のために必要かによって異なります。私は上記の人による移動平均1つ「時間=時間* 0.9 +最終フレーム* 0.1」を好んでいます。
しかし、私は個人的には、平均をより新しいデータに重きを置きたいと思っています。なぜなら、ゲームでは、スカッシュが最も難しく、私にとって最も興味深いスパイクだからです。したがって、.7 \ .3分割のようなものを使用すると、スパイクの表示がはるかに速くなります(ただし、効果が画面外にドロップする速度も速くなります。以下を参照してください)。
レンダリング時間に焦点を当てている場合、.9.1分割はかなりスムーズに機能します。ただし、ゲームプレイ/ AI /物理演算のスパイクは、通常、ゲームが途切れるように見えるため、はるかに懸念事項です(20 fpsを下回っていないと仮定すると、低フレームレートよりも悪い場合が多い)。
だから、私がやろうとしていることも、次のようなものを追加することです:
#define ONE_OVER_FPS (1.0f/60.0f)
static float g_SpikeGuardBreakpoint = 3.0f * ONE_OVER_FPS;
if(time > g_SpikeGuardBreakpoint)
DoInternalBreakpoint()
(容認できないスパイクであることがわかったマグニチュードを3.0fに入力します)これにより、FPSの問題を見つけて、フレームの最後で発生する問題を解決できます。
time = time * 0.9 + last_frame * 0.1
表示がスムーズに変化する平均計算が好きです。
// Set the end and start times
var start = (new Date).getTime(), end, FPS;
/* ...
* the loop/block your want to watch
* ...
*/
end = (new Date).getTime();
// since the times are by millisecond, use 1000 (1000ms = 1s)
// then multiply the result by (MaxFPS / 1000)
// FPS = (1000 - (end - start)) * (MaxFPS / 1000)
FPS = Math.round((1000 - (end - start)) * (60 / 1000));
Pythonを使用した完全な例を次に示します(ただし、任意の言語に簡単に適応できます)。マーティンの答えの平滑化方程式を使用しているため、メモリのオーバーヘッドはほとんどなく、私はうまくいく値を選択しました(定数をいじって、ユースケースに適応してみてください)。
import time
SMOOTHING_FACTOR = 0.99
MAX_FPS = 10000
avg_fps = -1
last_tick = time.time()
while True:
# <Do your rendering work here...>
current_tick = time.time()
# Ensure we don't get crazy large frame rates, by capping to MAX_FPS
current_fps = 1.0 / max(current_tick - last_tick, 1.0/MAX_FPS)
last_tick = current_tick
if avg_fps < 0:
avg_fps = current_fps
else:
avg_fps = (avg_fps * SMOOTHING_FACTOR) + (current_fps * (1-SMOOTHING_FACTOR))
print(avg_fps)
カウンターをゼロに設定します。フレームを描くたびに、カウンターが増加します。1秒ごとにカウンターを印刷します。泡立て、すすぎ、繰り返します。追加のクレジットが必要な場合は、実行中のカウンターを維持し、移動平均の合計秒数で割ります。
(c ++のような)疑似コードでは、これら2つは、外部トリガーカメラのセットからの画像を処理する必要があった産業用画像処理アプリケーションで使用したものです。「フレームレート」の変動にはさまざまな原因がありましたが(ベルトでの生成が遅いまたは速い)、問題は同じです。(私はあなたが単純なtimer.peek()呼び出しを持っていると思います、それはあなたがアプリケーションの開始または最後の呼び出し以来のmsec(nsec?)のnrのようなものを与えます)
解決策1:高速だがすべてのフレームが更新されない
do while (1)
{
ProcessImage(frame)
if (frame.framenumber%poll_interval==0)
{
new_time=timer.peek()
framerate=poll_interval/(new_time - last_time)
last_time=new_time
}
}
解決策2:フレームごとに更新され、より多くのメモリとCPUが必要
do while (1)
{
ProcessImage(frame)
new_time=timer.peek()
delta=new_time - last_time
last_time = new_time
total_time += delta
delta_history.push(delta)
framerate= delta_history.length() / total_time
while (delta_history.length() > avg_interval)
{
oldest_delta = delta_history.pop()
total_time -= oldest_delta
}
}
qx.Class.define('FpsCounter', {
extend: qx.core.Object
,properties: {
}
,events: {
}
,construct: function(){
this.base(arguments);
this.restart();
}
,statics: {
}
,members: {
restart: function(){
this.__frames = [];
}
,addFrame: function(){
this.__frames.push(new Date());
}
,getFps: function(averageFrames){
debugger;
if(!averageFrames){
averageFrames = 2;
}
var time = 0;
var l = this.__frames.length;
var i = averageFrames;
while(i > 0){
if(l - i - 1 >= 0){
time += this.__frames[l - i] - this.__frames[l - i - 1];
}
i--;
}
var fps = averageFrames / time * 1000;
return fps;
}
}
});
どうやってやるんだ!
boolean run = false;
int ticks = 0;
long tickstart;
int fps;
public void loop()
{
if(this.ticks==0)
{
this.tickstart = System.currentTimeMillis();
}
this.ticks++;
this.fps = (int)this.ticks / (System.currentTimeMillis()-this.tickstart);
}
つまり、ティッククロックはティックを追跡します。初めての場合は、現在の時間を使用して「tickstart」に入れます。最初のティックの後、変数 'fps'は、時間から最初のティックの時間を引いたもので割ったティッククロックのティック数を等しくします。
Fpsは整数であるため、「(int)」です。
ここに私がそれをする方法があります(Javaで):
private static long ONE_SECOND = 1000000L * 1000L; //1 second is 1000ms which is 1000000ns
LinkedList<Long> frames = new LinkedList<>(); //List of frames within 1 second
public int calcFPS(){
long time = System.nanoTime(); //Current time in nano seconds
frames.add(time); //Add this frame to the list
while(true){
long f = frames.getFirst(); //Look at the first element in frames
if(time - f > ONE_SECOND){ //If it was more than 1 second ago
frames.remove(); //Remove it from the list of frames
} else break;
/*If it was within 1 second we know that all other frames in the list
* are also within 1 second
*/
}
return frames.size(); //Return the size of the list
}
Typescriptでは、このアルゴリズムを使用してフレームレートとフレーム時間の平均を計算します。
let getTime = () => {
return new Date().getTime();
}
let frames: any[] = [];
let previousTime = getTime();
let framerate:number = 0;
let frametime:number = 0;
let updateStats = (samples:number=60) => {
samples = Math.max(samples, 1) >> 0;
if (frames.length === samples) {
let currentTime: number = getTime() - previousTime;
frametime = currentTime / samples;
framerate = 1000 * samples / currentTime;
previousTime = getTime();
frames = [];
}
frames.push(1);
}
使用法:
statsUpdate();
// Print
stats.innerHTML = Math.round(framerate) + ' FPS ' + frametime.toFixed(2) + ' ms';
ヒント:サンプルが1の場合、結果はリアルタイムのフレームレートとフレーム時間になります。
これはKPexEAの回答に基づいており、単純移動平均を提供します。簡単にコピーアンドペーストできるように整頓され、TypeScriptに変換されます。
変数宣言:
fpsObject = {
maxSamples: 100,
tickIndex: 0,
tickSum: 0,
tickList: []
}
関数:
calculateFps(currentFps: number): number {
this.fpsObject.tickSum -= this.fpsObject.tickList[this.fpsObject.tickIndex] || 0
this.fpsObject.tickSum += currentFps
this.fpsObject.tickList[this.fpsObject.tickIndex] = currentFps
if (++this.fpsObject.tickIndex === this.fpsObject.maxSamples) this.fpsObject.tickIndex = 0
const smoothedFps = this.fpsObject.tickSum / this.fpsObject.maxSamples
return Math.floor(smoothedFps)
}
使用法(アプリによって異なる場合があります):
this.fps = this.calculateFps(this.ticker.FPS)