完整代码查看# AndroidShaderDemo下的NativeActivity
代码参考# Android-OpenGLRenderer
不过原代码是使用libpng对图片解码,并在native层建立个RenderLoop,这里改用jnigraphics库,直接画图,代码更加容易理解。
之前写的《Android基于Shader的图像处理》是在java层使用OpenGL ES画图,因为Android控件GLSurfaceView底层封装了对OpenGL ES的使用环境。这里使用NDK在Native层使用OpenGL ES开发,搭建Native的OpenGL ES开发环境。
搭建主要过程如下:
1、在Android.mk文件中加入EGL,OpenGL ES等库和头文件。
2、初始化EGL,创建Open GL上下文环境。
3、连接EGL和屏幕。
前面的很好理解,最后一步的屏幕是什么?其实就是抽象出来Surface,在Android中就是SurfaceView,这样就理解了,因为是在Native层开发,所以通过jni将java层的SurfaceView传到C++层,下面会讲解。
一、Android.mk文件中加入EGL,OpenGL ES等库和头文件
完整的Android.mk如下,按格式写,没什么好说
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_CFLAGS += -D__STDC_CONSTANT_MACROS
LOCAL_SRC_FILES = \
./NativeController.cpp
LOCAL_STATIC_LIBRARIES := librender
LOCAL_LDLIBS := -L$(SYSROOT)/usr/lib -llog
LOCAL_LDLIBS += -lz
LOCAL_LDLIBS += -landroid
LOCAL_LDLIBS += -ljnigraphics
# Link with OpenGL ES
LOCAL_LDLIBS += -lGLESv2
LOCAL_LDLIBS += -lEGL
LOCAL_MODULE := libdrawdemo
include $(BUILD_SHARED_LIBRARY)
include $(call all-makefiles-under,$(LOCAL_PATH))
二、初始化EGL和创建Open GL上下文环境
Demo里将对EGL的使用封装成为类EGLCore,在init函数里完成初始化,初始化的过程也是按流程走,分下面几步:
1、eglGetDisplay取得显示设备
2、eglInitialize初始化显示设备
3、eglChooseConfig配置颜色,像素格式,缓存等。
4、eglCreateContext使用前面的显示设备和配置信息来创建OpenGL上下文环境。
完整代码查看egl_core.cpp的init函数。
三、连接EGL和屏幕
这里从java层开始看,在java层使用SurfaceView显示OpenGL 画出来的东西。在NativeActivity里创建SurfaceView,然后将它添加在根布局上。同时设置回调。这些是使用SurfaceView的普通操作,没什么特别。
java层和native层的桥梁是NativeController
public class NativeController {
public native void init();
public native void setSurface(Surface surface);
public native void resetSize(int width, int height);
public native void showBitmap(Bitmap bitmap);
public native void stop();
}
看NativeController的方法名就知道,NativeController的主要作用就是调用native层的C++函数。比如init()方法通过NativeController.cpp最后调用到了之前说的EGLCore初始化函数init()。
看下回调方法surfaceCreated完整代码:
@Override
public void surfaceCreated(SurfaceHolder holder) {
nativeController = new NativeController();
nativeController.init();//初始化EGL
nativeController.setSurface(holder.getSurface());
}
首先生成NativeController对象,通过它的一步步调用到EGLCore初始化函数init()完成EGL的初始化。
然后通过nativeController.setSurface(holder.getSurface());把SurfaceView传到native层。来看看在native层做了什么:
JNIEXPORT void JNICALL Java_com_andev_androidshaderdemo_nativedemo_NativeController_setSurface
(JNIEnv * env, jobject obj, jobject surface){
if (surface != 0 && NULL != controller) {
window = ANativeWindow_fromSurface(env, surface);
controller->setWindow(window);
} else if (window != 0) {
ANativeWindow_release(window);
window = 0;
}
}
其实就是通过ANativeWindow_fromSurface函数创建出一个window,这个window就表示设备屏幕。然后通过controller->setWindow(window);把这个window传给EGLCore来创建个EGLSurface,也就是能显示的Surface,代码如下:
EGLSurface EGLCore::createWindowSurface(ANativeWindow* _window) {
EGLSurface surface = NULL;
EGLint format;
if (!eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &format)) {
LOGE("eglGetConfigAttrib() returned error %d", eglGetError());
release();
return surface;
}
ANativeWindow_setBuffersGeometry(_window, 0, 0, format);
if (!(surface = eglCreateWindowSurface(display, config, _window, 0))) {
LOGE("eglCreateWindowSurface() returned error %d", eglGetError());
}
return surface;
}
这样就完成了EGL和显示屏幕的连接。
三、画图
上面把Native层 OpenGL ES开发环境搭建完成了,下面就可以画图了,这里是显示drawable下面的图片。原理就是加载drawable下面的图片生成Bitmap对象,然后将Bitmap对象传到natvie层生成纹理对象,这里封装了PicPreviewTexture,查看实现函数,里面都是OpenGL 使用纹理的基本操作,比如生成纹理,绑定纹理等等。这里不在重复说明。
画图过程在surfaceChanged方法里:
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
nativeController.resetSize(width, height);
Resources resources = getResources();
Bitmap bitmap = BitmapFactory.decodeResource(resources, R.drawable.face);
nativeController.showBitmap(bitmap);
}
先用回调的长宽设置ViewPort,然后showBitmap画图,调用到native层的showBitmap函数,完整函数如下:
JNIEXPORT void JNICALL Java_com_andev_androidshaderdemo_nativedemo_NativeController_showBitmap
(JNIEnv * env, jobject obj, jobject bitmap){
int ret = 0;
AndroidBitmapInfo bitmapInfo;
void *pixels = NULL;
int imgWidth = 0;
int imgHeight = 0;
if ((ret = AndroidBitmap_getInfo(env, bitmap, &bitmapInfo)) < 0) {
return;
}
LOGI("showBitmap width %d, height %d, format %d", bitmapInfo.width, bitmapInfo.height, bitmapInfo.format);
imgWidth = bitmapInfo.width;
imgHeight = bitmapInfo.height;
if (bitmapInfo.format != ANDROID_BITMAP_FORMAT_RGBA_8888) {
LOGI("Java_com_example_hellojni_HelloJni_showBitmap invalid rgb format");
return;
}
if ((ret = AndroidBitmap_lockPixels(env, bitmap, &pixels)) < 0) {
LOGI("AndroidBitmap_lockPixels() failed ! error=%d", ret);
}
controller->loadTexture((byte*)pixels, imgWidth, imgHeight);
controller->draw();
AndroidBitmap_unlockPixels(env, bitmap);
}
前面一大段其实就是通过将Bitmap图片解码到内存,然后生成OpenGL纹理,真正画图就两步:
controller->loadTexture((byte*)pixels, imgWidth, imgHeight);
controller->draw();
查看代码就知道,里面都是OpenGL指令调用。