创建窗口表面失败:EGL_BAD_MATCH?

11

安卓版本为2.2.1,设备型号为三星Galaxy II,完整的奔溃日志如下:

java.lang.RuntimeException: createWindowSurface failed: EGL_BAD_MATCH
at android.opengl.GLSurfaceView$EglHelper.throwEglException(GLSurfaceView.java:1077)
at android.opengl.GLSurfaceView$EglHelper.createSurface(GLSurfaceView.java:981)
at android.opengl.GLSurfaceView$GLThread.guardedRun(GLSurfaceView.java:1304)
at android.opengl.GLSurfaceView$GLThread.run(GLSurfaceView.java:1116)

这是导致崩溃的相关代码:

@Override 
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                         WindowManager.LayoutParams.FLAG_FULLSCREEN);
    glView = new GLSurfaceView(this);
    glView.setEGLConfigChooser(8 , 8, 8, 8, 16, 0);
    glView.setRenderer(this);
    setContentView(glView);
    \\etc..............}

我使用了setEGLConfigChooser(),因为如果不加这个选项,应用程序在API-17上会崩溃,所以针对这个具体的设备出现崩溃的问题,我一直在寻找解决办法,与该设备的PixelFormat有关。

我想知道如何编写代码,以便在三星Galaxy II Android 2.2.1版本上不会崩溃。我无法在模拟器中测试它,也没有该设备进行测试,我需要确定的代码并且不确定如何更改它?

2个回答

10

更新:我找到了解决这个问题的方法,实际上它非常简单。

首先:Android默认的 EGLConfigChooser 实现在某些设备上做出了不好的决策,特别是旧版 Android 设备似乎会遇到 EGL_BAD_MATCH 问题。在我的调试过程中,我还发现这些旧设备具有相当有限的可用 OpenGL ES 配置。

"bad match" 问题的原因不仅仅是 GLSurfaceView 的像素格式与 OpenGL ES 颜色位深设置之间的不匹配。总体而言,我们需要处理以下问题:

  • OpenGL ES API 版本不匹配
  • 请求的目标表面类型不匹配
  • 所请求的颜色位深不能在 Surface View 上呈现

当涉及到解释 OpenGL ES API 时, Android 开发文档非常缺乏。因此,阅读 Khronos.org 上的原始文档非常重要。尤其是关于 eglChooseConfig 的文档页在这里非常有帮助。

为了解决上述问题,您必须确保指定以下最小配置:

  • EGL_RENDERABLE_TYPE 必须与您正在使用的 OpenGL ES API 版本匹配。在 OpenGL ES 2.x 的情况下,您必须将该属性设置为 4(请参见egl.h
  • EGL_SURFACE_TYPE 应设置为 EGL_WINDOW_BIT

当然,您还需要设置一个 OpenGL ES 上下文,以提供正确的颜色、深度和模板缓冲区设置。

不幸的是,在直接方式下,无法挑选这些配置选项。我们必须从任何给定设备上可用的内容中进行选择。这就是为什么需要实现自定义的 EGLConfigChooser,它遍历可用配置集列表并选择最适合符合给定标准的那个。

无论如何,我为这样的配置选择器编写了一个示例实现:

public class MyConfigChooser implements EGLConfigChooser {
    final private static String TAG = "MyConfigChooser";

    // This constant is not defined in the Android API, so we need to do that here:
    final private static int EGL_OPENGL_ES2_BIT = 4;

    // Our minimum requirements for the graphics context
    private static int[] mMinimumSpec = {
            // We want OpenGL ES 2 (or set it to any other version you wish)
            EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,

            // We want to render to a window
            EGL10.EGL_SURFACE_TYPE, EGL10.EGL_WINDOW_BIT,

            // We do not want a translucent window, otherwise the
            // home screen or activity in the background may shine through
            EGL10.EGL_TRANSPARENT_TYPE, EGL10.EGL_NONE, 

            // indicate that this list ends:
            EGL10.EGL_NONE
    };

    private int[] mValue = new int[1];
    protected int mAlphaSize;
    protected int mBlueSize;
    protected int mDepthSize;
    protected int mGreenSize;
    protected int mRedSize;
    protected int mStencilSize;

    /**
    * The constructor lets you specify your minimum pixel format,
    * depth and stencil buffer requirements.
    */
    public MyConfigChooser(int r, int g, int b, int a, int depth, int 
                        stencil) {
        mRedSize = r;
        mGreenSize = g;
        mBlueSize = b;
        mAlphaSize = a;
        mDepthSize = depth;
        mStencilSize = stencil;
    }

    @Override
    public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
        int[] arg = new int[1];
        egl.eglChooseConfig(display, mMinimumSpec, null, 0, arg);
        int numConfigs = arg[0];
        Log.i(TAG, "%d configurations available", numConfigs);

        if(numConfigs <= 0) {
            // Ooops... even the minimum spec is not available here
            return null;
        }

        EGLConfig[] configs = new EGLConfig[numConfigs];
        egl.eglChooseConfig(display, mMinimumSpec, configs,    
            numConfigs, arg);

        // Let's do the hard work now (see next method below)
        EGLConfig chosen = chooseConfig(egl, display, configs);

        if(chosen == null) {
            throw new RuntimeException(
                    "Could not find a matching configuration out of "
                            + configs.length + " available.", 
                configs);
        }

        // Success
        return chosen;
    }

   /**
    * This method iterates through the list of configurations that 
    * fulfill our minimum requirements and tries to pick one that matches best
    * our requested color, depth and stencil buffer requirements that were set using 
    * the constructor of this class.
    */
    public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display,
            EGLConfig[] configs) {
        EGLConfig bestMatch = null;
        int bestR = Integer.MAX_VALUE, bestG = Integer.MAX_VALUE, 
            bestB = Integer.MAX_VALUE, bestA = Integer.MAX_VALUE, 
            bestD = Integer.MAX_VALUE, bestS = Integer.MAX_VALUE;

        for(EGLConfig config : configs) {
            int r = findConfigAttrib(egl, display, config, 
                        EGL10.EGL_RED_SIZE, 0);
            int g = findConfigAttrib(egl, display, config,
                        EGL10.EGL_GREEN_SIZE, 0);
            int b = findConfigAttrib(egl, display, config,         
                        EGL10.EGL_BLUE_SIZE, 0);
            int a = findConfigAttrib(egl, display, config,
                    EGL10.EGL_ALPHA_SIZE, 0);
            int d = findConfigAttrib(egl, display, config,
                    EGL10.EGL_DEPTH_SIZE, 0);
            int s = findConfigAttrib(egl, display, config,
                    EGL10.EGL_STENCIL_SIZE, 0);

            if(r <= bestR && g <= bestG && b <= bestB && a <= bestA
                    && d <= bestD && s <= bestS && r >= mRedSize
                    && g >= mGreenSize && b >= mBlueSize 
                    && a >= mAlphaSize && d >= mDepthSize 
                    && s >= mStencilSize) {
                bestR = r;
                bestG = g;
                bestB = b;
                bestA = a;
                bestD = d;
                bestS = s;
                bestMatch = config;
            }
        }

        return bestMatch;
    }

    private int findConfigAttrib(EGL10 egl, EGLDisplay display,
            EGLConfig config, int attribute, int defaultValue) {

        if(egl.eglGetConfigAttrib(display, config, attribute, 
            mValue)) {
            return mValue[0];
        }

        return defaultValue;
    }
}

