Android EGL / OpenGL ES Частота заиканий кадров - PullRequest
14 голосов
/ 30 августа 2011

TL; DR

Даже без рисования вообще невозможно поддерживать частоту обновления 60 Гц для потока рендеринга OpenGL ES на устройстве Android.Таинственные всплески часто возникают (как показано в коде внизу), и все мои усилия, которые я предпринял, чтобы выяснить, почему или как, привели к тупику.Синхронизация в более сложных примерах с пользовательским потоком рендеринга неизменно показала, что виновником является eglSwapBuffers (), часто приходящий через 17 мс-32 мс.Справка?

Подробнее

Это особенно ужасно, потому что требования к отрисовке для нашего проекта - выравнивание по экрану элементов, плавно прокручивающихся по горизонтали с фиксированной, высокой скоростью отодна сторона экрана на другую.Другими словами, игра платформер.Частые падения с 60 Гц приводят к заметным щелчкам и скачкам, как с движением, так и без него.Рендеринг с частотой 30 Гц невозможен из-за высокой скорости прокрутки, что является неотъемлемой частью проекта.

Наш проект основан на Java для максимальной совместимости и использует OpenGL ES 2.0.Мы погружаемся только в NDK для рендеринга OpenGL ES 2.0 на устройствах API 7-8 и поддержки ETC1 на устройствах API 7.И в этом, и в тестовом коде, приведенном ниже, я проверил, нет никаких распределений / событий GC, за исключением печати журнала и автоматических потоков вне моего контроля.

Я воссоздал проблему в одном файле, который использует стандартный Androidклассы и нет ндк.Приведенный ниже код может быть вставлен в новый проект Android, созданный в Eclipse, и он должен работать практически из коробки, если вы выберете уровень API 8 или выше.

Тест был воспроизведен наразличные устройства с различными графическими процессорами и версиями ОС:

  • Galaxy Tab 10.1 (Android 3.1)
  • Nexus S (Android 2.3.4)
  • Galaxy SII (Android 2.3.3)
  • XPERIA Play (Android 2.3.2)
  • Droid Incredible (Android 2.2)
  • Galaxy S (Android 2.1-обновление1) (при паденииТребования API до уровня 7)

Пример вывода (собранный менее чем за 1 секунду времени выполнения):

Spike: 0.017554
Spike: 0.017767
Spike: 0.018017
Spike: 0.016855
Spike: 0.016759
Spike: 0.016669
Spike: 0.024925
Spike: 0.017083999
Spike: 0.032984
Spike: 0.026052998
Spike: 0.017372

Я некоторое время гонялся за этим и получаюо ударил кирпичную стену.Если исправление недоступно, по крайней мере объяснение, почему это происходит, и советы о том, как это было преодолено в проектах с похожими требованиями.

Пример кода

package com.test.spikeglsurfview;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.app.Activity;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Window;
import android.view.WindowManager;
import android.widget.LinearLayout;

/**
 * A simple Activity that demonstrates frequent frame rate dips from 60Hz,
 * even when doing no rendering at all.
 * 
 * This class targets API level 8 and is meant to be drop-in compatible with a
 * fresh auto-generated Android project in Eclipse.
 * 
 * This example uses stock Android classes whenever possible.
 * 
 * @author Bill Roeske
 */
public class SpikeActivity extends Activity
{
    @Override
    public void onCreate( Bundle savedInstanceState )
    {
        super.onCreate( savedInstanceState );

        // Make the activity fill the screen.
        requestWindowFeature( Window.FEATURE_NO_TITLE );
        getWindow().setFlags( WindowManager.LayoutParams.FLAG_FULLSCREEN, 
                              WindowManager.LayoutParams.FLAG_FULLSCREEN );

        // Get a reference to the default layout.
        final LayoutInflater factory = getLayoutInflater();
        final LinearLayout layout = (LinearLayout)factory.inflate( R.layout.main, null );

        // Clear the layout to remove the default "Hello World" TextView.
        layout.removeAllViews();

        // Create a GLSurfaceView and add it to the layout.
        GLSurfaceView glView = new GLSurfaceView( getApplicationContext() );
        layout.addView( glView );

        // Configure the GLSurfaceView for OpenGL ES 2.0 rendering with the test renderer.
        glView.setEGLContextClientVersion( 2 );
        glView.setRenderer( new SpikeRenderer() );

        // Apply the modified layout to this activity's UI.
        setContentView( layout );
    }
}

class SpikeRenderer implements GLSurfaceView.Renderer
{
    @Override
    public void onDrawFrame( GL10 gl )
    {
        // Update base time values.
        final long  timeCurrentNS = System.nanoTime();
        final long  timeDeltaNS = timeCurrentNS - timePreviousNS;
        timePreviousNS = timeCurrentNS;

        // Determine time since last frame in seconds.
        final float timeDeltaS = timeDeltaNS * 1.0e-9f;

        // Print a notice if rendering falls behind 60Hz.
        if( timeDeltaS > (1.0f / 60.0f) )
        {
            Log.d( "SpikeTest", "Spike: " + timeDeltaS );
        }

        /*// Clear the screen.
        gl.glClear( GLES20.GL_COLOR_BUFFER_BIT );*/
    }

    @Override
    public void onSurfaceChanged( GL10 gl, int width, int height )
    {
    }

    @Override
    public void onSurfaceCreated( GL10 gl, EGLConfig config )
    {
        // Set clear color to purple.
        gl.glClearColor( 0.5f, 0.0f, 0.5f, 1.0f );
    }

    private long timePreviousNS = System.nanoTime();
}

Ответы [ 2 ]

3 голосов
/ 29 декабря 2011

Не уверен, что это ответ, но учтите, что вызов eglSwapBuffers () блокируется не менее 16 мс, даже если нечего рисовать.

Запуск игровой логики в отдельном потоке можетОтыграйте некоторое время.

Ознакомьтесь с постом в блоге с открытой исходной платформой игры Relica Island.Логика игры тяжелая, но фрейм плавный благодаря решению конвейера / двойного буфера авторов.

http://replicaisland.blogspot.com/2009/10/rendering-with-two-threads.html

0 голосов
/ 16 декабря 2011

Вы не привязываете частоту кадров самостоятельно. Так что это связано с процессором.

Это означает, что он будет работать быстро, когда процессору нечего делать, и медленнее, когда он делает другие вещи.

На вашем телефоне запущены другие функции, которые иногда отнимают процессорное время у вашей игры. Сборщик мусора также сделает это, хотя и не замораживает вашу игру, как раньше (поскольку теперь она запускается в отдельном потоке)

Вы увидите, что это происходит в любой многопрограммной операционной системе, которая использовалась в обычном режиме. Это не только ошибка Java.

Мое предложение: привязать частоту кадров к более низкому значению, если требуется постоянный битрейт. Подсказка: используйте сочетание Thread.sleep () и ожидания ожидания (в то время как цикл), так как сон может превысить необходимое время ожидания.

...