OpenGL + C++ + Java

来源:互联网 发布:哪个旅行软件好用 编辑:程序博客网 时间:2024/05/20 20:04

OpenGL + C++ + Java

这个组合有一点奇怪,因为要实现在opengl中播放视频,所以不得不有这样奇怪的组合。上层的MediaPlayer封装的基本上是针对android UI的框架,如果想在opengl中显示,估计难度很大。另外,很多开源的opengl的游戏基本上都是C++编写,所以这个体系的作用还是很大的,之所以需要java,因为上层的很多例如触摸、重力感应,这样的东西,android是以java实现的,底层的根本看不到,这样的组合起来,充分的利用每一个层次的优点,最大效率提高用户体验。

 

我们首先会简单的测试一下opengl立方体例子,然后我们实现在native层上面的绘制,如何使用jni来传递。最后我们简单的说一下使用自带的android封装的opengl接口api有什么样的限制。

 

Google提供的apijavaapi,对于java开发者来说,这是好消息,对于C++开发者来说,就会有很大的麻烦。现在主流的游戏引擎、游戏算法基本上都是C/C++实现。

 

我们纯粹利用C/C++来写OpenGL的应用程序的时候,基本流程如下,初始化EGL,选择配置,然后选择Display,然后创建Surface,之后就是DrawFramSwap了。Google很聪明的给这个流程进行了一次封装,封装成这样的几个类,大致的代码如下:

首先是EGL的相关操作:

public class EglHelper {

        public EglHelper() {

 

        }

        /**

         * Initialize EGL for a given configuration spec.

         * @param configSpec

         */

        public void start(int[] configSpec){

            /*

             * Get an EGL instance

             */

            mEgl = (EGL10) EGLContext.getEGL();

 

            /*

             * Get to the default display.

             */

            mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);

            /*

             * We can now initialize EGL forthat display

             */

            int[] version = new int[2];

            mEgl.eglInitialize(mEglDisplay, version);

 

            EGLConfig[] configs = new EGLConfig[1];

            int[] num_config = new int[1];

            mEgl.eglChooseConfig(mEglDisplay, configSpec, configs, 1,

                    num_config);

            mEglConfig = configs[0];

            /*

            * Create an OpenGL ES context. Thismust be done only once, an

            * OpenGL context is a somewhatheavy object.

            */

            mEglContext = mEgl.eglCreateContext(mEglDisplay, mEglConfig,

                    EGL10.EGL_NO_CONTEXT, null);

 

            mEglSurface = null;

        }

        /*

         * Create and return an OpenGL surface

         */

        public GL createSurface(SurfaceHolderholder) {

            /*

             * The window size has changed, so we need to create a new

             * surface.

             */

            if (mEglSurface != null) {

 

                /*

                 * Unbind and destroy the oldEGL surface, if

                 * there is one.

                 */

                mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE,

                        EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);

                mEgl.eglDestroySurface(mEglDisplay, mEglSurface);

            }

 

            /*

             * Create an EGL surface we canrender into.

             */

           mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay,

                    mEglConfig, holder, null);

 

            /*

             * Before we can issue GL commands,we need to make sure

             * the context is current and boundto a surface.

             */

            mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,

                    mEglContext);

 

 

            GL gl = mEglContext.getGL();

           

            return gl;

        }

        /**

         * Display the current render surface.

         * @return false if the context has been lost.

         */

        public boolean swap() {

            mEgl.eglSwapBuffers(mEglDisplay, mEglSurface);

            /*

             * Always check forEGL_CONTEXT_LOST, which means the context

             * and all associated data werelost (For instance because

             * the device went to sleep). Weneed to sleep until we

             * get a new surface.

             */

            return mEgl.eglGetError() != EGL11.EGL_CONTEXT_LOST;

        }

 

        public void finish() {

            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;

            }

        }

 

        public int getError () {

        return mEgl.eglGetError();

        }

       

//        public void setGLWrapper(GLWrapperglWrapper) {

//            mGLWrapper = glWrapper;

//        }

//        private GLWrapper mGLWrapper;

       

        EGL10 mEgl;

        EGLDisplay mEglDisplay;

        EGLSurface mEglSurface;

        EGLConfig mEglConfig;

        EGLContext mEglContext;

    }

上面的代码我不多说。有兴趣的自己看,无非是对EGL进行一次简单的封装。

然后实现一个SurfaceView专门针对opengl的。这里有一个GLSurfaceView

/*

 *Copyright (C) 2008 Google Inc.

 *

 *Licensed under the Apache License, Version 2.0 (the "License");

 *you may not use this file except in compliance with the License.

 *You may obtain a copy of the License at

 *

 *     http://www.apache.org/licenses/LICENSE-2.0

 *

 *Unless required by applicable law or agreed to in writing, software

 *distributed under the License is distributed on an "AS IS" BASIS,

 *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

 *See the License for the specific language governing permissions and

 *limitations under the License.

 */

 