假设我想要RGB_565。如果RGB_965(如果存在)会被选择,那么它会选择RGB_888吗?假设RGB_888在“configs”列表中排在第一位? - kiranpradeep
@Kiran 这是一个很好的问题。我担心这取决于OpenGL ES配置集列表(即数组EGLConfig [] config)的顺序,而且我相信这可能完全是特定设备的。如果你想确保,你可以改变config chooser,以便考虑颜色配置的上限。我的样本chooser并不那么严格,它只将用户给定的位深度视为最低要求,并愉快地选择任何超过该要求的东西。 - tiguchi
@Kiran 顺便说一下,我相信Android能够自动将RGB_888的EGL上下文转换为渲染目标表面视图的RGB_565。在我的调试会话中,我发现EGL_TRANSPARENT_TYPE标志实际上是主要的问题制造者(至少对于我的情况是这样)。如果选择器没有将值限制为EGL_NONE,则可能会意外选择需要表面视图本身支持半透明像素才能让底部的视图“透过”显示。这通常不是默认设置,在一些旧设备上甚至不被支持。 - tiguchi

4

我还没有足够的声望分数来添加评论,否则我会在Nobu Games的回答上简要评论。我遇到了同样的EGL_BAD_MATCH错误,他们的答案帮助我找到了正确的方向。因此,我不得不创建一个单独的答案。

正如Nobu Games所提到的,GLSurfaceView的PixelFormat与传递给setEGLConfigChooser()的像素格式参数似乎不匹配。在我的情况下,我请求RGBA8888,但我的GLSurfaceView是RGB565。这导致在初始化后出现EGL_BAD_MATCH错误。

对他们答案的增强是,您可以获取窗口所需的PixelFormat,并使用它动态选择EGL上下文。

为了使我的代码尽可能通用,我将GLSurfaceView更改为接受一个额外的参数--显示器的像素格式。我通过调用我的活动来获取这个参数:

getWindowManager().getDefaultDisplay().getPixelFormat();

我将这个值传递给GLSurfaceView,然后提取每个RGBA的最佳位深度,方法如下:
if (pixelFormatVal > 0) {

    PixelFormat info = new PixelFormat();
    PixelFormat.getPixelFormatInfo(pixelFormatVal, info);

    if (PixelFormat.formatHasAlpha(pixelFormatVal)) {

        if (info.bitsPerPixel >= 24) {
            m_desiredABits = 8;
        } else {
            m_desiredABits = 6;  // total guess
        }

    } else {
        m_desiredABits = 0;
    }

    if (info.bitsPerPixel >= 24) {
        m_desiredRBits = 8;
        m_desiredGBits = 8;
        m_desiredBBits = 8;
    } else if (info.bitsPerPixel >= 16) {
        m_desiredRBits = 5;
        m_desiredGBits = 6;
        m_desiredRBits = 5;
    } else {
        m_desiredRBits = 4;
        m_desiredGBits = 4;
        m_desiredBBits = 4;
    }

} else {
    m_desiredRBits = 8;
    m_desiredGBits = 8;
    m_desiredBBits = 8;
}

接着,我将这些值传递给我的配置选择器。这段代码在RGB565设备和RGBA8888设备上都能正常工作。

我假设供应商选择了默认值,因为它会提供最佳性能。当然,我没有任何依据支持这个说法,但这是我要采用的策略。


感谢您提供详细的答案!这看起来很有前途,但不幸的是,getPixelFormat()现在已经被弃用了,并且每次都会返回RGBA_8888,使其变得不那么有用。(请参见:http://developer.android.com/reference/android/view/Display.html#getPixelFormat)。我仍在寻找一个好的解决方案... - gkanwar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接