Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

GLES10.glGetIntegerv returns 0 in Lollipop only

This piece of code used to work in my Nexus 7 2012 KitKat:

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

In KitKat I can obtain the max pixel value correctly, but after the upgrade to factory image Lollipop this snippet of code causes problem as it only returns 0. The logcat showed this output when it reached this method:

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

I already have android:hardwareAccelerated="true" in my Manifest.xml. Is there any API changes that I am not aware of, that causes the above code unusable? Please advise.

like image 965
Neoh Avatar asked Nov 18 '14 02:11

Neoh


2 Answers

The error log points out the basic problem very clearly:

call to OpenGL ES API with no current context (logged once per thread)

You need a current OpenGL context in your thread before you can make any OpenGL calls, which includes your glGetIntegerv() call. This was always true. But it seems like in pre-Lollipop, there was an OpenGL context that was created in the frameworks, and that was sometimes (always?) current when app code was called.

I don't believe this was ever documented or intended behavior. Apps were always supposed to explicitly create a context, and make it current, if they wanted to make OpenGL calls. And it appears like this is more strictly enforced in Lollipop.

There are two main approaches to create an OpenGL context:

  • Create a GLSurfaceView (documentation). This is the easiest and most convenient approach, but only really makes sense if you plan to do OpenGL rendering to the display.
  • Use EGL14 (documentation). This provides a lower level interface that allows you to complete the necessary setup for OpenGL calls without creating a view or rendering to the display.

The GLSurfaceView approach is extensively documented with examples and tutorials all over the place. So I will focus on the EGL approach.

Using EGL14 (API level 17)

The following code assumes that you care about ES 2.0, some attribute values would have to be adjusted for other ES versions.

At the start of the file, import the EGL14 class, and a few related classes:

import android.opengl.EGL14;
import android.opengl.EGLConfig;
import android.opengl.EGLContext;
import android.opengl.EGLDisplay;
import android.opengl.EGLSurface;
import android.opengl.GLES20;

Then get a hold of the default display, and initialize. This could get more complex if you have to deal with devices that could have multiple displays, but will be sufficient for a typical phone/tablet:

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

Next, we need to find a config. Since we won't use this context for rendering, the exact attributes aren't very critical:

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];

To make a context current, which we will need later, you need a rendering surface, even if you don't actually plan to render. To satisfy this requirement, create a small offscreen (Pbuffer) surface:

int[] surfAttr = {
    EGL14.EGL_WIDTH, 64,
    EGL14.EGL_HEIGHT, 64,
    EGL14.EGL_NONE
};
EGLSurface surf = EGL14.eglCreatePbufferSurface(dpy, config, surfAttr, 0);

Next, create the context:

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

Ready to make the context current now:

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

If all of the above succeeded (error checking was omitted), you can make your OpenGL calls now:

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

Once you're all done, you can tear down everything:

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

Using EGL10 (API level 1)

If you need something that works for earlier levels, you can use EGL10 (documentation) instead of EGL14, which has been available since API level 1. The code above adopted for 1.0 looks like this:

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

Note that this version of the code uses an ES 1.x context. The reported maximum texture size can be different for ES 1.x and ES 2.0.

like image 149
Reto Koradi Avatar answered Nov 05 '22 20:11

Reto Koradi


The error message is saying that you are calling the GLES function before the OpenGL ES context exists. I have found that KitKat is stricter about correctness in several areas so that may be the reason for the problem appearing now, or there may be some difference in the order in which you app is starting up that is causing it. If you posted more of your initialisation code, the reason may be clearer.

Typically you have a class that implements GLSurfaceView.Renderer that has a function:

public void onSurfaceCreated(GL10 gl, EGLConfig config) 

In this function, you should be able to call gl.glGetIntegerv safely as at this point you know that the OpenGL ES context has been created. If you are calling it earlier than this, then that would explain the error you are seeing.

like image 1
Muzza Avatar answered Nov 05 '22 18:11

Muzza