21

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.

Neoh
  • 15,906
  • 14
  • 66
  • 78

2 Answers2

64

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.

Reto Koradi
  • 53,228
  • 8
  • 93
  • 133
  • EGL14 is added in API 17. Is there a solution for minSdk 15? I just want to check the maximum pixel allowed, so a minimal solution is desired. – Neoh Nov 24 '14 at 05:42
  • Since you asked about Lollipop, which is API level 21, I thought level 17 would not be a problem. I can try and add a version that works with a lower level. – Reto Koradi Nov 24 '14 at 05:58
  • All the methods `EGL10.egl..()` give error `non-static method cannot be referenced from static context`. I partially solved it by initializing an object `EGL10 gl = (EGL10) EGLContext.getEGL()` and call those methods using this object. There is another problem when I change EGL14 to EGL10 because in `int[] configAttr` there is no variable `EGL10.EGL_OPENGL_ES2_BIT`. How should this `configAttr` array look like for EGL10? – Neoh Nov 24 '14 at 07:51
  • Ah, yes, the methods are not static in EGL10. Fixed that now in the answer. Sorry, I didn't have a chance to test the code today. I believe you can just leave off the `EGL_RENDERABLE_TYPE` attribute for ES 1.x. If you check out the EGL10 version in the second half of my answer, that's what I did. – Reto Koradi Nov 24 '14 at 08:05
  • In the line `EGLConfig config = gl.eglChooseConfig(dpy, configAttr, configs, 1, numConfig)` I got error `incompatible types. Required: javax.microedition.khronos.egl.EGLConfig Found: boolean` – Neoh Nov 24 '14 at 08:36
  • I have edited to code to make it work for my case. Perhaps you should edit accordingly for EGL14 too. Thanks – Neoh Nov 24 '14 at 12:59
  • Ok, both versions of the code should be correct now. I also added an important note at the end. A device I tried it on reported a maximum size of 4096 for ES 1.x contexts, and 8192 for ES 2.0 contexts. – Reto Koradi Nov 24 '14 at 18:46
  • High five for this one! Thanks for taking the time to explain – zgc7009 Mar 20 '16 at 20:25
  • @Reto Koradi Why did you use 64*64 for off screen surface? Can I use 1*1, what will this size affect? – dragonfly Oct 21 '16 at 04:26
  • @dragonfly Good question. I thought there might be a minimal size, and chose a somewhat arbitrary size that I thought would probably be larger than the typical minimum size. But I actually can't find anything in the specs about a minimum size for pbuffers. So 1*1 should most likely be fine. – Reto Koradi Oct 22 '16 at 02:48
1

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.

Muzza
  • 1,236
  • 9
  • 13