之前学习OpenGL的时候,基本上都是使用GLSurfaceView来初始化,然后调用OpenGL的API来进行绘制。然而找OpenGL的教程时,发现基本上的教程都是C,这就很尴尬了呀,Android平台虽然也封装了名字类似的Java 的API,但是总感觉怪怪的。大概看了一下GLSurfaceView的源码,其实就是继承SurfaceView,然后开启一个线程来初始化EGL环境,接着也是使用OpenGL的API来绘制。那么EGL是什么呢?
EGL™在Khronos 的图形渲染API比如 OpenGL ES or Open VG与本地系统底层窗口之间的接口。它处理图形上下文管理,表面/缓冲区绑定和渲染同步,并使用其他Khronos API实现高性能,加速,混合模式2D和3D渲染。
下面我们通过分析EglHelper来查看native代码的初始化流程。
EglHelper类中,初始化主要调用了两个方法。
/**
* Initialize EGL for a given configuration spec.
* @param configSpec
*/
public void start() {
if (LOG_EGL) {
Log.w("EglHelper", "start() tid=" + Thread.currentThread().getId());
}
/*
* Get an EGL instance
*/
mEgl = (EGL10) EGLContext.getEGL();
/*
* Get to the default display.
*/
mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
if (mEglDisplay == EGL10.EGL_NO_DISPLAY) {
throw new RuntimeException("eglGetDisplay failed");
}
/*
* We can now initialize EGL for that display
*/
int[] version = new int[2];
if(!mEgl.eglInitialize(mEglDisplay, version)) {
throw new RuntimeException("eglInitialize failed");
}
GLSurfaceView view = mGLSurfaceViewWeakRef.get();
if (view == null) {
mEglConfig = null;
mEglContext = null;
} else {
mEglConfig = view.mEGLConfigChooser.chooseConfig(mEgl, mEglDisplay);
/*
* Create an EGL context. We want to do this as rarely as we can, because an
* EGL context is a somewhat heavy object.
*/
mEglContext = view.mEGLContextFactory.createContext(mEgl, mEglDisplay, mEglConfig);
}
if (mEglContext == null || mEglContext == EGL10.EGL_NO_CONTEXT) {
mEglContext = null;
throwEglException("createContext");
}
if (LOG_EGL) {
Log.w("EglHelper", "createContext " + mEglContext + " tid=" + Thread.currentThread().getId());
}
mEglSurface = null;
}
/**
* Create an egl surface for the current SurfaceHolder surface. If a surface
* already exists, destroy it before creating the new surface.
*
* @return true if the surface was created successfully.
*/
public boolean createSurface() {
if (LOG_EGL) {
Log.w("EglHelper", "createSurface() tid=" + Thread.currentThread().getId());
}
/*
* Check preconditions.
*/
if (mEgl == null) {
throw new RuntimeException("egl not initialized");
}
if (mEglDisplay == null) {
throw new RuntimeException("eglDisplay not initialized");
}
if (mEglConfig == null) {
throw new RuntimeException("mEglConfig not initialized");
}
/*
* The window size has changed, so we need to create a new
* surface.
*/
destroySurfaceImp();
/*
* Create an EGL surface we can render into.
*/
GLSurfaceView view = mGLSurfaceViewWeakRef.get();
if (view != null) {
mEglSurface = view.mEGLWindowSurfaceFactory.createWindowSurface(mEgl,
mEglDisplay, mEglConfig, view.getHolder());
} else {
mEglSurface = null;
}
if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {
int error = mEgl.eglGetError();
if (error == EGL10.EGL_BAD_NATIVE_WINDOW) {
Log.e("EglHelper", "createWindowSurface returned EGL_BAD_NATIVE_WINDOW.");
}
return false;
}
/*
* Before we can issue GL commands, we need to make sure
* the context is current and bound to a surface.
*/
if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
/*
* Could not make the context current, probably because the underlying
* SurfaceView surface has been destroyed.
*/
logEglErrorAsWarning("EGLHelper", "eglMakeCurrent", mEgl.eglGetError());
return false;
}
return true;
}
大部分的实现在EGLImpl类中,大概看一下初始化环境的流程。
这里首先调用了native方法,然后封装了一个EGLDisplayImpl返回。先看native方法。
public synchronized EGLDisplay eglGetDisplay(Object native_display) {
long value = _eglGetDisplay(native_display);
if (value == 0) {
return EGL10.EGL_NO_DISPLAY;
}
if (mDisplay.mEGLDisplay != value)
mDisplay = new EGLDisplayImpl(value);
return mDisplay;
}
这个cpp文件是com_google_android_gles_jni_EGLImpl.cpp,调用了eglGetDisplay方法返回一个jlong。
static jlong jni_eglGetDisplay(JNIEnv *_env, jobject _this, jobject native_display) {
return reinterpret_cast(eglGetDisplay(EGL_DEFAULT_DISPLAY));
}
这个直接是一个native方法,通过解封装拿到上一步初始化的EGLDisplayImpl里的mEGLDisplay(所以封装一层的目的是什么)。然后调用eglInitialize()进行初始化。
static jboolean jni_eglInitialize(JNIEnv *_env, jobject _this, jobject display,
jintArray major_minor) {
if (display == NULL || (major_minor != NULL &&
_env->GetArrayLength(major_minor) < 2)) {
jniThrowException(_env, "java/lang/IllegalArgumentException", NULL);
return JNI_FALSE;
}
EGLDisplay dpy = getDisplay(_env, display);
EGLBoolean success = eglInitialize(dpy, NULL, NULL);
if (success && major_minor) {
int len = _env->GetArrayLength(major_minor);
if (len) {
// we're exposing only EGL 1.0
//我们在将java类型转到native时,可能会使用的是拷贝数据或者jvm堆中的原始数据
//该方法严格请求原始数据的指针,然而jvm同样可能会创建新的副本
//而且使用的时候会有诸多限制
jint* base = (jint *)_env->GetPrimitiveArrayCritical(major_minor, (jboolean *)0);
if (len >= 1) base[0] = 1;
if (len >= 2) base[1] = 0;
_env->ReleasePrimitiveArrayCritical(major_minor, base, 0);
}
}
return EglBoolToJBool(success);
}
接下来是chooseConfig,先看GLSurfaceView中的实现,大概就是先获取config的数量,然后再获取实际的config,并进行筛选。
public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
int[] num_config = new int[1];
if (!egl.eglChooseConfig(display, mConfigSpec, null, 0,
num_config)) {
throw new IllegalArgumentException("eglChooseConfig failed");
}
int numConfigs = num_config[0];
if (numConfigs <= 0) {
throw new IllegalArgumentException(
"No configs match configSpec");
}
EGLConfig[] configs = new EGLConfig[numConfigs];
if (!egl.eglChooseConfig(display, mConfigSpec, configs, numConfigs,
num_config)) {
throw new IllegalArgumentException("eglChooseConfig#2 failed");
}
EGLConfig config = chooseConfig(egl, display, configs);
if (config == null) {
throw new IllegalArgumentException("No config chosen");
}
return config;
}
egl.eglChooseConfig同样是一个native方法,从实现我们可以看到,如果第configs参数为NULL将只返回config的数量,如果不为NULL将会生成新的java对象,填充回该对象的class为com/google/android/gles_jni/EGLConfigImpl。最终获取的在configs数组中。
static jboolean jni_eglChooseConfig(JNIEnv *_env, jobject _this, jobject display,
jintArray attrib_list, jobjectArray configs, jint config_size, jintArray num_config) {
if (display == NULL
|| !validAttribList(_env, attrib_list)
|| (configs != NULL && _env->GetArrayLength(configs) < config_size)
|| (num_config != NULL && _env->GetArrayLength(num_config) < 1)) {
jniThrowException(_env, "java/lang/IllegalArgumentException", NULL);
return JNI_FALSE;
}
EGLDisplay dpy = getDisplay(_env, display);
EGLBoolean success = EGL_FALSE;
if (configs == NULL) {
config_size = 0;
}
EGLConfig nativeConfigs[config_size];
int num = 0;
jint* attrib_base = beginNativeAttribList(_env, attrib_list);
success = eglChooseConfig(dpy, attrib_base, configs ? nativeConfigs : 0, config_size, &num);
endNativeAttributeList(_env, attrib_list, attrib_base);
if (num_config != NULL) {
_env->SetIntArrayRegion(num_config, 0, 1, (jint*) &num);
}
if (success && configs!=NULL) {
for (int i=0 ; iNewObject(gConfig_class, gConfig_ctorID, reinterpret_cast(nativeConfigs[i]));
_env->SetObjectArrayElement(configs, i, obj);
}
}
return EglBoolToJBool(success);
}
拿到config后我们就可以创建上下文了。这里在java层也是调用jni方法,然后简单封装返回。
public EGLContext eglCreateContext(EGLDisplay display, EGLConfig config, EGLContext share_context, int[] attrib_list) {
long eglContextId = _eglCreateContext(display, config, share_context, attrib_list);
if (eglContextId == 0) {
return EGL10.EGL_NO_CONTEXT;
}
return new EGLContextImpl( eglContextId );
}
native方法,通过反射拿到对应的值,java包装类中的属性,然后创建eglCreateContext
static jlong jni_eglCreateContext(JNIEnv *_env, jobject _this, jobject display,
jobject config, jobject share_context, jintArray attrib_list) {
if (display == NULL || config == NULL || share_context == NULL
|| !validAttribList(_env, attrib_list)) {
jniThrowException(_env, "java/lang/IllegalArgumentException", NULL);
return JNI_FALSE;
}
EGLDisplay dpy = getDisplay(_env, display);
EGLConfig cnf = getConfig(_env, config);
EGLContext shr = getContext(_env, share_context);
jint* base = beginNativeAttribList(_env, attrib_list);
EGLContext ctx = eglCreateContext(dpy, cnf, shr, base);
endNativeAttributeList(_env, attrib_list, base);
return reinterpret_cast(ctx);
}
接下来是eglCreateWindowSurface,我们先拿到surface,然后调用native方法创建WindoSurface。
public EGLSurface eglCreateWindowSurface(EGLDisplay display, EGLConfig config, Object native_window, int[] attrib_list) {
Surface sur = null;
if (native_window instanceof SurfaceView) {
SurfaceView surfaceView = (SurfaceView)native_window;
sur = surfaceView.getHolder().getSurface();
} else if (native_window instanceof SurfaceHolder) {
SurfaceHolder holder = (SurfaceHolder)native_window;
sur = holder.getSurface();
} else if (native_window instanceof Surface) {
sur = (Surface) native_window;
}
long eglSurfaceId;
if (sur != null) {
eglSurfaceId = _eglCreateWindowSurface(display, config, sur, attrib_list);
} else if (native_window instanceof SurfaceTexture) {
eglSurfaceId = _eglCreateWindowSurfaceTexture(display, config,
native_window, attrib_list);
} else {
throw new java.lang.UnsupportedOperationException(
"eglCreateWindowSurface() can only be called with an instance of " +
"Surface, SurfaceView, SurfaceHolder or SurfaceTexture at the moment.");
}
if (eglSurfaceId == 0) {
return EGL10.EGL_NO_SURFACE;
}
return new EGLSurfaceImpl( eglSurfaceId );
}
这个方法也比较简单,一开是又是去拿包装类的属性,然后_eglCreateWindowSurface
static jlong jni_eglCreateWindowSurface(JNIEnv *_env, jobject _this, jobject display,
jobject config, jobject native_window, jintArray attrib_list) {
if (display == NULL || config == NULL
|| !validAttribList(_env, attrib_list)) {
jniThrowException(_env, "java/lang/IllegalArgumentException", NULL);
return JNI_FALSE;
}
EGLDisplay dpy = getDisplay(_env, display);
EGLContext cnf = getConfig(_env, config);
sp window;
if (native_window == NULL) {
not_valid_surface:
jniThrowException(_env, "java/lang/IllegalArgumentException",
"Make sure the SurfaceView or associated SurfaceHolder has a valid Surface");
return 0;
}
window = android_view_Surface_getNativeWindow(_env, native_window);
if (window == NULL)
goto not_valid_surface;
jint* base = beginNativeAttribList(_env, attrib_list);
EGLSurface sur = eglCreateWindowSurface(dpy, cnf, window.get(), base);
endNativeAttributeList(_env, attrib_list, base);
return reinterpret_cast(sur);
}
最后一步也是直接调用eglMakeCurrent,这里中间又通过反射去拿属性,所以封装过去封装过来,头疼。。
static jboolean jni_eglMakeCurrent(JNIEnv *_env, jobject _this, jobject display, jobject draw, jobject read, jobject context) {
if (display == NULL || draw == NULL || read == NULL || context == NULL) {
jniThrowException(_env, "java/lang/IllegalArgumentException", NULL);
return JNI_FALSE;
}
EGLDisplay dpy = getDisplay(_env, display);
EGLSurface sdr = getSurface(_env, draw);
EGLSurface srd = getSurface(_env, read);
EGLContext ctx = getContext(_env, context);
return EglBoolToJBool(eglMakeCurrent(dpy, sdr, srd, ctx));
}
总之,这几步完成之后,我们终于可以通过OpenGL的API来绘制东西了。在java层我们只需要额外传一个surface就可以了,其他的都可以在native层操作了,绘制完成之后要记得调用eglSwapBuffers来将数据显示在屏幕上。下一篇文章照例分析书上的OpenGL的demo。最后附上一些参考资料。