利用EGL在android上使用C/C++写OpenGL ES程序

很多教程都是在C/C++写的OpenGL的代码,其中有很多优秀的框架,除了前面提过的Assimp库外,还有很强大的库glm,从另外一个角度来看,在学习EGL的时候,很多的资料都是C语言的代码,我在android上写OpenGL ES的代码似乎从来没见过那些代码,不使用一下总觉得缺少点什么。

事实上,Android在native层构建OpenGL环境的步骤就如同前面博客OpenGL ES EGL介绍中讲过的这样,在Java层,与EGL相关的操作我们也看不到,其实都是由GLSurfaceView封装好了。

因此这篇博客就研究一下怎么使用Native代码写OpenGL ES代码和如何利用EGL自己创建OpenGL的环境。最终包含:

1.使用Native代码+GLSurfaceView写的六边形,这里会介绍到glm库。

2.Java代码自己写一个功能类似GLSurfaceView的类,主要是在java层使用EGL自己创建OpenGL的环境,思想上参考了GLSurfaceView的源码。

3.全部使用native代码写的六边形。Java层仅用了SurfaceView,余下的功能全部在native层实现。

先贴上效果图,这三幅图分别是上面描述的三个程序执行得到的结果。
第一幅图是第一篇写OpenGL入门的时候使用的Demo,我用本博客第二部分写的MySurfaceView替换了系统自带的GLSurfaceView执行的效果。
第一幅图和三幅图也是画一个六边形,只是没有旋转,大小并且都是用AndroidStudio创建的项目。

pic


使用Native代码+GLSurfaceView

这种方式其实就是将GLSurfaceView.Renderer的几个回调函数在native层实现,算是体验C/C++写OpenGL代码的第一步吧。Render类大概就是下面的样子

    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        String vetexShaderStr = LoadShaderStr(mContext, R.raw.vshader);
        String fragmentShaderStr = LoadShaderStr(mContext, R.raw.fshader);
      // native
        nativeInit(vetexShaderStr, fragmentShaderStr);
    }
    @Override
    public void onDrawFrame(GL10 gl) {
      // native
        nativeDraw(mAngleX, mAngleY);
    }
    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
      // native
        nativeSurfaceChanged(width, height);
    }
    public static native void nativeInit(String vertexShaderCode, String fragmentShaderCode);
    private static native void nativeDraw(float angleX, float angleY);
    private static native void nativeSurfaceChanged(int width, int height);
}

在native层要做的事情其实和前面用java语言写的代码是类似的,无外乎就是写一个六边形的类,函数名也是一致的,因此写起来很简单。

使用Java代码的时候在android.opengl包中有个很方便的类Matrix,里面封装了矩阵的相关操作,设置投影、摄像机矩阵、矩阵相乘等操作都被封装好了,在C/C++中可没有这个类,不过也不是难事。Android Native Development Kit Cookbook一书的作者就封装了相关操作。

#ifndef MATRIX_H
#define MATRIX_H

#include <math.h>

#define MYPI 3.14159265358979323846

void translate_matrix(float tx, float ty, float tz, float *M);
void scale_matrix(float xs, float ys, float zs, float *M);
void rotate_matrix(float angle, float x, float y, float z, float *M);
void perspective_matrix(float fovy, float aspect, float znear, float zfar, float *M);
void multiply_matrix(float *A, float *B, float *M);

#endif



#include "matrix.h"

//all matrix are in column-major order
//the 4x4 matrix are stored in an array as shown below
//0-15 indicates the array index
//0 4   8   12
//1 5   9   13
//2 6   10  14
//3 7   11  15


void load_identity(float *M) {
    for (int i = 0; i < 16; ++i) {
        M[i] = 0.0f;
    }
    M[0] = 1.0f;
    M[5] = 1.0f;
    M[10] = 1.0f;
    M[15] = 1.0f;
}

//return translation matrix
//  1   0   0   tx
//  0   1   0   ty
//  0   0   1   tz
//  0   0   0   1
void translate_matrix(float tx, float ty, float tz, float *M) {
    load_identity(M);
    M[12] = tx;
    M[13] = ty;
    M[14] = tz;
}

//return scaling matrix
//  sx  0   0   0
//  0   sy  0   0
//  0   0   sz  0
//  0   0   0   1
void scale_matrix(float sx, float sy, float sz, float *M) {
    load_identity(M);
    M[0] *= sx;
    M[5] *= sy;
    M[10] *= sz;
}

