Android的EGL / OpenGL ES的帧速率口吃 [英] Android EGL/OpenGL ES Frame Rate Stuttering

查看:410
本文介绍了Android的EGL / OpenGL ES的帧速率口吃的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

TL; DR

即使这样做没有图纸的时候的话,那似乎是不可能维持一个OpenGL ES渲染线程60Hz的更新率在Android设备上。神秘的尖峰经常突然出现(在code底部证明),而我所做的一切努力,找出原因或如何导致了一个死胡同。定时与自定义渲染线程更复杂的例子一再表明eglSwapBuffers()是罪魁祸首,经常进来了17MS-为32ms。帮助?

详细信息

这是特别确凿,因为我们的项目的渲染要求是屏幕对齐元素在一个固定的,高速率从屏幕的一侧到另一顺利水平滚动。换言之,一个构建平台的游戏。频繁滴从60赫兹导致明显的爆裂和蹒跚,无论有没有基于时间的运动。渲染以30Hz是因为高速率滚动速度,这是设计的不可转让的部分不是一个选项。

我们的项目是基于Java的,最大限度的兼容性和使用OpenGL ES 2.0。我们只下沉,进入NDK进行OpenGL ES 2.0的渲染API 7-8设备和API 7设备ETC1支持。在它和下面给出的测试code,我已经验证没有分配/ GC事件除了日志打印和自动线超出了我的控制。

我在使用普通的Andr​​oid类和NDK没有一个单一的文件重新创建的问题。在code以下可粘贴到Eclipse中创建并应pretty的大量工作外的开箱即用,只要你选择API 8级或以上的一个新的Andr​​oid项目。

的测试已被再现的各种设备与一系列GPU和OS版本中:

  • 的Galaxy Tab 10.1(安卓3.1)
  • 的Nexus S(安卓2.3.4)
  • 的Galaxy S II(安卓2.3.3)
  • 在Xperia PLAY的(安卓2.3.2)
  • 在Droid难以置信(安卓2.2)
  • 的Galaxy S(的Andr​​oid 2.1 UPDATE1)(下降API的要求降低到7级的时候)

样本输出(从不到1秒钟的运行时间聚集):

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

我一直在追逐这个一段时间,有关于撞到南墙。如果一个修复程序不可用,那么至少,为什么出现这种情况,并建议如何被克服类似要求的项目说明会大大AP preciated。

示例code

 包com.test.spikeglsurfview;

进口javax.microedition.khronos.egl.EGLConfig;
进口javax.microedition.khronos.opengles.GL10;

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

/ **
 *一个简单的活动,展示了频繁的帧速率骤降,从60赫兹,
 *即使做任何渲染的时候都没有。
 *
 *这个类的目标API级别8,其目的是要落的兼容与
 *在Ec​​lipse新鲜自动生成的Andr​​oid项目。
 *
 *本例使用普通的Andr​​oid类只要有可能。
 *
 * @author比尔Roeske
 * /
公共类SpikeActivity扩展活动
{
    @覆盖
    公共无效的onCreate(包savedInstanceState)
    {
        super.onCreate(savedInstanceState);

        //使活动充满整个屏幕。
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow()。setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                              WindowManager.LayoutParams.FLAG_FULLSCREEN);

        //获取一个参考的默认布局。
        最后LayoutInflater厂= getLayoutInflater();
        最后的LinearLayout布局=(的LinearLayout)factory.inflate(R.layout.main,NULL);

        //清除布局,以删除默认的Hello World的TextView。
        layout.removeAllViews();

        //创建一个GLSurfaceView并把它添加到布局。
        GLSurfaceView glView =新GLSurfaceView(getApplicationContext());
        layout.addView(glView);

        //配置GLSurfaceView的OpenGL ES 2.0的渲染与测试渲染。
        glView.setEGLContextClientVersion(2);
        glView.setRenderer(新SpikeRenderer());

        //适用修改后的布局,本次活动的用户界面。
        的setContentView(布局);
    }
}

