Menghitung frame per detik dalam sebuah game

110

Apa algoritme yang bagus untuk menghitung frame per detik dalam sebuah game? Saya ingin menunjukkannya sebagai angka di sudut layar. Jika saya hanya melihat berapa lama waktu yang dibutuhkan untuk membuat bingkai terakhir, angkanya berubah terlalu cepat.

Poin bonus jika jawaban Anda memperbarui setiap bingkai dan tidak menyatu secara berbeda saat frekuensi gambar meningkat vs menurun.

Rubah
sumber

Jawaban:

100

Anda membutuhkan rata-rata yang diperhalus, cara termudah adalah dengan mengambil jawaban saat ini (waktu menggambar bingkai terakhir) dan menggabungkannya dengan jawaban sebelumnya.

// eg.
float smoothing = 0.9; // larger=more smoothing
measurement = (measurement * smoothing) + (current * (1.0-smoothing))

Dengan menyesuaikan rasio 0,9 / 0,1 Anda dapat mengubah 'konstanta waktu' - yaitu seberapa cepat angka tersebut merespons perubahan. Pecahan yang lebih besar mendukung jawaban lama memberikan perubahan yang lebih lambat dan lebih lambat, pecahan besar mendukung jawaban baru memberikan nilai perubahan yang lebih cepat. Jelaslah bahwa kedua faktor itu harus menjadi satu!

Martin Beckett
sumber
14
Kemudian untuk kesederhanaan dan kerapian, Anda mungkin menginginkan sesuatu seperti float weightRatio = 0.1; dan waktu = waktu * (1,0 - rasio berat) + kerangka_akhir * rasio berat
korona
2
Kedengarannya bagus dan pada prinsipnya sederhana, tetapi pada kenyataannya pemulusan dari pendekatan ini hampir tidak terlihat. Tidak baik.
Petrucio
1
@Petrucio jika smoothing terlalu rendah, cukup naikkan konstanta waktu (weightRatio = 0,05, 0,02, 0,01 ...)
John Dvorak
8
@Petrucio: last_frametidak berarti (atau setidaknya tidak berarti) durasi frame sebelumnya; itu harus berarti nilai timeyang Anda hitung untuk bingkai terakhir. Dengan cara ini, semua bingkai sebelumnya akan disertakan, dengan bingkai terbaru diberi bobot paling banyak.
j_random_hacker
1
Nama variabel "last_frame" menyesatkan. "current_frame" akan lebih deskriptif. Penting juga untuk mengetahui bahwa variabel "waktu" dalam contoh harus global (yaitu, pertahankan nilainya di semua bingkai) dan idealnya berupa angka floating point. Ini berisi nilai rata-rata / agregat dan diperbarui pada setiap frame. Semakin tinggi rasionya, semakin lama waktu yang dibutuhkan untuk menetapkan variabel "waktu" menuju suatu nilai (atau menjauhinya).
jox
52

Inilah yang saya gunakan di banyak game.

#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);
}
KPexEA
sumber
Saya sangat menyukai pendekatan ini. Adakah alasan khusus mengapa Anda menyetel MAXSAMPLES ke 100?
Zolomon
1
MAXSAMPLES di sini adalah jumlah nilai yang dirata-ratakan untuk menghasilkan nilai fps.
Cory Gross
8
Ini simple moving average (SMA)
KindDragon
Sempurna, terima kasih! Saya men-tweaknya dalam permainan saya sehingga fungsi centang tidak berlaku, dan fungsi lain mengembalikan FPS, lalu saya dapat menjalankan yang utama setiap centang, bahkan jika kode render tidak menampilkan FPS.
TheJosh
2
Harap gunakan modulo dan bukan jika. tickindex = (tickindex + 1) % MAXSAMPLES;
Felix K.
25

Ya, tentu

frames / sec = 1 / (sec / frame)

Namun, seperti yang Anda tunjukkan, ada banyak variasi dalam waktu yang diperlukan untuk merender satu frame, dan dari perspektif UI, memperbarui nilai fps pada frekuensi gambar tidak dapat digunakan sama sekali (kecuali jumlahnya sangat stabil).

