在 Lollipop 版本中,GLES10.glGetIntegerv 仅返回 0

21

这段代码曾经在我的Nexus 7 2012 KitKat上正常运作:

int[] maxSize = new int[1];
GLES10.glGetIntegerv(GL10.GL_MAX_TEXTURE_SIZE, maxSize, 0);

在KitKat中,我可以正确获取最大像素值,但是升级到工厂镜像的Lollipop后,这段代码会出问题,因为它只返回0。当它到达这个方法时,logcat显示了这个输出:

E/libEGL﹕ call to OpenGL ES API with no current context (logged once per thread)

我在Manifest.xml中已经有 android:hardwareAccelerated="true" 了。是否有任何我不知道的API更改导致上述代码无法使用?请给予建议。


请查看http://stackoverflow.com/questions/27026332/detect-if-resolution-is-too-high-on-android-lollipop。 - Reto Koradi
@RetoKoradi - 不确定那个链接是否还值得提及;那里的回答没有详细信息;你在这里的回答更加详尽! - ToolmakerSteve
2个回答

64
错误日志非常清晰地指出了基本问题:

没有当前上下文的OpenGLES API调用(每个线程记录一次)

在进行任何OpenGL调用之前,包括你的glGetIntegerv()调用,你需要在你的线程中拥有一个当前的OpenGL上下文。这一点始终如此,但似乎在棒棒糖版本之前,在框架中创建了一个OpenGL上下文,并且在调用应用程序代码时有时(总是?)处于当前状态。
我不相信这曾经被记录或者是期望的行为。如果想要进行OpenGL调用,应用程序始终应该明确地创建并使其成为当前上下文。而且似乎在棒棒糖版本中更加严格执行此规定。
创建OpenGL上下文有两种主要方法:
  • 创建一个GLSurfaceView(文档)。这是最简单和最方便的方法,但只有在计划对显示器进行OpenGL渲染时才有意义。
  • 使用EGL14(文档)。这提供了一个更低级别的接口,允许您完成OpenGL调用所需的必要设置,而无需创建视图或对显示器进行渲染。
GLSurfaceView方法在各处都有广泛的文档、示例和教程。因此,我将重点介绍EGL方法。

使用EGL14 (API级别17)

下面的代码假定你关心ES 2.0,对于其他版本的ES,一些属性值需要进行调整。
在文件开始处导入EGL14类和几个相关的类。
import android.opengl.EGL14;
import android.opengl.EGLConfig;
import android.opengl.EGLContext;
import android.opengl.EGLDisplay;
import android.opengl.EGLSurface;
import android.opengl.GLES20;

获取默认显示器并进行初始化。如果你需要处理可能有多个显示设备的情况,这可能会更加复杂,但对于普通手机/平板电脑来说已经足够:

EGLDisplay dpy = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
int[] vers = new int[2];
EGL14.eglInitialize(dpy, vers, 0, vers, 1);

接下来,我们需要找到一个配置文件。由于我们不会将此上下文用于渲染,因此确切的属性并不是非常关键:

int[] configAttr = {
    EGL14.EGL_COLOR_BUFFER_TYPE, EGL14.EGL_RGB_BUFFER,
    EGL14.EGL_LEVEL, 0,
    EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
    EGL14.EGL_SURFACE_TYPE, EGL14.EGL_PBUFFER_BIT,
    EGL14.EGL_NONE
};
EGLConfig[] configs = new EGLConfig[1];
int[] numConfig = new int[1];
EGL14.eglChooseConfig(dpy, configAttr, 0,
                      configs, 0, 1, numConfig, 0);
if (numConfig[0] == 0) {
    // TROUBLE! No config found.
}
EGLConfig config = configs[0];

为了使上下文在稍后使用时处于当前状态,您需要一个渲染表面,即使您实际上没有计划进行渲染。为满足此要求,请创建一个小的离屏(Pbuffer)表面:
int[] surfAttr = {
    EGL14.EGL_WIDTH, 64,
    EGL14.EGL_HEIGHT, 64,
    EGL14.EGL_NONE
};
EGLSurface surf = EGL14.eglCreatePbufferSurface(dpy, config, surfAttr, 0);

接下来,创建上下文:

int[] ctxAttrib = {
    EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
    EGL14.EGL_NONE
};
EGLContext ctx = EGL14.eglCreateContext(dpy, config, EGL14.EGL_NO_CONTEXT, ctxAttrib, 0);

现在准备更新上下文:

EGL14.eglMakeCurrent(dpy, surf, surf, ctx);