package opengl.scenes;

 

import opengl.jni.Natives;

import android.content.Context;

import android.graphics.PixelFormat;

import android.util.AttributeSet;

import android.view.SurfaceHolder;

import android.view.SurfaceView;

 

 

 

/**

 * Animplementation of SurfaceView that uses the dedicated surface for

 *displaying an OpenGL animation.  Thisallows the animation to run in a

 *separate thread, without requiring that it be driven by the update mechanism

 * ofthe view hierarchy.

 *

 *The application-specific rendering code is delegated to a GLView.Renderer

 *instance.

 */

public class GLSurfaceView extendsSurfaceView implements SurfaceHolder.Callback {

   public GLSurfaceView(Context context) {

       super(context);

       init();

    }

 

   public GLSurfaceView(Context context, AttributeSet attrs) {

       super(context, attrs);

        init();

    }

 

   private void init() {

       // Install a SurfaceHolder.Callback so we get notified when the

       // underlying surface is created and destroyed

       mHolder = getHolder();

       mHolder.addCallback(this);

       mHolder.setType(SurfaceHolder.SURFACE_TYPE_GPU);

 

              //得到我们的holder 并且添加callback

    }

 

   public SurfaceHolder getSurfaceHolder() {

       return mHolder;

    }

 

//这个是我们添加的一个借口,只要调用了这个就会render,创建一个线程

   public void setRenderer(Renderer renderer) {

       mGLThread = new GLThread(renderer, mHolder);

       mGLThread.start();

       

       System.out.println("GLSurfaceView::setRenderer setting nativeslistener");

       Natives.setListener(mGLThread);

    }

 

   public void surfaceCreated(SurfaceHolder holder) {

       mGLThread.surfaceCreated();

    }

 

   public void surfaceDestroyed(SurfaceHolder holder) {

       // Surface will be destroyed when we return

       mGLThread.surfaceDestroyed();

    }

 

   public void surfaceChanged(SurfaceHolder holder, int format, int w, inth) {

       // Surface size or format has changed. This should not happen in this

       // example.

       mGLThread.onWindowResize(w, h);

    }

 

   /**

    * Inform the view that the activity is paused.

    */

   public void onPause() {

       mGLThread.onPause();

    }

 

   /**

    * Inform the view that the activity is resumed.

    */

   public void onResume() {

       mGLThread.onResume();

    }

 

   /**

    * Inform the view that the window focus has changed.

    */

   @Override public void onWindowFocusChanged(boolean hasFocus) {

       super.onWindowFocusChanged(hasFocus);

       mGLThread.onWindowFocusChanged(hasFocus);

    }

 

   /**

    * Queue an "event" to be run on the GL rendering thread.

    * @param r the runnable to be run on the GL rendering thread.

    */

   public void queueEvent(Runnable r) {

       mGLThread.queueEvent(r);

    }

 

   @Override

   protected void onDetachedFromWindow() {

       super.onDetachedFromWindow();

       mGLThread.requestExitAndWait();

    }

 

 

   private SurfaceHolder mHolder;

   private GLThread mGLThread;

 

}

 

下面就是GLThread这个整个这几个内,这里略。通过上面的代码我们可以知道,实际上整个的Opengl上层google给的凤凰其实还是按照我们C/C++的编写习惯来的,实际上还是通过jni调用下面的EGL接口。这样java可以使用,但是就会有一个问题,频繁的jni调用严重的影响效率。特别是连我们的绘制都是jni的调用,这样效率就会大大的折扣,所以这里我们开始把上面的过程的大部分转移到native层。分为如下几个步骤:

1、 初始化:OpenGLES是一个单线程的东西。需要初始化一个GLContext,同时只有可能一个线程来访问这个东西。在EGL中,这一步分为了如下几步:

a、 得到一个EGLContextEGLContex.getEgl();

b、 得到默认的display

c、 初始化display 

d、 设置像素格式 深度大小等等

2、 主循环,一帧又一帧的绘制。

3、 真正的绘制 swap

4、 清空

上面所有的东西都可以通过纯粹的java来实现,这里感谢一下google。但是某些步骤同样也可以用C/C++来实现的。哪些可以通过java

1、 初始化工作。获得EGL的实例初始化显示器和一些颜色和深度的设置。

2、 Javamain loop,这个loop不做什么事情,直接调用native的方法进行绘制。

3、 Swap buffer

一个C/C++开源的opengl代码最大的部分就是他的显示,至于如何swap如何初始化都是这些代码不关心的,所以可以用java来实现。

我们用这种思想在此实现我们的OpenGL程序:

Main Activity 主要的activity

 

public class NativeGLActivity extends Activity

{

    private GLSurfaceView mGLSurfaceView;

    //首先载入我们的so