Apa yang Anda inginkan mungkin adalah rata-rata bergerak atau semacam penghitung binning / reset.

Misalnya, Anda dapat mempertahankan struktur data antrian yang menahan waktu rendering untuk masing-masing frame 30, 60, 100, atau what-have-you terakhir (Anda bahkan dapat mendesainnya sehingga batasnya dapat disesuaikan pada waktu proses). Untuk menentukan perkiraan fps yang layak, Anda dapat menentukan fps rata-rata dari semua waktu rendering dalam antrian:

fps = # of rendering times in queue / total rendering time

Saat Anda selesai merender bingkai baru, Anda mengantrekan waktu rendering baru dan menghapus waktu rendering lama. Sebagai alternatif, Anda dapat melakukan dequeue hanya jika total waktu rendering melebihi beberapa nilai preset (mis. 1 detik). Anda dapat mempertahankan "nilai fps terakhir" dan stempel waktu terakhir diperbarui sehingga Anda dapat memicu kapan harus memperbarui angka fps, jika Anda menginginkannya. Meskipun dengan rata-rata bergerak jika Anda memiliki pemformatan yang konsisten, mencetak fps "rata-rata sesaat" pada setiap bingkai mungkin tidak masalah.

Metode lain adalah memiliki penghitung pengaturan ulang. Pertahankan stempel waktu yang tepat (milidetik), penghitung bingkai, dan nilai fps. Saat Anda selesai merender bingkai, tingkatkan penghitung. Ketika penghitung mencapai batas yang telah ditentukan sebelumnya (misalnya 100 bingkai) atau ketika waktu sejak cap waktu telah melewati beberapa nilai yang telah ditetapkan sebelumnya (misalnya 1 detik), hitung fps:

fps = # frames / (current time - start time)

Kemudian setel ulang penghitung ke 0 dan setel stempel waktu ke waktu saat ini.

Baji
sumber
12

Tingkatkan penghitung setiap kali Anda merender layar dan menghapus penghitung itu untuk beberapa interval waktu yang Anda inginkan untuk mengukur laju bingkai.

Yaitu. Setiap 3 detik, dapatkan penghitung / 3 lalu bersihkan penghitung.

apandit
sumber
+1 Meskipun ini hanya akan memberi Anda nilai baru dalam interval, ini mudah dipahami dan tidak memerlukan larik maupun menebak nilai dan benar secara ilmiah.
opatut
10

Setidaknya ada dua cara untuk melakukannya:


Yang pertama adalah yang disebutkan orang lain di sini sebelum saya. Saya pikir itu cara yang paling sederhana dan disukai. Anda hanya untuk melacak

  • cn: penghitung berapa banyak frame yang telah Anda render
  • time_start: waktu sejak Anda mulai menghitung
  • time_now: waktu saat ini

Menghitung fps dalam kasus ini semudah mengevaluasi rumus ini:

  • FPS = cn / (time_now - time_start).

Lalu ada cara paling keren yang mungkin ingin Anda gunakan suatu hari:

Katakanlah Anda memiliki bingkai 'i' untuk dipertimbangkan. Saya akan menggunakan notasi ini: f [0], f [1], ..., f [i-1] untuk menjelaskan berapa lama waktu yang dibutuhkan untuk merender frame 0, frame 1, ..., frame (i-1 ) masing-masing.

Example where i = 3

|f[0]      |f[1]         |f[2]   |
+----------+-------------+-------+------> time

Kemudian, definisi matematis dari fps setelah i frame akan menjadi

(1) fps[i]   = i     / (f[0] + ... + f[i-1])

Dan rumusnya sama tetapi hanya mempertimbangkan frame i-1.

(2) fps[i-1] = (i-1) / (f[0] + ... + f[i-2]) 

Sekarang triknya di sini adalah memodifikasi sisi kanan rumus (1) sedemikian rupa sehingga akan berisi sisi kanan rumus (2) dan menggantinya dengan sisi kirinya.