//return rotation matrix
//R = Rx*Ry*Rz
//          1   0           0       0
//  Rx =    0   cosx        -sinx   0
//          0   sinx        cosx    0
//          0   0           0       1

//      cosy        0       siny        0
//      0           1       0           0
//  Ry =-siny       0       cosy        0
//      0           0       0           1
//
//      cosz        -sinz   0       0
//  Rz= sinz        cosz    0       0
//      0           0       1       0
//      0           0       0       1
//refer to http://lignumcad.sourceforge.net/doc/en/HTML/SOHTML/TechnicalReference.html
//for detailed info

void rotate_matrix(float angle, float x, float y, float z, float *M) {
    double radians, c, s, c1, u[3], length;
    int i, j;

    radians = (angle * MYPI) / 180.0;
    c = cos(radians);
    s = sin(radians);
    c1 = 1.0 - cos(radians);
    length = sqrt(x * x + y * y + z * z);
    u[0] = x / length;
    u[1] = y / length;
    u[2] = z / length;

    for (i = 0; i < 16; i++) {
        M[i] = 0.0;
    }
    M[15] = 1.0;

    for (i = 0; i < 3; i++) {
        M[i * 4 + (i + 1) % 3] = u[(i + 2) % 3] * s;
        M[i * 4 + (i + 2) % 3] = -u[(i + 1) % 3] * s;
    }
    for (i = 0; i < 3; i++) {
        for (j = 0; j < 3; j++) {
            M[i * 4 + j] += c1 * u[i] * u[j] + (i == j ? c : 0.0);
        }
    }
}


/* simulate gluPerspectiveMatrix
 * //return perspective projection matrix
 *  cot(fovy/2) / aspect        0               0                   0
 *  0                       cot(fovy/2)         0                   0
 *  0                           0       (znear+zfar)/(znear-zfar)   2*znear*zfa/(znear-zfar)
 *  0                           0               -1                  0
 */
void perspective_matrix(float fovy, float aspect, float znear, float zfar, float *M) {
    int i;
    double f;

    load_identity(M);

    f = 1.0/tan(fovy * 0.5);

    M[0] = f / aspect;
    M[5] = f;
    M[10] = (znear + zfar) / (znear - zfar);
    M[11] = -1.0;
    M[14] = (2.0 * znear * zfar) / (znear - zfar);
    M[15] = 0.0;
}

//Multiplies A by B and output to C, a tmp buffer is used to support in-place multiplication
void multiply_matrix(float *A, float *B, float *C) {
    int i, j, k;
    float tmpM[16];

    for (i = 0; i < 4; i++) {
        for (j = 0; j < 4; j++) {
            tmpM[j * 4 + i] = 0.0;
            for (k = 0; k < 4; k++) {
                tmpM[j * 4 + i] += A[k * 4 + i] * B[j * 4 + k];
            }
        }
    }
    for (i = 0; i < 16; i++) {
        C[i] = tmpM[i];
    }
}

不过我觉得这都是不必要的工作,很浪费时间,事实上有一个非常强大的数学库glm(OpenGL Mathematics

)主要还体现在它是为OpenGL开发人员量身定做的,和使用着色器语言一样,使用很方便。我在这个Demo中主要用了它的设置摄像机矩阵、投影矩阵。

#include "glm/mat4x4.hpp"
#include "glm/ext.hpp"
glm::mat4 projection;
glm::mat4 view;
//glm::mat4 module;
projection = glm::ortho(-1.0f, 1.0f, -(float) height / width, (float) height / width, 5.0f,
                            7.0f);
view = glm::lookAt(glm::vec3(0.0f, 0.0f, 6.0f),
                       glm::vec3(0.0f, 0.0f, 0.0f),
                       glm::vec3(0.0f, 1.0f, 0.0f));
// 矩阵相乘太方便
glm::mat4 mvpMatrix = projection * view/* * module*/;
// 装换成数组形式
float *mvp = (float *) glm::value_ptr(mvpMatrix);
mShape.draw(mvp);

代码在这里:https://github.com/qhyuan1992/OpenGL-ES/tree/master/NativeGLESView

在Java层使用EGL

其实在利用OpenGL ES进行Android手游录屏研究这篇博客中已经接触到了Java层的EGL使用了,GLSurfaceView继承自SurfaceView,我们用它来写OpenGL代码很方便,就是因为它已经封装好了创建EGL环境的部分了,如果看GLSurfaceView的代码必然可以看到前面讲EGL部分的时候创建EGL环境的相关函数调用。

不过由于GLSurfaceView代码比较多,下面是我自己写的一个类似GLSurfaceView的类,写的很简略,用其来替代系统的GLSurfaceView即可。显然这样做用来了解Java层EGL的使用是足够的。