    {

       final String LIB_PATH = "/data/libgltest_jni.so";

      

       System.out.println("Loading JNIlib using abs path:" + LIB_PATH);

       System.load(LIB_PATH);

    }

   

    /** Called when the activity is first created. */

    @Override

    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        //setContentView(R.layout.main);

       

        mGLSurfaceView = new GLSurfaceView(this);

       

        try {

        mGLSurfaceView.setRenderer(new CubeRenderer(true, true));

        setContentView(mGLSurfaceView);

       

       } catch (Exception e) {

           e.printStackTrace();

       }

    }

   

    @Override

    protected void onResume() {

        // Ideally a game should implement onResume() and onPause()

        // to take appropriate action when the activity looses focus

        super.onResume();

        mGLSurfaceView.onResume();

    }

 

    @Override

    protected void onPause() {

        // Ideally a game should implement onResume() and onPause()

        // to take appropriate action when the activity looses focus

        super.onPause();

        mGLSurfaceView.onPause();

    }

   

}

 

因为我们的Render有这样的一个功能,自动的选择是javaDrawFrame或者C/C++draw Frame

public class CubeRenderer implements Renderer

{

    private boolean mNativeDraw = false;

   

    public CubeRenderer(booleanuseTranslucentBackground, boolean nativeDraw) {

        mTranslucentBackground =useTranslucentBackground;

        mNativeDraw = nativeDraw;

        mCube = new Cube();

    }

 

    public void drawFrame(GL10 gl) {

    if (mNativeDraw)

        doNativeDraw();

    else

        doJavaDraw(gl);

}

这里我们直接看看我们的doNativeDraw方法。

    public void doNativeDraw() {

            Natives.NativeRender();

    }

 

这样我们看看我们下面的主要的C++代码:

JNIEXPORT jintJNICALL Java_opengl_jni_Natives_NativeRender

  (JNIEnv *, jclass);

 

/*

 * Class:    opengl_jni_Natives

 * Method:   RenderTest

 * Signature: ()V

 */

 

JNIEXPORT jintJNICALL Java_opengl_jni_Natives_NativeRender

  (JNIEnv * env, jclass cls)

{

 

       (*env)->GetJavaVM(env, &g_VM);

       static int initialized = 0;

 

       if ( ! initialized ) {

              jni_printf("Native:RenderTestinitscene");

              init_scene();

 

              initialized = 1;

 

       }

 

       drawFrame();

       return 1;

}

 

static voiddrawFrame()

{

        /*

         * By default, OpenGL enables featuresthat improve quality

         * but reduce performance. One mightwant to tweak that

         * especially on software renderer.

         */

        glDisable(GL_DITHER);

 

        glTexEnvx(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);

 

        /*

         * Usually, the first thing one mightwant to do is to clear

         * the screen. The most efficient wayof doing this is to use

         * glClear().

         */

 

        glClear(GL_COLOR_BUFFER_BIT |GL_DEPTH_BUFFER_BIT);

 

        /*

         * Now we're ready to draw some 3Dobjects

         */

       glMatrixMode(GL_MODELVIEW);

       glLoadIdentity();

 

       glTranslatef(0, 0, -3.0f);

       glRotatef(mAngle,        0, 0, 1.0f);

       glRotatef(mAngle*0.25f, 1, 0, 0);

 

 

       glEnableClientState(GL_VERTEX_ARRAY);

       glEnableClientState(GL_COLOR_ARRAY);

 

       Cube_draw();

 

       glRotatef(mAngle*2.0f, 0, 1, 1);

       glTranslatef(0.5f, 0.5f,0.5f);

 

       Cube_draw();

 

       mAngle += 1.2f;

}

基本上把原来java使用的东西全部搬到了我们下面的。

显示效果基本一样,全部都是58fps左右。由于代码比较简单,好像c++的速度优势还木有显示出来。

小结一下:这里有一些注意的问题,当今智能设备越来越强大,GPU的能力也越来越大。毫无疑问,用OpenGL来写嵌入式设备上的应用程序是大势所趋。像quake这样的游戏,已经被移植到很多智能平台上面了这个游戏使用的是直接绘制一些特殊的几何图形,例如下面是绘制一个多边形。

glBegin(GL_POLYGON);

glTexcoord2();

…………

glEnd();

这种方式很显然只是桌面应用程序的,在android上面是不适用的,因为OES是没有多边形绘制的接口。移植这样的代码很困难。这是我们要考虑的第一点。

 

然后就是浮点数的问题。很多嵌入式设备都没有FPU的概念,浮点运算很弱。OpenglES使用16位来表示整数部分,剩下的16位表示小数部分。所以浮点数可以通过另外一种方式:

glTranslatex10<<16,0,0,2<<16; // glTranslatex(10.0f,0.0f,0.0f,2.0f)

另外一点就是这里木有GLU工具函数,可以自己去实现。