Android EGL / OpenGL ES帧速率口吃

TL; DR

即使在完全没有绘图的情况下,在Android设备上的OpenGL ES渲染线程上保持60Hz的更新速率似乎也是不可能的。 经常出现神秘的尖峰(在底部的代码中显示),并且我为了弄清楚为什么或如何导致死路一条而做出的每一个努力。 使用自定义渲染线程定时更复杂的示例一直表明eglSwapBuffers()是罪魁祸首,经常在17ms-32ms以上。 帮帮我?

更多细节

这特别令人沮丧,因为我们项目的渲染要求是屏幕对齐的元素以固定的高速从屏幕的一侧平滑地水平滚动到另一侧。 换句话说,一个平台游戏。 从60Hz频繁下降导致明显的爆裂和l,,无论是否有基于时间的移动。 由于滚动速度很高,因此30Hz下的渲染不是一种选择,这是设计中不可谈判的部分。

我们的项目是基于Java的,以最大限度地提高兼容性并使用OpenGL ES 2.0。 我们只能深入到API 7-8设备上的OpenGL ES 2.0渲染和API 7设备上的ETC1支持。 在它和下面给出的测试代码中,除了我的控制之外的日志打印和自动线程,我没有验证分配/ GC事件。

我已经在使用库存Android类和NDK的单个文件中重新创建了该问题。 下面的代码可以粘贴到在Eclipse中创建的新的Android项目中,只要您选择API级别8或更高版本,就可以开箱即用。

该测试已在各种GPU和操作系统版本的各种设备上进行了复制:

  • Galaxy Tab 10.1(Android 3.1)
  • Nexus S(Android 2.3.4)
  • Galaxy S II(Android 2.3.3)
  • XPERIA Play(Android 2.3.2)
  • Droid Incredible(Android 2.2)
  • Galaxy S(Android 2.1-update1)(将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();
    }
    

    不知道这是否是答案,但请注意,对eglSwapBuffers()的调用至少会阻塞16毫秒,即使没有任何内容可以绘制。

    在单独的线程中运行游戏逻辑可能会赢回一些时间。

    查看开源平台游戏Relica Island的博文。 游戏逻辑很重,但由于作者管线/双缓冲解决方案,该框架非常流畅。

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


    你不是自己绑定帧速率。 所以它受CPU限制。

    这意味着当CPU无所事事时它会运行得很快,而当它做其他事情时会变慢。

    其他的东西正在你的手机上运行,​​偶尔会从你的游戏中花费CPU时间。 垃圾收集器也会这样做,尽管不像以前那样冻结你的游戏(因为它现在在一个单独的线程中运行)

    您会发现在任何正常使用的多程序操作系统上都会发生这种情况。 这不仅仅是Java的错。

    我的建议是:如果需要恒定比特率,则将帧速率绑定到较低的值。 提示:使用Thread.sleep()和busy waiting(while循环)的混合,因为睡眠可能会超过所需的等待时间。

    链接地址: http://www.djcxy.com/p/64635.html

    上一篇: Android EGL/OpenGL ES Frame Rate Stuttering

    下一篇: RDMA between GPU and remote host