里面有部分代码我是参考GLSurfaceView的源码的。

package com.example.opengles_circle;
import java.lang.ref.WeakReference;

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;

import android.content.Context;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class MyGLSurfaceView extends SurfaceView implements SurfaceHolder.Callback{
    public SurfaceHolder mHolder;
    public Renderer mRenderer;
    private final WeakReference<MyGLSurfaceView> mThisWeakRef = new WeakReference<MyGLSurfaceView>(this);
    Object mLock = new Object();
    private GLThread mThread;
    public MyGLSurfaceView(Context context) {
        super(context);
        mHolder = getHolder();
        mHolder.addCallback(this);
    }

    public void setRenderer(Renderer renderer) {
        mRenderer = renderer;
        mThread = new GLThread(mThisWeakRef);
        mThread.start();
    }

    public void surfaceCreated(SurfaceHolder holder) {
        mThread.surfaceCreated();
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
        mThread.surfaceDestroyed();
    }

    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
        mThread.surfaceChanged(w, h);
    }

    public void stopRender() {
        // 设置停止标示,notify
    }

    public interface Renderer {
        void onSurfaceCreated();

        void onSurfaceChanged(int width, int height);

        void onDrawFrame();
    }

}

class GLThread extends Thread {
    private Object lock = new Object();
    private EGLHelper mEglHelper;
    private boolean mHasSurface = false;
    public int mWidth;
    public int mHeight;
    private WeakReference<MyGLSurfaceView> mGLSurfaceViewWeakRef;

     GLThread(WeakReference<MyGLSurfaceView> glSurfaceViewWeakRef) {
         super();
         mWidth = 0;
         mHeight = 0;
         mGLSurfaceViewWeakRef = glSurfaceViewWeakRef;
     }

    @Override
    public void run() {
        try {
            doThread();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void doThread() throws InterruptedException {
        mEglHelper = new EGLHelper(mGLSurfaceViewWeakRef);
        MyGLSurfaceView view = mGLSurfaceViewWeakRef.get();
        // 确保有了Surface
        synchronized (lock) {
            if (!mHasSurface) {
                lock.wait();
            }
        }
        mEglHelper.start();
        /*----实际上是在一个循环里面针对不同的事件(onSurfaceCreated/onSurfaceChanged/onDrawFrame/terminal)执行不同的回调----*/
        /*-----所有情况都执行完后,wait,当某个事件发生后标志置位,然后调用notify----*/
        view.mRenderer.onSurfaceCreated();
        // 也要确保在surfaceChanged之后执行
        view.mRenderer.onSurfaceChanged(mWidth, mHeight);
        view.mRenderer.onDrawFrame();
        mEglHelper.swap();
    }

    public void surfaceCreated() {
    }

    public void surfaceChanged(int w, int h) {
        mWidth = w;
        mHeight = h;
        synchronized (lock) {
            mHasSurface = true;
            lock.notifyAll();
        }
    }

    public void surfaceDestroyed() {
        synchronized (lock) {
            mHasSurface = false;
            lock.notifyAll();           
        }
    }
}

class EGLHelper{
    public WeakReference<MyGLSurfaceView> mGLSurfaceViewWeakRef;
    private EGL10 mEgl;
    private EGLDisplay mEglDisplay;
    private EGLConfig mEglConfig;
    private EGLContext mEglContext;
    private EGLSurface mEglSurface;

    public EGLHelper(WeakReference<MyGLSurfaceView> glSurfaceViewWeakRef) {
        mGLSurfaceViewWeakRef = glSurfaceViewWeakRef;
    }

  // start函数中就是创建EGL环境的步骤
    public void start(){
        int[] num_config = new int[1];
        EGLConfig[] configs = new EGLConfig[1];
        int[] configSpec = { EGL10.EGL_RED_SIZE, 8, 
                EGL10.EGL_GREEN_SIZE, 8,
                EGL10.EGL_BLUE_SIZE, 8,
                EGL10.EGL_SURFACE_TYPE, EGL10.EGL_WINDOW_BIT, EGL10.EGL_NONE };

        this.mEgl = (EGL10) EGLContext.getEGL();

        mEglDisplay = this.mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
        this.mEgl.eglInitialize(mEglDisplay, null);

        this.mEgl.eglChooseConfig(mEglDisplay, configSpec, configs, 1, num_config);

        mEglConfig = configs[0];
        mEglSurface = this.mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig,
                mGLSurfaceViewWeakRef.get().mHolder, null);

        int[] attrs = {0x3098, 2, EGL10.EGL_NONE}; // EGL_CONTEXT_CLIENT_VERSION
        mEglContext = this.mEgl.eglCreateContext(mEglDisplay, mEglConfig,
                EGL10.EGL_NO_CONTEXT, attrs);

        this.mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext);
    }

