web-dev-qa-db-ja.com

AndroidのGLSurfaceView.RENDERMODE_CONTINUOUSLYを使用するときにフレームレートを制限するにはどうすればよいですか?

AndroidでJNIを介して実行しているC++ゲームがあります。シーンの複雑さにより、フレームレートは約20〜45fpsの範囲で変化します。 30fpsを超えるものは、ゲームにとってばかげています。バッテリーが燃えているだけです。フレームレートを30 fpsに制限したい。

  • RENDERMODE_WHEN_DIRTYに切り替え、TimerまたはScheduledThreadPoolExecutorを使用してrequestRender()を実行できます。しかし、それは一貫して正しく機能するかもしれないし、しないかもしれない余分な可動部品の全体の混乱を追加します。
  • 物事が速く実行されているときにThread.sleep()を挿入しようとしましたが、これは小さな時間値ではまったく機能しないようです。とにかく、実際に一時停止するのではなく、イベントをキューに戻すだけかもしれません。

APIに隠れている「capFramerate()」メソッドはありますか?これを行うための信頼できる方法はありますか?

21
grinliz

Markの解決策はほとんど問題ありませんが、完全に正しいわけではありません。問題は、スワップ自体にかなりの時間がかかることです(特に、ビデオドライバーが命令をキャッシュしている場合)。したがって、それを考慮に入れる必要があります。そうしないと、必要なフレームレートよりも低いフレームレートで終了します。したがって、次のようになります。

開始時のどこか(コンストラクターなど):

startTime = System.currentTimeMillis();

次に、レンダリングループで:

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

このようにして、バッファを交換するのにかかる時間とフレームを描画するのにかかる時間を考慮に入れます。

38
Fili

GLSurfaceViewを使用する場合、GLSurfaceViewによって別のスレッドで処理されるレンダラーのonDrawFrameで描画を実行します。単純に、onDrawFrameの各呼び出しに(1000/[frames])ミリ秒かかることを確認してください。

これを行うには:(onDrawFrame内で)

  1. System.currentTimeMillisを使用して、描画を開始する前に現在の時間を測定します(startTimeと呼びましょう)。
  2. 描画を実行します
  3. 時間をもう一度測定します(endTimeとしましょう)。
  4. deltaT = endTime-starTime
  5. deltaT <33の場合、スリープ(33-deltaT)

それでおしまい。

9
Lior

フィリの答え 見栄えが良く、残念ながらFPSは私のデバイスのAndroid=デバイスを25 FPSに制限しましたが、30をリクエストしましたが、Thread.sleep()は十分に正確に機能せず、必要以上に長くスリープします。

LWJGLプロジェクトからこの実装が仕事をするのを見つけました: https://github.com/LWJGL/lwjgl/blob/master/src/Java/org/lwjgl/opengl/Sync.Java

2
tyrondis

Thread.sleepに依存したくない場合は、以下を使用してください

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

Filiのソリューションは一部の人にとって失敗しているので、次のvsyncの直前ではなく、直後までスリープしていると思われます。また、スリープを関数の最後に移動すると、前のフレームを補正しようとする代わりに、次のvsyncの前に現在のフレームを埋め込むことができるため、より良い結果が得られると思います。 Thread.sleep()は不正確ですが、幸いにも、最も近いvsync期間の1/60秒まで正確であることが必要です。 LWJGLコードtyrondisがこの状況に対して過度に複雑に見えるようにリンクを投稿しました。おそらく、vsyncが無効または利用できない場合のために設計されていますが、この質問の場合はそうではありません。

私はこのようなことを試みます:

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