Suka begitu (Anda akan melihatnya lebih jelas jika Anda menulisnya di atas kertas):

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)

Jadi menurut rumus ini (keterampilan matematika saya agak berkarat), untuk menghitung fps baru Anda perlu mengetahui fps dari frame sebelumnya, durasi yang dibutuhkan untuk membuat frame terakhir dan jumlah frame yang Anda miliki. diberikan.

Peter Jankuliak
sumber
1
1 untuk metode kedua. Saya membayangkan itu akan bagus untuk komputasi yang tepat: 3
zeboidlund
5

Ini mungkin berlebihan bagi kebanyakan orang, itulah mengapa saya tidak mempostingnya saat saya menerapkannya. Tapi itu sangat kuat dan fleksibel.

Ini menyimpan Antrean dengan waktu bingkai terakhir, sehingga secara akurat dapat menghitung nilai FPS rata-rata jauh lebih baik daripada hanya mempertimbangkan bingkai terakhir.

Ini juga memungkinkan Anda untuk mengabaikan satu frame, jika Anda melakukan sesuatu yang Anda tahu akan mengacaukan waktu frame itu secara artifisial.

Ini juga memungkinkan Anda untuk mengubah jumlah bingkai yang akan disimpan dalam Antrean saat dijalankan, sehingga Anda dapat mengujinya dengan cepat apa nilai terbaik untuk Anda.

// 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);
}
Petrucio
sumber
2

Jawaban bagus di sini. Hanya bagaimana Anda menerapkannya tergantung pada apa yang Anda butuhkan. Saya lebih suka running average sendiri "time = time * 0.9 + last_frame * 0.1" oleh orang di atas.

namun saya pribadi lebih suka membobotkan rata-rata saya lebih banyak ke data yang lebih baru karena dalam permainan itu SPIKES yang paling sulit untuk dihancurkan dan karenanya paling menarik bagi saya. Jadi saya akan menggunakan sesuatu yang lebih seperti pemisahan .7 \ .3 akan membuat lonjakan muncul lebih cepat (meskipun efeknya akan turun layar lebih cepat juga .. lihat di bawah)

Jika fokus Anda adalah pada waktu RENDERING, maka pemisahan .9.1 berfungsi cukup baik karena cenderung lebih mulus. Meskipun untuk gameplay / AI / physics spikes lebih menjadi perhatian karena ITU biasanya akan membuat game Anda terlihat berombak (yang seringkali lebih buruk daripada frame rate rendah dengan asumsi kami tidak turun di bawah 20 fps)

Jadi, yang akan saya lakukan adalah menambahkan sesuatu seperti ini:

#define ONE_OVER_FPS (1.0f/60.0f)
static float g_SpikeGuardBreakpoint = 3.0f * ONE_OVER_FPS;
if(time > g_SpikeGuardBreakpoint)
    DoInternalBreakpoint()

(isi 3.0f dengan besaran apa pun yang Anda anggap sebagai lonjakan yang tidak dapat diterima) Ini akan memungkinkan Anda menemukan dan dengan demikian menyelesaikan masalah FPS pada akhir frame yang terjadi.

David Frenkel
sumber
Saya suka time = time * 0.9 + last_frame * 0.1kalkulasi rata - rata yang membuat tampilan berubah dengan mulus.
Fabien Quatravaux
2

Sistem yang jauh lebih baik daripada menggunakan sejumlah besar framerate lama adalah dengan melakukan sesuatu seperti ini:

new_fps = old_fps * 0.99 + new_fps * 0.01

Metode ini menggunakan memori yang jauh lebih sedikit, memerlukan kode yang jauh lebih sedikit, dan lebih mementingkan frekuensi gambar terbaru daripada frekuensi gambar lama sambil tetap menghaluskan efek perubahan frekuensi gambar yang tiba-tiba.

Barry Smith
sumber
1

