Gles10.Glgetintegerv Returns 0 in Lollipop Only

GLES10.glGetIntegerv returns 0 in Lollipop only

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.

Detect if resolution is too high on Android Lollipop

That GL query requires an active GL context on the thread. Previously your app was accidentally using framework's private GL context to perform that query. In Lollipop due to various architecture changes this context no longer leaks into application code, hence why the query is now failing. If you look in logcat you will probably see something like: "call to opengl es api with no current context".

You can create your own GL context to perform that query if you want. Alternatively you can assume the screen resolution is the maximum, or use a scalable solution such as a tiled approach.

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

The problem is exactly what the error message tells you: You're trying to make OpenGL calls (glGetString) without having a current OpenGL context. OpenGL calls require a current context in the thread you're making the calls from.

The typical use of OpenGL includes using a GLSurfaceView, which takes care of creating a context, and making it current. If you do need to make OpenGL calls from other places in the code, you need to take care of creating an OpenGL context yourself. This is done using the EGL10 or EGL14 class. You can find complete code in my answer here: GLES10.glGetIntegerv returns 0 in Lollipop only.

Why it works from the button click handler is less clear. It seems like at least some versions of Android have a "stray" OpenGL context that happens to be current under certain circumstances. I wouldn't count on that working consistently, though. For example, as you can see in the question liked above, some people got burnt by relying on this when it stopped working on Lollipop.

How to render Bitmap off-screen in Android using OpenGL?

Pixel buffer must be copied to bitmap:

val mPixelBuf bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
bitmap.copyPixelsFromBuffer(mPixelBuf)
return bitmap

How to use OpenGL without displaying it?

You don't have to use GLSurfaceView to do OpenGL rendering. If you look at the source code, you can see that it's only using only publicly available APIs. It is merely a convenience class that makes the most common uses of OpenGL under Android (using OpenGL to draw the content of a view) very easy. If your use case is different then you just... don't use it.

The API you use for creating contexts and rendering surfaces more directly is EGL. There are two versions of it available in the Android Java frameworks: EGL10 and EGL14. EGL10 is very old, and I would strongly recommend using EGL14.

The EGL calls are not really documented in the Android SDK documentation, but you can use the man pages on www.khronos.org to see the calls explained.

Using EGL directly, you can create a context and an off-screen rendering surface that will allow you to use OpenGL without any kind of view.

I posted complete code showing how to create a context with an off-screen rendering surface in a previous answer here: GLES10.glGetIntegerv returns 0 in Lollipop only.

how to test that there is a current OpenGL ES context when using the Android NDK?

To check if there's a current context in native code, you can call eglGetCurrentContext():

if (eglGetCurrentContext() != EGL_NO_CONTEXT) {
// There is a current context.
}

Yes, contexts remain current across the JNI boundary. So if you have a current context in your Java code (e.g. managed by GLSurfaceView), you can make JNI calls, and then make OpenGL calls in the implementation of the native function, without any special consideration. I answered a question related to this in more detail here: FrameBuffers with GLSurfaceView pattern in OpenGLES 1.1 on android ndk.

There's no good reason why the opposite wouldn't be true. You should be able to create a context in native code, make it current, and it should still be current in your Java code after the JNI call returns. It seems much less useful, but if you have a good reason to do this, nothing should stop you.

If you really do want to create contexts in native code, I posted a complete example of how to do that in an answer here: GLES10.glGetIntegerv returns 0 in Lollipop only.

IMHO, by far the easiest way to use native code with OpenGL calls is to use a GLSurfaceView, let it handle the whole context creation and management, and make JNI calls in the implementation of the Renderer. Then in the native code, you simply make your OpenGL calls, without worrying about context management.



Related Topics



Leave a reply



Submit