it-swarm-eu.dev

Berechnen von Bildern pro Sekunde in einem Spiel

Was ist ein guter Algorithmus zur Berechnung von Bildern pro Sekunde in einem Spiel? Ich möchte es als Zahl in der Ecke des Bildschirms anzeigen. Wenn ich mir nur ansehe, wie lange es gedauert hat, bis das letzte Bild gerendert ist, ändert sich die Zahl zu schnell.

Bonuspunkte, wenn Ihre Antwort jeden Frame aktualisiert und nicht unterschiedlich konvergiert, wenn die Framerate steigt oder sinkt.

104
Tod

Sie benötigen einen geglätteten Durchschnitt. Am einfachsten ist es, die aktuelle Antwort (die Zeit zum Zeichnen des letzten Frames) zu nehmen und mit der vorherigen Antwort zu kombinieren.

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

Durch Anpassen des 0,9/0,1-Verhältnisses können Sie die "Zeitkonstante" ändern - so schnell reagiert die Zahl auf Änderungen. Ein größerer Bruchteil zugunsten der alten Antwort ergibt eine langsamere Änderung, ein großer Bruchteil zugunsten der neuen Antwort ergibt einen sich schneller ändernden Wert. Offensichtlich müssen sich die beiden Faktoren zu einem addieren!

94
Martin Beckett

Das habe ich in vielen Spielen benutzt.

#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

Na sicher

frames / sec = 1 / (sec / frame)

Wie Sie jedoch betonen, variiert die Zeit, die zum Rendern eines einzelnen Frames benötigt wird, erheblich, und aus der Sicht der Benutzeroberfläche ist die Aktualisierung des fps-Werts mit der Framerate überhaupt nicht verwendbar (es sei denn, die Anzahl ist sehr stabil).

Was Sie wollen, ist wahrscheinlich ein gleitender Durchschnitt oder eine Art Binning/Reset-Zähler.

Sie können beispielsweise eine Warteschlangendatenstruktur verwalten, die die Renderzeiten für die letzten 30, 60, 100 oder What-Have-You-Frames enthält (Sie können sie sogar so gestalten, dass das Limit zur Laufzeit einstellbar ist). Um eine annehmbare fps-Annäherung zu ermitteln, können Sie die durchschnittlichen fps aus allen Renderzeiten in der Warteschlange ermitteln:

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

Wenn Sie mit dem Rendern eines neuen Frames fertig sind, stellen Sie eine neue Renderzeit in eine Warteschlange und eine alte Renderzeit in eine Warteschlange. Alternativ können Sie die Warteschlange nur dann entfernen, wenn die Summe der Renderzeiten einen voreingestellten Wert (z. B. 1 Sekunde) überschreitet. Sie können den "letzten fps-Wert" und einen zuletzt aktualisierten Zeitstempel beibehalten, damit Sie auslösen können, wann die fps-Zahl aktualisiert werden soll, wenn Sie dies wünschen. Mit einem gleitenden Durchschnitt, wenn Sie eine konsistente Formatierung haben, wäre es wahrscheinlich in Ordnung, den "Momentanen Durchschnitt" der Bilder pro Sekunde auf jedem Bild zu drucken.

Eine andere Methode wäre das Zurücksetzen eines Zählers. Behalten Sie einen präzisen (Millisekunden-) Zeitstempel, einen Bildzähler und einen fps-Wert bei. Wenn Sie mit dem Rendern eines Frames fertig sind, erhöhen Sie den Zähler. Wenn der Zähler ein voreingestelltes Limit erreicht (z. B. 100 Bilder) oder wenn die Zeit seit dem Zeitstempel einen voreingestellten Wert überschritten hat (z. B. 1 Sekunde), berechnen Sie die fps:

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

Setzen Sie dann den Zähler auf 0 zurück und setzen Sie den Zeitstempel auf die aktuelle Zeit.

23
Wedge

Erhöhen Sie jedes Mal einen Zähler, wenn Sie einen Bildschirm rendern, und löschen Sie diesen Zähler für ein Zeitintervall, in dem Sie die Bildrate messen möchten.

Dh Holen Sie sich alle 3 Sekunden Zähler/3 und löschen Sie den Zähler.

11
apandit

Es gibt mindestens zwei Möglichkeiten:


Der erste ist der, den andere hier vor mir erwähnt haben. Ich denke, es ist der einfachste und bevorzugte Weg. Sie nur um den Überblick zu behalten

  • cn: Zähler für die Anzahl der von Ihnen gerenderten Frames
  • time_start: Die Zeit seit dem Beginn der Zählung
  • time_now: die aktuelle Zeit

Die Berechnung der fps ist in diesem Fall so einfach wie das Auswerten dieser Formel:

  • FPS = cn/(time_now - time_start).

Dann gibt es da noch die coole Art, die du eines Tages verwenden könntest:

Angenommen, Sie müssen 'i'-Frames berücksichtigen. Ich benutze diese Notation: f [0], f [1], ..., f [i-1], um zu beschreiben, wie lange es gedauert hat, Frame 0, Frame 1, ..., Frame (i-1) zu rendern ) beziehungsweise.

Example where i = 3

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

Dann wäre die mathematische Definition von fps nach i Frames

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

Und die gleiche Formel, aber nur unter Berücksichtigung von i-1-Frames.

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

Der Trick hier besteht nun darin, die rechte Seite von Formel (1) so zu ändern, dass sie die rechte Seite von Formel (2) enthält, und sie durch die linke Seite zu ersetzen.

