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
How to Create a Http Server in Android
Android: How to Convert String to Date
Why Are Most UI Frameworks Single Threaded
Asynctask and Looper.Prepare() Error
Distance Calculation from My Location to Destination Location in Android
System.Loadlibrary(...) Couldn't Find Native Library in My Case
React Native Android Fetch Failing on Connection to Local API
How to Know When an Edittext Loses Focus
How to Do a Http Post in Android
Android Automatic Horizontally Scrolling Textview
How to Create a Simple Divider in the New Navigationview
How to Add an Image on Edittext
Comparing Two Drawables in Android
Java.Lang.Noclassdeffounderror: Javax.Activation.Datahandler in Android
Fool-Proof Way to Handle Fragment on Orientation Change
Securityexception: Caller Uid Xxxx Is Different Than the Authenticator's Uid