前段时间为了这个问题纠结了很久,总算找到了还算不错的解决方法,现总结如下。
首先要明白我说的是一个什么东西:
我们要使用OpenGL进行后台处理,但并不需要实时显示到我们app的窗口上,而仅仅存储在Bitmap中。涉及的应用场景有很多比如:你需要做一个图像处理的app,你希望界面上最多有个image view之类的东西显示待处理/处理好的图片。所以你不可能会为了后台处理而在前台界面上挂上一个glSurfaceView或者glTextureView之类的东西上去。
因为对Android并不熟悉,我在谷歌和百度上查找了很久,甚至跑到stackoverflow.com上去提问,总算找到可行的方案:PBufferSurface 和 PixmapSurface
但是找到的方案都没有例子,大概像这样用的人比较少,描述到它们的时候都是一句话带过,所以本文将详细描述一下。
安卓下使用EGL来创建context。首先是khronos官方文档描述: eglCreatePbufferSurface 以及 eglCreatePixmapSurface
看了这两个函数,你大概就明白了。那么我们应该使用哪个呢?
首先要明白你的需求,假如你是要处理图像的话,显然不管你用哪个,你最终都必须绘制到FBO里面再取出来的。为什么? 因为你在创建context的时候是不知道你要处理的图像的大小的(……如果你要每次加载图片就重新初始化,那……我不管了)。所以就算你用了pixmap surface,直接绘制的话也会因为图像大小而纠结不已。
所以本文直接选择了pbuffer,效率比pixmap更高。
那么我们要做的就很简单了,首先使用adt创建一个demo吧,在主activity的onCreate方法前面加上: @SuppressLint(“NewApi”). 如果已经加了的话就不用管了。
为了便于管理,我们新建一个java文件把所有的初始化写到一个class里面:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
package org.wysaid.ndkopenglbackdraw; 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.opengles.GL10; import android.graphics.Bitmap; import android.util.Log; public class GLHelpFunctions { public static native void getGLBackDrawImage(Bitmap bm); // use GLES2.0. static int EGL_CONTEXT_CLIENT_VERSION = 0x3098; static int[] attrib_list = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE }; static private int[] version = new int[2]; static EGLConfig[] configs = new EGLConfig[1]; static int[] num_config = new int[1]; static int[] configSpec = { EGL10.EGL_SURFACE_TYPE, EGL10.EGL_PBUFFER_BIT, EGL10.EGL_RED_SIZE, 8, EGL10.EGL_GREEN_SIZE, 8, EGL10.EGL_BLUE_SIZE, 8, EGL10.EGL_ALPHA_SIZE, 8, EGL10.EGL_NONE }; // eglCreatePbufferSurface used this config static int attribListPbuffer[] = { // The NDK code would never draw to Pbuffer, so it's not neccessary to // match anything. EGL10.EGL_WIDTH, 32, EGL10.EGL_HEIGHT, 32, EGL10.EGL_NONE }; static EGL10 mEgl; static GL10 gl; static javax.microedition.khronos.egl.EGLSurface mEglPBSurface; static EGLContext mEglContext; static EGLConfig mEglConfig; static EGLDisplay mEglDisplay; static public void initEGL() { mEgl = (EGL10) EGLContext.getEGL(); mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); mEgl.eglInitialize(mEglDisplay, version); mEgl.eglChooseConfig(mEglDisplay, configSpec, configs, 1, num_config); mEglConfig = configs[0]; mEglContext = mEgl.eglCreateContext(mEglDisplay, mEglConfig, EGL10.EGL_NO_CONTEXT, attrib_list); if (mEglContext == EGL10.EGL_NO_CONTEXT) { Log.d("ERROR:", "eglCreateContext Failed!"); } mEglPBSurface = mEgl.eglCreatePbufferSurface(mEglDisplay, mEglConfig, attribListPbuffer); if (mEglPBSurface == EGL10.EGL_NO_SURFACE) { Log.d("ERROR:", "eglCreatePbufferSurface Failed!"); } if (!mEgl.eglMakeCurrent(mEglDisplay, mEglPBSurface, mEglPBSurface, mEglContext)) { Log.d("ERROR:", "eglMakeCurrent failed:" + mEgl.eglGetError()); } // You can do some works using OpenGL with java code. But this demo would do that within NDK. gl = (GL10) mEglContext.getGL(); } static public void enableEGL() { if (!mEgl.eglMakeCurrent(mEglDisplay, mEglPBSurface, mEglPBSurface, mEglContext)) { Log.d("ERROR:", "eglMakeCurrent failed:" + mEgl.eglGetError()); } } } |
然后在创建的时候调用 initEGL, 使用后台绘图之前调用enableEGL即可。
当然,也许你还是可能会遇到各种各样的问题,所以给出一个完整的demo以供参考:
点击跳转到demo下载页面。 <– 如果打不开,请把地址的https换成http,谷歌的服务器比较扯蛋。
来一张demo效果图吧。界面上只有一个button和一个imageView。
实在是太好了,现在正常了,多谢多谢!
大师兄,为啥虚拟机上能正常显示,到了真机上就没有显示了?
嗯,当时也发现了,但是已经上传了,就懒得管了,你们如果看一下shader代码的话应该能看出来的。GLES不默认gl_FragColor.a为1
你在Fragment shader末尾加上一句 gl_FragColor.a = 1.0; 就ok了
多谢大师兄回复。不过刚刚加了还是显示不了……
好像是我记错了,是vertex shader里面不同,我重传了一份,你重新下载一下,还是上面的地址