Anda dapat menyimpan penghitung, menaikkannya setelah setiap bingkai dirender, lalu mengatur ulang penghitung saat Anda berada di detik baru (menyimpan nilai sebelumnya sebagai # bingkai detik terakhir yang dirender)

Mike Stone
sumber
1

JavaScript:

// 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));
Ephellon Dantzler
sumber
1

Berikut contoh lengkapnya, menggunakan Python (tetapi mudah disesuaikan dengan bahasa apa pun). Ini menggunakan persamaan pemulusan dalam jawaban Martin, jadi hampir tidak ada overhead memori, dan saya memilih nilai yang berhasil untuk saya (jangan ragu untuk bermain-main dengan konstanta untuk beradaptasi dengan kasus penggunaan Anda).

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)
jd20
sumber
0

Setel penghitung ke nol. Setiap kali Anda menggambar bingkai, tambahkan penghitung. Setelah setiap detik mencetak penghitung. busa, bilas, ulangi. Jika Anda menginginkan kredit ekstra, pertahankan penghitung lari dan bagi dengan jumlah total detik untuk rata-rata lari.

Bryan Oakley
sumber
0

Dalam pseudocode (c ++ like), keduanya adalah yang saya gunakan dalam aplikasi pemrosesan gambar industri yang harus memproses gambar dari satu set kamera yang dipicu secara eksternal. Variasi dalam "kecepatan bingkai" memiliki sumber yang berbeda (produksi lebih lambat atau lebih cepat pada sabuk) tetapi masalahnya sama. (Saya berasumsi bahwa Anda memiliki panggilan timer.peek () sederhana yang memberi Anda sesuatu seperti nr msec (nsec?) Sejak aplikasi dimulai atau panggilan terakhir)

Solusi 1: Cepat tetapi tidak diperbarui setiap frame

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

Solusi 2: Memperbarui setiap frame, membutuhkan lebih banyak memori dan 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
   }
} 
jilles de wit
sumber
0
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;
        }
    }

});
Totty.js
sumber
0

Bagaimana saya melakukannya!

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

Dengan kata lain, detak jam melacak detak. Jika ini pertama kalinya, ini mengambil waktu saat ini dan meletakkannya di 'tickstart'. Setelah tick pertama, variabel 'fps' sama dengan jumlah tick pada tick clock dibagi waktu dikurangi waktu dari tick pertama.

Fps adalah integer, oleh karena itu "(int)".

BotolFact
sumber
1
Tidak akan merekomendasikan untuk siapa pun. Membagi jumlah total tanda centang dengan jumlah total detik membuat pendekatan FPS seperti batas matematis, yang pada dasarnya menetapkan 2-3 nilai setelah waktu yang lama, dan menampilkan hasil yang tidak akurat.
Kartik Chugh
0

Begini cara saya melakukannya (di 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
}
petualangOK
sumber
0

Di Ketik, saya menggunakan algoritma ini untuk menghitung rata-rata framerate dan frametime:

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

pemakaian:

statsUpdate();

// Print
stats.innerHTML = Math.round(framerate) + ' FPS ' + frametime.toFixed(2) + ' ms';

Tip: Jika sampel adalah 1, hasilnya adalah framerate dan frametime waktu nyata.

Donavan Carvalho
sumber
0

Ini didasarkan pada jawaban KPexEA dan memberikan Rata-Rata Bergerak Sederhana. Dirapikan dan diubah menjadi TypeScript agar mudah disalin dan ditempel:

Deklarasi variabel:

fpsObject = {
  maxSamples: 100,
  tickIndex: 0,
  tickSum: 0,
  tickList: []
}

Fungsi:

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

Penggunaan (mungkin berbeda di aplikasi Anda):

this.fps = this.calculateFps(this.ticker.FPS)
danday74
sumber
-1

menyimpan waktu mulai dan menaikkan framecounter Anda sekali per loop? setiap beberapa detik Anda cukup mencetak framecount / (Now - starttime) dan kemudian menginisialisasi ulang.

edit: oops. ninja ganda

Jimmy
sumber