web-dev-qa-db-fra.com

Comment limiter le nombre d'images par seconde lorsque vous utilisez GLSurfaceView.RENDERMODE_CONTINUOUS Android?

J'ai un jeu C++ fonctionnant via JNI sous Android. La fréquence d'images varie d'environ 20 à 45 images par seconde en raison de la complexité de la scène. Tout ce qui dépasse 30fps est idiot pour le jeu; c'est juste brûler la batterie. J'aimerais limiter la fréquence d'images à 30 images par seconde.

  • Je pourrais passer à RENDERMODE_WHEN_DIRTY et utiliser un temporisateur ou un ScheduledThreadPoolExecutor pour requestRender (). Mais cela ajoute tout un fouillis de pièces mobiles supplémentaires qui pourraient ou ne pourraient pas fonctionner correctement et correctement.
  • J'ai essayé d'injecter Thread.sleep () lorsque les choses tournent vite, mais cela ne semble pas fonctionner du tout pour des valeurs de temps réduites. Et il se peut tout simplement que des événements soient sauvegardés dans la file d’attente de toute façon, pas en fait en pause.

Existe-t-il une méthode "capFramerate ()" masquée dans l'API? Un moyen fiable de le faire?

21
grinliz

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 en tenir compte ou vous obtiendrez une cadence d'images inférieure à celle souhaitée. Il convient donc de:

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 nécessaire pour échanger les tampons et le temps nécessaire pour dessiner le cadre.

38
Fili

Lorsque vous utilisez GLSurfaceView, vous effectuez le dessin dans le onDrawFrame de votre outil de rendu, qui est traité dans un fil séparé par GLSurfaceView. Assurez-vous simplement que chaque appel à onDrawFrame prend (1000/[frames]) millisecondes, dans votre cas environ 33 ms.

Pour ce faire: (dans votre onDrawFrame)

  1. Mesurez l'heure actuelle avant de commencer à dessiner à l'aide de System.currentTimeMillis (appelons-le startTime) 
  2. Effectuer le dessin
  3. Mesurer à nouveau le temps (appelons-le temps de fin) 
  4. deltaT = endTime - starTime
  5. si deltaT <33, veillez (33-deltaT)

C'est tout.

9
Lior

La réponse de Fili était superbe pour moi. Malheureusement, le nombre d'images par seconde sur mon appareil Android a été limité à 25 images par seconde, même si j'en ai demandé 30. J'ai découvert que Thread.sleep() ne fonctionne pas assez correctement et qu'il dort plus longtemps qu'il ne le devrait.

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

1
tyrondis

Si vous ne voulez pas compter sur Thread.sleep, utilisez ce qui suit

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
Confuse

La solution de Fili échoue pour certaines personnes, alors je suppose qu'elle dort jusqu'au lendemain du prochain vsync au lieu du précédent. Je pense aussi que déplacer le sommeil à la fin de la fonction donnerait de meilleurs résultats, car cela permettrait de masquer le cadre actuel avant le prochain vsync, au lieu d'essayer de compenser le précédent. Thread.sleep () est inexact, mais heureusement nous n’avons besoin que de précision pour la période vsync la plus proche de 1/60. Le code LWJGL tyrondis a publié un lien vers semble trop compliqué pour cette situation, il est probablement conçu pour quand vsync est désactivé ou non disponible, ce qui ne devrait pas être le cas dans le contexte de cette question.

Je voudrais essayer quelque chose comme ça:

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
realh