类SpikeRenderer实现GLSurfaceView.Renderer
{
    @覆盖
    公共无效onDrawFrame(GL10 GL)
    {
        //更新基准时间值。
        最终长timeCurrentNS = System.nanoTime();
        最终长timeDeltaNS = timeCurrentNS  - 时间previousNS;
        时间previousNS = timeCurrentNS;

        //确定自秒最后一帧的时间。
        最终浮动timeDeltaS = timeDeltaNS * 1.0E-9F;

        //打印的通知,如果渲染落后60赫兹。
        如果(timeDeltaS>(1.0F / 60.0f))
        {
            Log.d(SpikeTest,狼牙:+ timeDeltaS);
        }

        / * //清屏。
        gl.glClear(GLES20.GL_COLOR_BUFFER_BIT); * /
    }

    @覆盖
    公共无效onSurfaceChanged(GL10 GL,诠释的宽度,高度INT)
    {
    }

    @覆盖
    公共无效onSurfaceCreated(GL10 GL,EGLConfig配置)
    {
        //设置明确的颜色为紫色。
        gl.glClearColor(0.5F,0.0,0.5F,1.0F);
    }

    私人很久previousNS = System.nanoTime();
}
 

解决方案

不知道这就是答案,但请注意,调用eglSwapBuffers()至少16毫秒块,即使有什么可借鉴。

运行游戏逻辑在一个单独的线程可以重新赢得一些时间。

退房的博客文章在开源平台化游戏Relica岛。游戏逻辑是沉重的,然而framrate光滑由于作者流水线/双缓冲液。

http://replicaisland.blogspot.com/2009/ 10 /渲染与 - 两threads.html

TL;DR

Even when doing no drawing at all, it seems impossible to maintain a 60Hz update rate on an OpenGL ES rendering thread on an Android device. Mysterious spikes frequently crop up (demonstrated in the code at bottom), and every effort that I've made to figure out why or how has lead to a dead end. Timing in more complicated examples with a custom rendering thread has consistently shown eglSwapBuffers() to be the culprit, frequently coming in over 17ms-32ms. Help?

More Details

This is particularly damning because the rendering requirements for our project is screen-aligned elements smoothly scrolling horizontally at a fixed, high rate of speed from one side of the screen to the other. In other words, a platforming game. The frequent drops from 60Hz result in noticeable popping and lurching, both with and without time-based movement. Rendering at 30Hz isn't an option because of the high rate of scrolling speed, which is a non-negotiable part of the design.

Our project is Java-based to maximize compatibility and uses OpenGL ES 2.0. We only dip down into the NDK for OpenGL ES 2.0 rendering on API 7-8 devices and ETC1 support on API 7 devices. In both it and the test code given below, I have verified no allocations/GC events except for the log print and automatic threads beyond my control.

I've recreated the problem in a single file that uses stock Android classes and no NDK. The code below can be pasted into a new Android project created in Eclipse and should pretty much work out-of-the-box so long as you choose API level 8 or above.

The test has been reproduced on a variety of devices with a range of GPUs and OS versions:

  • 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) (when dropping API requirements down to level 7)

Sample output (gathered from under 1 second of run time):

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

I've been chasing this one for a while and have about hit a brick wall. If a fix isn't available, then at least an explanation about why this happens and advice on how this has been overcome in projects with similar requirements would be greatly appreciated.

Example Code

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

解决方案

Not sure if this is the answer, but note that the call to eglSwapBuffers() blocks for at least 16 ms, even if there is nothing to draw.

Running the game logic in a separate thread could win back some time.

Check out the blog post at the open source Platforming game Relica Island. The game logic is heavy, yet the framrate is smooth due to the authors pipeline/double buffer solution.

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

这篇关于Android的EGL / OpenGL ES的帧速率口吃的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