如果上述所有步骤都成功(略去错误检查),现在就可以进行OpenGL调用:
int[] maxSize = new int[1];
GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_SIZE, maxSize, 0);

完成后,您可以拆除所有内容:

EGL14.eglMakeCurrent(dpy, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE,
                     EGL14.EGL_NO_CONTEXT);
EGL14.eglDestroySurface(dpy, surf);
EGL14.eglDestroyContext(dpy, ctx);
EGL14.eglTerminate(dpy);

使用 EGL10(API 级别 1)

如果您需要适用于早期级别的内容,可以使用 EGL10 (文档) 代替 EGL14,EGL10 自 API 级别 1 就已可用。如下所示是针对 1.0 改编后的代码:

import android.opengl.GLES10;
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLContext;
import javax.microedition.khronos.egl.EGLDisplay;
import javax.microedition.khronos.egl.EGLSurface;

EGL10 egl = (EGL10)EGLContext.getEGL();

EGLDisplay dpy = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
int[] vers = new int[2];
egl.eglInitialize(dpy, vers);

int[] configAttr = {
    EGL10.EGL_COLOR_BUFFER_TYPE, EGL10.EGL_RGB_BUFFER,
    EGL10.EGL_LEVEL, 0,
    EGL10.EGL_SURFACE_TYPE, EGL10.EGL_PBUFFER_BIT,
    EGL10.EGL_NONE
};
EGLConfig[] configs = new EGLConfig[1];
int[] numConfig = new int[1];
egl.eglChooseConfig(dpy, configAttr, configs, 1, numConfig);
if (numConfig[0] == 0) {
    // TROUBLE! No config found.
}
EGLConfig config = configs[0];

int[] surfAttr = {
    EGL10.EGL_WIDTH, 64,
    EGL10.EGL_HEIGHT, 64,
    EGL10.EGL_NONE
};
EGLSurface surf = egl.eglCreatePbufferSurface(dpy, config, surfAttr);
final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;  // missing in EGL10
int[] ctxAttrib = {
    EGL_CONTEXT_CLIENT_VERSION, 1,
    EGL10.EGL_NONE
};
EGLContext ctx = egl.eglCreateContext(dpy, config, EGL10.EGL_NO_CONTEXT, ctxAttrib);
egl.eglMakeCurrent(dpy, surf, surf, ctx);
int[] maxSize = new int[1];
GLES10.glGetIntegerv(GLES10.GL_MAX_TEXTURE_SIZE, maxSize, 0);
egl.eglMakeCurrent(dpy, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE,
                   EGL10.EGL_NO_CONTEXT);
egl.eglDestroySurface(dpy, surf);
egl.eglDestroyContext(dpy, ctx);
egl.eglTerminate(dpy);

请注意,此代码版本使用的是ES 1.x上下文。所报告的最大纹理尺寸可能与ES 2.0不同。

EGL14在API 17中被添加。是否有适用于minSdk 15的解决方案?我只想检查允许的最大像素,因此需要一个简单的解决方案。 - Neoh
既然你问到的是Lollipop,也就是API 21,我认为级别17应该不会有问题。我可以尝试添加一个适用于较低级别的版本。 - Reto Koradi
所有的方法EGL10.egl..()都会报错non-static method cannot be referenced from static context。我通过初始化一个对象EGL10 gl = (EGL10) EGLContext.getEGL()并使用该对象调用这些方法来部分解决了这个问题。当我将EGL14更改为EGL10时,另一个问题出现了,因为在int[] configAttr中没有变量EGL10.EGL_OPENGL_ES2_BIT。对于EGL10,configAttr数组应该是什么样子的? - Neoh
我已经编辑了代码,使其适用于我的情况。也许你应该相应地编辑EGL14。谢谢。 - Neoh
这个真不错,高个好评!感谢你抽出时间来解释。 - zgc7009
显示剩余5条评论

1
错误信息表明您在OpenGL ES上下文存在之前就调用了GLES函数。我发现KitKat在几个方面更加严格,因此这可能是问题出现的原因,或者您的应用程序启动顺序存在某些差异导致它出现。如果您发布更多的初始化代码,则原因可能会更清楚。
通常您有一个实现GLSurfaceView.Renderer的类,该类具有一个函数:
public void onSurfaceCreated(GL10 gl, EGLConfig config) 

在这个函数中,您应该能够安全地调用gl.glGetIntegerv,因为此时您知道已创建OpenGL ES上下文。如果您在此之前调用它,则会解释您看到的错误。

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