    public void swap() {
        mEgl.eglSwapBuffers(mEglDisplay, mEglSurface);
    }

    public void destroySurface() {
        if (mEglSurface != null) {
            mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
            mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
            mEglSurface = null;
        }
        if (mEglContext != null) {
            mEgl.eglDestroyContext(mEglDisplay, mEglContext);
            mEglContext = null;
        }
        if (mEglDisplay != null) {
            mEgl.eglTerminate(mEglDisplay);
            mEglDisplay = null;
        }
    }
}

可以看到当调用setRender时就开启了一个子线程GLThread,不过这个线程没有立即执行而是wait直到surface创建成功。

EGLHelper就是一个工具类,里面的start函数就包含了创建EGL环境的步骤,等待EGL上下文创建好了,在执行 view.mRenderer.onDrawFrame();回调中其实就是我们要实现的部分,在里面进行OpenGL绘图,然后在执行EGLHelper的swap函数,将后台缓冲中的内容显示到native_window也就是屏幕上去。

一般来说在这个GLThread线程中是一个无限循环,根据收到的不同状态值,执行对应的操作,每执行一轮就会调用wait直到其他的状态被设置后notify唤醒,在循环执行。

在这里注意一下eglCreateWindowSurface函数中的native_window参数。它是一个Object对象,它的实现在com.google.android.gles_jni.EGLImpl.java(EGLImpl.java),先记住native_window可以传入的参数是SurfaceView、SurfaceHolder或者Surface,最终都转换成了Surface,Surface在Java层代表这一个窗口。

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) {

          // .......
}

在native层使用EGL

在native层使用EGL创建EGL环境并且使用C/C++写OpenGL代码,思路就是结合前面两个Demo,在native层创建EGL环境和在Java层是类似的思路,只是有些API可能不一样,并且还用到了很多NDK的知识,

代码在这里https://github.com/qhyuan1992/OpenGL-ES/tree/master/NativeGLESViewWithEGL

核心部分的代码


void Renderer::requestInitEGL(ANativeWindow * pWindow) {
    LOGI(1, "-------requestInitEGL");
    pthread_mutex_lock(&mMutex);
    mWindow = pWindow;
  // mEnumRenderEvent表示事件
    mEnumRenderEvent = RE_SURFACE_CHANGED;
    LOGI(1, "-------mEnumRenderEvent=%d", mEnumRenderEvent);
    pthread_mutex_unlock(&mMutex);
  // 唤醒处于wait状态的线程,继续函数onRenderThreadRun的执行,onRenderThreadRun是线程的入口函数
    pthread_cond_signal(&mCondVar);
}

// 每次需要绘制的时候调用,实际上会调用到
void Renderer::requestRenderFrame() {
    pthread_mutex_lock(&mMutex);
    mEnumRenderEvent = RE_DRAW_FRAME;
    pthread_mutex_unlock(&mMutex);
    pthread_cond_signal(&mCondVar);
}

void Renderer::requestDestroy() {
    pthread_mutex_lock(&mMutex);
    mEnumRenderEvent = RE_EXIT;
    pthread_mutex_unlock(&mMutex);
    pthread_cond_signal(&mCondVar);
}

void Renderer::onRenderThreadRun() {
    mISRenderering = true;
    while(mISRenderering) {
        pthread_mutex_lock(&mMutex);
        // 每完成一个事件就wait在这里直到有其他事件唤醒
        pthread_cond_wait(&mCondVar, &mMutex);

        LOGI(1, "-------this mEnumRenderEvent is %d", mEnumRenderEvent);
        switch (mEnumRenderEvent) {
            case RE_SURFACE_CHANGED:
                LOGI(1, "-------case RE_SURFACE_CHANGED");
                mEnumRenderEvent = RE_NONE;
                pthread_mutex_unlock(&mMutex);
                initEGL();
                nativeSurfaceCreated();
                nativeSurfaceChanged(mWidth, mHeight);
                break;
            case RE_DRAW_FRAME:
                mEnumRenderEvent = RE_NONE;
                pthread_mutex_unlock(&mMutex);
                // draw
                // 这个函数留给真实的GLES绘制操作。
                nativeDraw();
                eglSwapBuffers(mDisplay, mSurface);
                break;
            case RE_EXIT:
                mEnumRenderEvent = RE_NONE;
                pthread_mutex_unlock(&mMutex);
                terminateDisplay();
                mISRenderering = false;
                break;
            default:
                mEnumRenderEvent = RE_NONE;
                pthread_mutex_unlock(&mMutex);
        }
    }
}

