GLES10.glGetIntegerv возвращает 0 только в Lollipop
Этот фрагмент кода использовался в моем Nexus 7 2012 KitKat:
int[] maxSize = new int[1];
GLES10.glGetIntegerv(GL10.GL_MAX_TEXTURE_SIZE, maxSize, 0);
В KitKat я могу получить значение максимального пикселя правильно, но после обновления до заводского образа Lollipop этот фрагмент кода вызывает проблему, поскольку он возвращает только 0. Logcat показал этот вывод, когда он достиг этого метода:
E/libEGL﹕ call to OpenGL ES API with no current context (logged once per thread)
у меня уже есть android:hardwareAccelerated="true"
в моем Manifest.xml. Существуют ли какие-либо изменения API, о которых я не знаю, что приводит к невозможности использования вышеуказанного кода? Пожалуйста, порекомендуйте.
2 ответа
Журнал ошибок очень четко указывает на основную проблему:
вызов API OpenGL ES без текущего контекста (регистрируется один раз для потока)
Вам нужен текущий контекст 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];
Чтобы сделать контекст текущим, что нам понадобится позже, вам нужна поверхность рендеринга, даже если вы на самом деле не планируете рендеринг. Чтобы удовлетворить это требование, создайте небольшую поверхность за пределами экрана (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.
В сообщении об ошибке говорится, что вы вызываете функцию GLES до появления контекста OpenGL ES. Я обнаружил, что KitKat более строго относится к правильности в нескольких областях, поэтому это может быть причиной проблемы, возникающей сейчас, или может быть некоторая разница в порядке запуска приложения, которое вызывает его. Если вы отправили больше своего кода инициализации, причина может быть более ясной.
Обычно у вас есть класс, который реализует GLSurfaceView.Renderer
это имеет функцию:
public void onSurfaceCreated(GL10 gl, EGLConfig config)
В этой функции вы должны быть в состоянии вызвать gl.glGetIntegerv
безопасно, так как на данный момент вы знаете, что контекст OpenGL ES был создан. Если вы звоните раньше, чем это, то это объясняет ошибку, которую вы видите.