it-swarm-id.com

Menghitung frame per detik dalam game

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

Poin bonus jika jawaban Anda memperbarui setiap frame dan tidak bertemu secara berbeda ketika frame rate meningkat vs menurun.

104
Tod

Anda memerlukan rata-rata yang dihaluskan, cara termudah adalah dengan mengambil jawaban saat ini (waktu untuk 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. Bagian yang lebih besar dari jawaban yang lama memberikan perubahan yang lebih lambat, bagian yang lebih besar dari jawaban yang baru memberikan nilai perubahan yang lebih cepat. Tentunya kedua faktor tersebut harus menambah satu!

94
Martin Beckett

Ini 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);
}
45
KPexEA

Ya tentu saja

frames / sec = 1 / (sec / frame)

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

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

Misalnya, Anda dapat mempertahankan struktur data antrian yang menahan waktu render untuk masing-masing dari 30, 60, 100, atau frame apa pun yang Anda miliki (Anda bahkan dapat mendesainnya sehingga batasnya dapat disesuaikan pada saat run-time). 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

Ketika Anda selesai merender bingkai baru Anda memerlukan waktu render baru dan waktu render lama. Sebagai alternatif, Anda hanya dapat keluar jika total waktu rendering melebihi beberapa nilai yang telah ditetapkan (mis. 1 detik). Anda dapat mempertahankan "nilai fps terakhir" dan cap waktu terakhir yang diperbarui sehingga Anda dapat memicu kapan harus memperbarui angka fps, jika diinginkan. Meskipun dengan rata-rata bergerak jika Anda memiliki pemformatan yang konsisten, mencetak fps "sesaat rata-rata" pada setiap bingkai mungkin akan baik-baik saja.

Metode lain adalah memiliki penghitung ulang. Pertahankan stempel waktu (milidetik) yang tepat, penghitung bingkai, dan nilai fps. Saat Anda selesai membuat bingkai, tambahkan penghitung. Saat penghitung mencapai batas yang ditentukan sebelumnya (mis. 100 frame) atau ketika waktu sejak cap waktu telah melewati beberapa nilai yang telah ditentukan sebelumnya (mis. 1 detik), hitung fps:

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

Kemudian atur ulang penghitung ke 0 dan atur timestamp ke waktu saat ini.

23
Wedge

Menambah penghitung setiap kali Anda membuat layar dan menghapus penghitung itu untuk beberapa interval waktu di mana Anda ingin mengukur frame-rate.

Yaitu. Setiap 3 detik, dapatkan penghitung/3 lalu kosongkan penghitungnya.

11
apandit

Setidaknya ada dua cara untuk melakukannya:


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

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

Menghitung fps dalam hal ini sesederhana mengevaluasi rumus ini:

  • FPS = cn/(time_now - time_start).

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

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

Example where i = 3

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

Kemudian, definisi matematis fps setelah i frame akan menjadi

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

Dan formula yang sama tetapi hanya mempertimbangkan frame i-1.

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

Sekarang triknya adalah memodifikasi sisi kanan formula (1) sedemikian rupa sehingga akan mengandung sisi kanan formula (2) dan menggantikannya dengan sisi kiri.

Seperti itu (Anda harus melihatnya dengan 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 (kemampuan matematika saya agak berkarat), untuk menghitung fps baru Anda perlu mengetahui fps dari frame sebelumnya, durasi yang diperlukan untuk membuat frame terakhir dan jumlah frame yang Anda miliki diberikan.

9
Peter Jankuliak

Ini mungkin berlebihan bagi kebanyakan orang, itu sebabnya saya belum memposting ketika saya menerapkannya. Tetapi sangat kuat dan fleksibel.

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

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

Ini juga memungkinkan Anda untuk mengubah jumlah frame untuk disimpan dalam Antrian saat berjalan, 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);
}
5
Petrucio

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

namun saya pribadi lebih suka bobot rata-rata saya lebih berat terhadap data yang lebih baru karena dalam permainan itu adalah SPIKES yang paling sulit untuk squash dan dengan demikian yang paling menarik bagi saya. Jadi saya akan menggunakan sesuatu yang lebih seperti .7\.3 split akan membuat lonjakan muncul lebih cepat (meskipun efeknya akan turun lebih cepat dari layar juga .. lihat di bawah)

Jika fokus Anda adalah pada waktu RENDERING, maka pemisahan .9.1 bekerja dengan sangat baik b/c cenderung lebih halus. THough untuk gameplay/AI/spike fisika jauh lebih memprihatinkan karena biasanya itulah yang membuat game Anda terlihat berombak (yang seringkali lebih buruk daripada frame rate rendah dengan asumsi kita tidak turun di bawah 20 fps)

Jadi, apa 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 temukan sebagai lonjakan yang tidak dapat diterima) Ini akan memungkinkan Anda menemukan dan karenanya selesaikan FPS mengeluarkan akhir frame yang terjadi.

2
David Frenkel

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

new_fps = old_fps * 0.99 + new_fps * 0.01

Metode ini menggunakan jauh lebih sedikit memori, membutuhkan jauh lebih sedikit kode, dan menempatkan lebih penting pada framerate baru daripada framerate lama sementara masih menghaluskan efek perubahan framerate tiba-tiba.

2
Barry Smith

Anda dapat menyimpan penghitung, menambahkannya setelah setiap frame dirender, lalu mengatur ulang penghitung saat Anda menggunakan detik baru (menyimpan nilai sebelumnya sebagai # frame yang diberikan detik terakhir)

1
Mike Stone

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

Berikut ini adalah contoh lengkap, menggunakan Python (tetapi mudah disesuaikan dengan bahasa apa pun). Ia menggunakan persamaan smoothing dalam jawaban Martin, jadi hampir tidak ada memori overhead, dan saya memilih nilai yang bekerja untuk saya (merasa bebas 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)
0
jd20

Setel penghitung ke nol. Setiap kali Anda menggambar bingkai, bertambahlah penghitung. Setelah setiap detik cetak penghitung. busa, bilas, ulangi. Jika Anda ingin kredit tambahan, simpan penghitung berjalan dan bagi dengan jumlah total detik untuk rata-rata berjalan.

0
Bryan Oakley
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;
        }
    }

});
0
Totty.js

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-kata, jam tick melacak ticks. Jika ini adalah pertama kalinya, dibutuhkan waktu saat ini dan meletakkannya di 'tickstart'. Setelah kutu pertama, itu membuat variabel 'fps' sama dengan berapa banyak kutu dari jam kutu dibagi dengan waktu dikurangi waktu kutu pertama.

Fps adalah bilangan bulat, karenanya "(int)".

0
BottleFact

Dalam (c ++ like) pseudocode keduanya adalah apa yang saya gunakan dalam aplikasi pemrosesan gambar industri yang harus memproses gambar dari satu set kamera yang dipicu secara eksternal. Variasi dalam "frame rate" memiliki sumber yang berbeda (produksi lebih lambat atau lebih cepat pada sabuk) tetapi masalahnya adalah sama. (Saya berasumsi bahwa Anda memiliki panggilan timer.peek () sederhana yang memberi Anda sesuatu seperti nr dari msec (nsec?) Sejak aplikasi mulai 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
   }
} 
0
jilles de wit

Inilah cara saya melakukannya (di Jawa):

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