Comment limiter framerate lors de L'utilisation de GLSurfaceView Android.RENDERMODE EN CONTINU?

J'ai un jeu c++ qui passe par JNI dans Android. La cadence varie d'environ 20-45fps en raison de la complexité des scènes. Tout ce qui dépasse 30fps est stupide pour le jeu; il ne fait que brûler la batterie. Je voudrais limiter la fréquence d'images à 30 fps.

  • je pourrais passer à RENDERMODE_WHEN_DIRTY, et utiliser un Timer ou ScheduledThreadPoolExecutor pour requestRender (). Mais cela ajoute tout un gâchis de pièces mobiles supplémentaires qui pourraient ou pourraient ne pas fonctionner de manière cohérente et correcte.
  • j'ai essayé l'injection de Thread.sleep () quand les choses fonctionnent rapidement, mais cela ne semble pas fonctionner du tout pour les petites valeurs temporelles. Et il peut juste être en train de sauvegarder des événements dans la file d'attente de toute façon, pas réellement en pause.

Existe-t-il une méthode "capFramerate()" cachée dans L'API? Tout moyen fiable pour ce faire?

21
demandé sur grinliz 2011-01-23 10:04:02

6 réponses

La solution de Mark est presque bonne, mais pas tout à fait correcte. Le problème est que le swap lui-même prend beaucoup de temps (surtout si le pilote vidéo met en cache des instructions). Par conséquent, vous devez prendre cela en compte ou vous finirez avec une fréquence d'images inférieure à celle souhaitée. Donc, la chose devrait être:

Quelque part au début (comme le constructeur):

startTime = System.currentTimeMillis();

, Puis dans la boucle de rendu:

public void onDrawFrame(GL10 gl)
{    
    endTime = System.currentTimeMillis();
    dt = endTime - startTime;
    if (dt < 33)
        Thread.Sleep(33 - dt);
    startTime = System.currentTimeMillis();

    UpdateGame(dt);
    RenderGame(gl);
}

De cette façon, vous prendrez en compte le temps qu'il faut pour échangez les tampons et le temps de dessiner le cadre.

38
répondu Fili 2011-06-10 10:41:58

Lorsque vous utilisez GLSurfaceView, vous effectuez le dessin dans onDrawFrame de votre moteur de rendu qui est géré dans un thread séparé par GLSurfaceView. Assurez-vous simplement que chaque appel à onDrawFrame prend (1000 / [frames]) millisecondes, dans votre cas quelque chose comme 33ms.

Pour ce faire: (dans votre onDrawFrame)

  1. mesurez l'heure actuelle avant de commencer à dessiner à L'aide du système.currentTimeMillis (appelons-le startTime)
  2. Effectuer le dessin
  3. mesurer à nouveau le temps (Appelons-le endTime)
  4. deltaT = endTime-starTime
  5. si deltaT

C'est ça.

8
répondu Lior 2011-01-23 10:43:14

La réponse de Fili avait l'air bien pour moi, mal malheureusement limité le FPS sur mon appareil Android à 25 FPS, même si j'ai demandé 30. J'ai compris que Thread.sleep() ne fonctionne pas assez avec précision et dort plus longtemps qu'il ne le devrait.

J'ai trouvé cette implémentation du projet LWJGL pour faire le travail: https://github.com/LWJGL/lwjgl/blob/master/src/java/org/lwjgl/opengl/Sync.java

1
répondu tyrondis 2017-05-23 12:17:39

La solution de Fili échoue pour certaines personnes, donc je soupçonne qu'elle dort immédiatement après le prochain vsync au lieu de immédiatement avant. Je pense aussi que déplacer le sommeil à la fin de la fonction donnerait de meilleurs résultats, car il peut tamponner le cadre actuel avant le prochain vsync, au lieu d'essayer de compenser le précédent. Fil.sleep () est inexact, mais heureusement nous n'avons besoin que d'être précis à la période vsync la plus proche de 1 / 60s. le code LWJGL tyrondis posté un lien vers semble trop compliqué pour cette situation, il est probablement conçu pour quand vsync est désactivé ou indisponible, ce qui ne devrait pas être le cas dans le contexte de cette question.

Je voudrais essayer quelque chose comme ceci:

private long lastTick = System.currentTimeMillis();

public void onDrawFrame(GL10 gl)
{
    UpdateGame(dt);
    RenderGame(gl);

    // Subtract 10 from the desired period of 33ms to make generous
    // allowance for overhead and inaccuracy; vsync will take up the slack
    long nextTick = lastTick + 23;
    long now;
    while ((now = System.currentTimeMillis()) < nextTick)
        Thread.sleep(nextTick - now);
    lastTick = now;
}
0
répondu realh 2017-06-27 21:09:59

Si vous ne voulez pas compter sur Thread.sleep, Utilisez

double frameStartTime = (double) System.nanoTime()/1000000;
// start time in milliseconds
// using System.currentTimeMillis() is a bad idea
// call this when you first start to draw

int frameRate = 30;
double frameInterval = (double) 1000/frame_rate;
// 1s is 1000ms, ms is millisecond
// 30 frame per seconds means one frame is 1s/30 = 1000ms/30

public void onDrawFrame(GL10 gl)
{
  double endTime = (double) System.nanoTime()/1000000;
  double elapsedTime = endTime - frameStartTime;

  if (elapsed >= frameInterval)
  {
    // call GLES20.glClear(...) here

    UpdateGame(elapsedTime);
    RenderGame(gl);

    frameStartTime += frameInterval;
  }
}
0
répondu Confuse 2018-09-09 06:53:09

Vous pouvez également essayer de réduire la priorité de thread de onSurfaceCreated():

Process.setThreadPriority(Process.THREAD_PRIORITY_LESS_FAVORABLE);
-3
répondu olivierg 2011-08-31 17:22:39