GLES10.glGetIntegerv возвращает 0 только в Lollipop

Этот фрагмент кода использовался для моего Nexus 7 2012 KitKat:

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

В KitKat я могу получить максимальное значение пикселя правильно, но после обновления до factory изображения Lollipop этот фрагмент кода вызывает проблему, поскольку он возвращает только 0. Лог-код показывал этот вывод, когда он достиг этого метода:

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

У меня уже есть android:hardwareAccelerated="true" в моем Manifest.xml. Есть ли какие-либо изменения API, о которых я не знаю, что приводит к невозможности использования вышеуказанного кода? Просьба сообщить.

Ответ 1

Журнал ошибок очень четко указывает на основную проблему:

вызывать OpenGL ES API без текущего контекста (регистрируется один раз в потоке)

Вам нужен текущий контекст OpenGL в вашем потоке, прежде чем вы сможете сделать любые вызовы OpenGL, включая ваш вызов glGetIntegerv(). Это всегда было правдой. Но похоже, что в pre-Lollipop существовал контекст OpenGL, который был создан в рамках, и это было иногда (всегда?), Когда вызывался код приложения.

Я не верю, что это когда-либо документировало или предполагало поведение. Приложения всегда должны явно создавать контекст и делать его актуальным, если они хотят делать вызовы OpenGL. И похоже, что это более строго соблюдается в Lollipop.

Существует два основных подхода к созданию контекста 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];

Чтобы сделать контекст current, который нам понадобится позже, вам нужна поверхность рендеринга, даже если вы на самом деле не планируете рендеринг. Чтобы удовлетворить это требование, создайте небольшую внеэкранную поверхность (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, доступную с уровня 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 1.x и ES 2.0.

Ответ 2

В сообщении об ошибке говорится, что вы вызываете функцию GLES до того, как существует контекст OpenGL ES. Я обнаружил, что KitKat более строг относительно правильности в нескольких областях, так что это может быть причиной появления проблемы сейчас, или может быть какая-то разница в том порядке, в котором вы запускаете приложение, которое вызывает его. Если вы указали больше своего кода инициализации, причина может быть более ясной.

Обычно у вас есть класс, который реализует GLSurfaceView.Renderer, который имеет функцию:

public void onSurfaceCreated(GL10 gl, EGLConfig config) 

В этой функции вы можете безопасно вызывать gl.glGetIntegerv, так как на данный момент вы знаете, что был создан контекст OpenGL ES. Если вы вызываете его раньше, чем это, то это объясняет ошибку, которую вы видите.