下面我也把native层创建EGL环境的部分列出来,显然和Java层是类似的。

void Renderer::initEGL() {
    const EGLint attribs[] = {
            EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
            EGL_BLUE_SIZE, 8,
            EGL_GREEN_SIZE, 8,
            EGL_RED_SIZE, 8,
            EGL_NONE
    };
    EGLint width, height, format;
    EGLint numConfigs;
    EGLConfig config;
    EGLSurface surface;
    EGLContext context;

    EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);

    eglInitialize(display, 0, 0);

    eglChooseConfig(display, attribs, &config, 1, &numConfigs);

    surface = eglCreateWindowSurface(display, config, mWindow, NULL);
    EGLint attrs[]= {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};
    context = eglCreateContext(display, config, NULL, attrs);

    if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE) {
        LOGI(1, "------EGL-FALSE");
        return ;
    }

    eglQuerySurface(display, surface, EGL_WIDTH, &width);
    eglQuerySurface(display, surface, EGL_HEIGHT, &height);

    mDisplay = display;
    mSurface = surface;
    mContext = context;
    mWidth = width;
    mHeight = height;
    LOGI(1, "width:%d, height:%d", mWidth, mHeight);

}

在Java层继承自一个SurfaceView

public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback {
    public MySurfaceView(Context context) {
        super(context);
        getHolder().addCallback(this);
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        nativeRequestRender();
        return super.onTouchEvent(event);
    }
    @Override
    public void surfaceCreated(SurfaceHolder surfaceHolder) {
        setRender();
    }
    @Override
    public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
        nativeSurfaceChanged(surfaceHolder.getSurface());
    }
    @Override
    public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
        nativeSurfaceDestroyed();
    }
    private void setRender() {
        nativeStartRender();
    }
    private static native void nativeSurfaceChanged(Surface surface);
    private static native void nativeSurfaceDestroyed();
    private static native void nativeStartRender();
    private static native void nativeRequestRender();
    static {
        System.loadLibrary("NativeWithEGL");
    }
}

转而在native层执行SurfaceHolder.Callback的回调,注意和前面的GLSurfaceView.Renderer的回调。

接下来在合适的时刻调用Renderer的request***函数,发出某种消息,比如执行requestRenderFrame函数,也就是发出了要绘制一帧的消息,那么在绘制线程中,就去执行OpenGL ES绘制操作代码。

ANativeWindow * mWindow;
Renderer * mRenderer;
extern "C" {
JNIEXPORT void JNICALL
Java_com_example_weiersyuan_nativeglesviewwithegl_MySurfaceView_nativeStartRender(JNIEnv *env, jclass type) {
    mRenderer = new Renderer();
    mRenderer->start();
}
JNIEXPORT void JNICALL
Java_com_example_weiersyuan_nativeglesviewwithegl_MySurfaceView_nativeSurfaceChanged(JNIEnv *env, jclass type, jobject surface) {
    mWindow = ANativeWindow_fromSurface(env, surface);
    // surfacechange时 发送SurfaceChanged消息,此时创建egl环境的消息
    mRenderer->requestInitEGL(mWindow);
}

JNIEXPORT void JNICALL
Java_com_example_weiersyuan_nativeglesviewwithegl_MySurfaceView_nativeSurfaceDestroyed(JNIEnv *env, jclass type) {
    mRenderer->requestDestroy();
    ANativeWindow_release(mWindow);
    delete mRenderer;
}
JNIEXPORT void JNICALL
Java_com_example_weiersyuan_nativeglesviewwithegl_MySurfaceView_nativeRequestRender(JNIEnv *env, jclass type){
    mRenderer->requestRenderFrame();
}
}

在Native层ANativeWindow对象代表一个窗口,看起来好像和Java层的Surface是对应着的,native层的ANativeWindow的指针是Java层的Surface对象的一个成员变量,这也是持久保存native层数据的常用方式,在NDK中有很多类似于ANativeWindow_fromSurface的函数,将Java层的独享转换为native层的对象,都是类似的方式。

Java代码自己写一个功能类似GLSurfaceView的类

posted @ 2016-11-30 23:37  浩荡乾坤  阅读(1394)  评论(0编辑  收藏  举报