So (Sie sollten es klarer sehen, wenn Sie es auf Papier schreiben):

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)

Nach dieser Formel (meine mathematischen Fähigkeiten sind allerdings ein bisschen verrostet) müssen Sie zum Berechnen der neuen FPS die FPS des vorherigen Frames, die Dauer des Renderns des letzten Frames und die Anzahl der von Ihnen verwendeten Frames kennen gerendert.

9
Peter Jankuliak

Dies könnte für die meisten Leute übertrieben sein, deshalb hatte ich es nicht gepostet, als ich es implementiert habe. Aber es ist sehr robust und flexibel.

Es speichert eine Warteschlange mit den letzten Frame-Zeiten, sodass ein durchschnittlicher FPS-Wert viel besser berechnet werden kann, als wenn nur der letzte Frame berücksichtigt wird.

Sie können auch einen Frame ignorieren, wenn Sie etwas tun, von dem Sie wissen, dass es die Zeit dieses Frames künstlich verkürzt.

Sie können damit auch die Anzahl der Frames ändern, die während der Ausführung in der Warteschlange gespeichert werden sollen, damit Sie im Handumdrehen testen können, was der beste Wert für Sie ist.

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

Gute Antworten hier. Wie Sie es implementieren, hängt davon ab, wofür Sie es benötigen. Ich bevorzuge den laufenden Durchschnitt selbst "time = time * 0.9 + last_frame * 0.1" vom obigen Typ.

ich persönlich mag es jedoch, meinen Durchschnitt stärker auf neuere Daten abzuwägen, da es in einem Spiel die SPIKES sind, die am schwersten zu quetschen sind und daher für mich am interessantesten sind. Also würde ich eher eine .7\.3-Aufteilung verwenden, damit ein Spike viel schneller auftaucht (obwohl der Effekt auch schneller vom Bildschirm verschwindet .. siehe unten).

Wenn Sie sich auf die RENDERING-Zeit konzentrieren, funktioniert die .9.1-Aufteilung ziemlich gut, da sie in der Regel flüssiger ist. Obwohl für Gameplay/KI/Physik-Spikes viel mehr Anlass zur Sorge geben, da DAS normalerweise dazu führt, dass Ihr Spiel abgehackt aussieht (was oft schlechter ist als eine niedrige Framerate, vorausgesetzt, wir fallen nicht unter 20 fps).

Also, was ich tun würde, ist auch so etwas hinzuzufügen:

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

(Füllen Sie 3.0f mit einer beliebigen Größe aus, die Sie als inakzeptabel erachten.) Auf diese Weise können Sie feststellen und somit lösen FPS gibt das Ende des Frames aus, in dem sie auftreten.

2
David Frenkel

Ein viel besseres System als die Verwendung einer großen Anzahl alter Frameraten besteht darin, einfach Folgendes zu tun:

new_fps = old_fps * 0.99 + new_fps * 0.01

Diese Methode benötigt viel weniger Speicher, benötigt viel weniger Code und misst den jüngsten Frameraten mehr Bedeutung bei als den alten, während die Auswirkungen plötzlicher Frameratenänderungen immer noch geglättet werden.

2
Barry Smith

Sie können einen Zähler behalten, ihn nach dem Rendern jedes Frames inkrementieren und dann den Zähler zurücksetzen, wenn Sie sich in einer neuen Sekunde befinden (wobei der vorherige Wert als Anzahl der gerenderten Frames der letzten Sekunde gespeichert wird).

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

Hier ist ein vollständiges Beispiel mit Python (aber leicht an jede Sprache anzupassen). Es verwendet die Glättungsgleichung in Martins Antwort, also fast keinen Speicheraufwand, und ich habe Werte ausgewählt, die für mich (Gefühl) funktionierten frei, mit den Konstanten herumzuspielen, um sie an Ihren Anwendungsfall anzupassen).

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

Zähler auf Null setzen. Jedes Mal, wenn Sie einen Rahmen zeichnen, erhöhen Sie den Zähler. Drucken Sie nach jeder Sekunde den Zähler aus. einschäumen, ausspülen, wiederholen. Wenn Sie zusätzliches Guthaben wünschen, behalten Sie einen laufenden Zähler bei und dividieren Sie durch die Gesamtanzahl der Sekunden für einen laufenden Durchschnitt.

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

Wie ich es mache!

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

In Worten, eine Tick Clock verfolgt Ticks. Wenn es das erste Mal ist, nimmt es die aktuelle Zeit und setzt sie in 'tickstart'. Nach dem ersten Tick entspricht die Variable 'fps' der Anzahl der Ticks der Tick-Uhr geteilt durch die Zeit minus der Zeit des ersten Ticks.

Fps ist eine ganze Zahl, daher "(int)".

0
BottleFact

In (c ++ like) Pseudocode habe ich diese beiden in industriellen Bildverarbeitungsanwendungen verwendet, die Bilder von einer Reihe von extern ausgelösten Kameras verarbeiten mussten. Schwankungen in der "Bildrate" hatten eine andere Ursache (langsamere oder schnellere Produktion auf dem Riemen), aber das Problem ist das gleiche. (Ich gehe davon aus, dass Sie einen einfachen timer.peek () - Aufruf haben, der Ihnen so etwas wie die nr von ms (nsec?) Seit dem Start der Anwendung oder dem letzten Aufruf gibt.)

Lösung 1: Schnell, aber nicht bei jedem Frame aktualisiert

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

Lösung 2: Aktualisiert jeden Frame, benötigt mehr Speicher und 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

So mache ich das (in 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
}
0
adventurerOK