Mega Code Archive

 
Categories / Android / 2D Graphics
 

An OpenGL ES renderer based on the GLSurfaceView rendering framework

/*  * Copyright (C) 2008 The Android Open Source Project  *  * 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.  */ import java.io.IOException; import java.io.InputStream; import java.util.concurrent.Semaphore; import javax.microedition.khronos.egl.EGL10; import javax.microedition.khronos.egl.EGL11; 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 javax.microedition.khronos.opengles.GL; import javax.microedition.khronos.opengles.GL10; import javax.microedition.khronos.opengles.GL11; import javax.microedition.khronos.opengles.GL11Ext; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.opengl.GLUtils; import android.util.AttributeSet; import android.util.Log; import android.view.SurfaceHolder; import android.view.SurfaceView; /**  * An OpenGL ES renderer based on the GLSurfaceView rendering framework. This  * class is responsible for drawing a list of renderables to the screen every  * frame. It also manages loading of textures and (when VBOs are used) the  * allocation of vertex buffer objects.  */ public class SimpleGLRenderer implements GLSurfaceView.Renderer {   // Specifies the format our textures should be converted to upon load.   private static BitmapFactory.Options sBitmapOptions = new BitmapFactory.Options();   // An array of things to draw every frame.   private GLSprite[] mSprites;   // Pre-allocated arrays to use at runtime so that allocation during the   // test can be avoided.   private int[] mTextureNameWorkspace;   private int[] mCropWorkspace;   // A reference to the application context.   private Context mContext;   // Determines the use of vertex arrays.   // Determines the use of vertex buffer objects.   public SimpleGLRenderer(Context context) {     // Pre-allocate and store these objects so we can use them at runtime     // without allocating memory mid-frame.     mTextureNameWorkspace = new int[1];     mCropWorkspace = new int[4];     // Set our bitmaps to 16-bit, 565 format.     sBitmapOptions.inPreferredConfig = Bitmap.Config.RGB_565;     mContext = context;   }   public int[] getConfigSpec() {     // We don't need a depth buffer, and don't care about our     // color depth.     int[] configSpec = { EGL10.EGL_DEPTH_SIZE, 0, EGL10.EGL_NONE };     return configSpec;   }   public void setSprites(GLSprite[] sprites) {     mSprites = sprites;   }   /**    * Changes the vertex mode used for drawing.    *     * @param useVerts    *            Specifies whether to use a vertex array. If false, the    *            DrawTexture extension is used.    * @param useHardwareBuffers    *            Specifies whether to store vertex arrays in main memory or on    *            the graphics card. Ignored if useVerts is false.    */   /** Draws the sprites. */   public void drawFrame(GL10 gl) {     if (mSprites != null) {       gl.glMatrixMode(GL10.GL_MODELVIEW);       for (int x = 0; x < mSprites.length; x++) {         mSprites[x].draw(gl);       }     }   }   /* Called when the size of the window changes. */   public void sizeChanged(GL10 gl, int width, int height) {     gl.glViewport(0, 0, width, height);     /*      * Set our projection matrix. This doesn't have to be done each time we      * draw, but usually a new projection needs to be set when the viewport      * is resized.      */     gl.glMatrixMode(GL10.GL_PROJECTION);     gl.glLoadIdentity();     gl.glOrthof(0.0f, width, 0.0f, height, 0.0f, 1.0f);     gl.glShadeModel(GL10.GL_FLAT);     gl.glEnable(GL10.GL_BLEND);     gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);     gl.glColor4x(0x10000, 0x10000, 0x10000, 0x10000);     gl.glEnable(GL10.GL_TEXTURE_2D);   }   /**    * Called whenever the surface is created. This happens at startup, and may    * be called again at runtime if the device context is lost (the screen goes    * to sleep, etc). This function must fill the contents of vram with texture    * data and (when using VBOs) hardware vertex arrays.    */   public void surfaceCreated(GL10 gl) {     /*      * Some one-time OpenGL initialization can be made here probably based      * on features of this particular context      */     gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST);     gl.glClearColor(0.5f, 0.5f, 0.5f, 1);     gl.glShadeModel(GL10.GL_FLAT);     gl.glDisable(GL10.GL_DEPTH_TEST);     gl.glEnable(GL10.GL_TEXTURE_2D);     /*      * By default, OpenGL enables features that improve quality but reduce      * performance. One might want to tweak that especially on software      * renderer.      */     gl.glDisable(GL10.GL_DITHER);     gl.glDisable(GL10.GL_LIGHTING);     gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);     if (mSprites != null) {       // If we are using hardware buffers and the screen lost context       // then the buffer indexes that we recorded previously are now       // invalid. Forget them here and recreate them below.       // Load our texture and set its texture name on all sprites.       // To keep this sample simple we will assume that sprites that share       // the same texture are grouped together in our sprite list. A real       // app would probably have another level of texture management,       // like a texture hash.       int lastLoadedResource = -1;       int lastTextureId = -1;       for (int x = 0; x < mSprites.length; x++) {         int resource = mSprites[x].getResourceId();         if (resource != lastLoadedResource) {           lastTextureId = loadBitmap(mContext, gl, resource);           lastLoadedResource = resource;         }         mSprites[x].setTextureName(lastTextureId);         // mSprites[x].getGrid().generateHardwareBuffers(gl);       }     }   }   /**    * Called when the rendering thread shuts down. This is a good place to    * release OpenGL ES resources.    *     * @param gl    */   public void shutdown(GL10 gl) {     if (mSprites != null) {       int lastFreedResource = -1;       int[] textureToDelete = new int[1];       for (int x = 0; x < mSprites.length; x++) {         int resource = mSprites[x].getResourceId();         if (resource != lastFreedResource) {           textureToDelete[0] = mSprites[x].getTextureName();           gl.glDeleteTextures(1, textureToDelete, 0);           mSprites[x].setTextureName(0);         }       }     }   }   /**    * Loads a bitmap into OpenGL and sets up the common parameters for 2D    * texture maps.    */   protected int loadBitmap(Context context, GL10 gl, int resourceId) {     int textureName = -1;     if (context != null && gl != null) {       gl.glGenTextures(1, mTextureNameWorkspace, 0);       textureName = mTextureNameWorkspace[0];       gl.glBindTexture(GL10.GL_TEXTURE_2D, textureName);       gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER,           GL10.GL_NEAREST);       gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER,           GL10.GL_LINEAR);       gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S,           GL10.GL_CLAMP_TO_EDGE);       gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T,           GL10.GL_CLAMP_TO_EDGE);       gl.glTexEnvf(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE,           GL10.GL_REPLACE);       InputStream is = context.getResources().openRawResource(resourceId);       Bitmap bitmap;       try {         bitmap = BitmapFactory.decodeStream(is, null, sBitmapOptions);       } finally {         try {           is.close();         } catch (IOException e) {           // Ignore.         }       }       GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);       mCropWorkspace[0] = 0;       mCropWorkspace[1] = bitmap.getHeight();       mCropWorkspace[2] = bitmap.getWidth();       mCropWorkspace[3] = -bitmap.getHeight();       bitmap.recycle();       ((GL11) gl).glTexParameteriv(GL10.GL_TEXTURE_2D,           GL11Ext.GL_TEXTURE_CROP_RECT_OES, mCropWorkspace, 0);       int error = gl.glGetError();       if (error != GL10.GL_NO_ERROR) {         Log.e("SpriteMethodTest", "Texture Load GLError: " + error);       }     }     return textureName;   } } /**  * Base class defining the core set of information necessary to render (and move  * an object on the screen. This is an abstract type and must be derived to add  * methods to actually draw (see CanvasSprite and GLSprite).  */ abstract class Renderable {   // Position.   public float x;   public float y;   public float z;   // Velocity.   public float velocityX;   public float velocityY;   public float velocityZ;   // Size.   public float width;   public float height; } /**  * This is the OpenGL ES version of a sprite. It is more complicated than the  * CanvasSprite class because it can be used in more than one way. This class  * can draw using a grid of verts, a grid of verts stored in VBO objects, or  * using the DrawTexture extension.  */ class GLSprite extends Renderable {   // The OpenGL ES texture handle to draw.   private int mTextureName;   // The id of the original resource that mTextureName is based on.   private int mResourceId;   // If drawing with verts or VBO verts, the grid object defining those verts.   public GLSprite(int resourceId) {     super();     mResourceId = resourceId;   }   public void setTextureName(int name) {     mTextureName = name;   }   public int getTextureName() {     return mTextureName;   }   public void setResourceId(int id) {     mResourceId = id;   }   public int getResourceId() {     return mResourceId;   }   public void draw(GL10 gl) {     gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureName);     // Draw using the DrawTexture extension.     ((GL11Ext) gl).glDrawTexfOES(x, y, z, width, height);   } } /**  * An implementation of SurfaceView that uses the dedicated surface for  * displaying an OpenGL animation. This allows the animation to run in a  * separate thread, without requiring that it be driven by the update mechanism  * of the view hierarchy.  *   * The application-specific rendering code is delegated to a GLView.Renderer  * instance.  */ class GLSurfaceView extends SurfaceView 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);   }   public SurfaceHolder getSurfaceHolder() {     return mHolder;   }   public void setGLWrapper(GLWrapper glWrapper) {     mGLWrapper = glWrapper;   }   public void setRenderer(Renderer renderer) {     mGLThread = new GLThread(renderer);     mGLThread.start();   }   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, int h) {     // 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);   }   /**    * Set an "event" to be run on the GL rendering thread.    *     * @param r    *            the runnable to be run on the GL rendering thread.    */   public void setEvent(Runnable r) {     mGLThread.setEvent(r);   }   @Override   protected void onDetachedFromWindow() {     super.onDetachedFromWindow();     mGLThread.requestExitAndWait();   }   // ----------------------------------------------------------------------   public interface GLWrapper {     GL wrap(GL gl);   }   // ----------------------------------------------------------------------   /**    * A generic renderer interface.    */   public interface Renderer {     /**      * @return the EGL configuration specification desired by the renderer.      */     int[] getConfigSpec();     /**      * Surface created. Called when the surface is created. Called when the      * application starts, and whenever the GPU is reinitialized. This will      * typically happen when the device awakes after going to sleep. Set      * your textures here.      */     void surfaceCreated(GL10 gl);     /**      * Called when the rendering thread is about to shut down. This is a      * good place to release OpenGL ES resources (textures, buffers, etc).      *       * @param gl      */     void shutdown(GL10 gl);     /**      * Surface changed size. Called after the surface is created and      * whenever the OpenGL ES surface size changes. Set your viewport here.      *       * @param gl      * @param width      * @param height      */     void sizeChanged(GL10 gl, int width, int height);     /**      * Draw the current frame.      *       * @param gl      */     void drawFrame(GL10 gl);   }   /**    * An EGL helper class.    */   private 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 for that 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. This must be done only once, an        * OpenGL context is a somewhat heavy object.        */       mEglContext = mEgl.eglCreateContext(mEglDisplay, mEglConfig,           EGL10.EGL_NO_CONTEXT, null);       mEglSurface = null;     }     /*      * Create and return an OpenGL surface      */     public GL createSurface(SurfaceHolder holder) {       /*        * The window size has changed, so we need to create a new surface.        */       if (mEglSurface != null) {         /*          * Unbind and destroy the old EGL 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 can render into.        */       mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig,           holder, null);       /*        * Before we can issue GL commands, we need to make sure the context        * is current and bound to a surface.        */       mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,           mEglContext);       GL gl = mEglContext.getGL();       if (mGLWrapper != null) {         gl = mGLWrapper.wrap(gl);       }       return gl;     }     /**      * Display the current render surface.      *       * @return false if the context has been lost.      */     public boolean swap() {       mEgl.eglSwapBuffers(mEglDisplay, mEglSurface);       /*        * Always check for EGL_CONTEXT_LOST, which means the context and        * all associated data were lost (For instance because the device        * went to sleep). We need 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;       }     }     EGL10 mEgl;     EGLDisplay mEglDisplay;     EGLSurface mEglSurface;     EGLConfig mEglConfig;     EGLContext mEglContext;   }   /**    * A generic GL Thread. Takes care of initializing EGL and GL. Delegates to    * a Renderer instance to do the actual drawing.    *     */   class GLThread extends Thread {     GLThread(Renderer renderer) {       super();       mDone = false;       mWidth = 0;       mHeight = 0;       mRenderer = renderer;       setName("GLThread");     }     @Override     public void run() {       /*        * When the android framework launches a second instance of an        * activity, the new instance's onCreate() method may be called        * before the first instance returns from onDestroy().        *         * This semaphore ensures that only one instance at a time accesses        * EGL.        */       try {         try {           sEglSemaphore.acquire();         } catch (InterruptedException e) {           return;         }         guardedRun();       } catch (InterruptedException e) {         // fall thru and exit normally       } finally {         sEglSemaphore.release();       }     }     private void guardedRun() throws InterruptedException {       mEglHelper = new EglHelper();       /*        * Specify a configuration for our opengl session and grab the first        * configuration that matches is        */       int[] configSpec = mRenderer.getConfigSpec();       mEglHelper.start(configSpec);       GL10 gl = null;       boolean tellRendererSurfaceCreated = true;       boolean tellRendererSurfaceChanged = true;       /*        * This is our main activity thread's loop, we go until asked to        * quit.        */       while (!mDone) {         /*          * Update the asynchronous state (window size)          */         int w, h;         boolean changed;         boolean needStart = false;         synchronized (this) {           if (mEvent != null) {             mEvent.run();           }           if (mPaused) {             mEglHelper.finish();             needStart = true;           }           if (needToWait()) {             while (needToWait()) {               wait();             }           }           if (mDone) {             break;           }           changed = mSizeChanged;           w = mWidth;           h = mHeight;           mSizeChanged = false;         }         if (needStart) {           mEglHelper.start(configSpec);           tellRendererSurfaceCreated = true;           changed = true;         }         if (changed) {           gl = (GL10) mEglHelper.createSurface(mHolder);           tellRendererSurfaceChanged = true;         }         if (tellRendererSurfaceCreated) {           mRenderer.surfaceCreated(gl);           tellRendererSurfaceCreated = false;         }         if (tellRendererSurfaceChanged) {           mRenderer.sizeChanged(gl, w, h);           tellRendererSurfaceChanged = false;         }         if ((w > 0) && (h > 0)) {           /* draw a frame here */           mRenderer.drawFrame(gl);           /*            * Once we're done with GL, we need to call swapBuffers() to            * instruct the system to display the rendered frame            */           mEglHelper.swap();         }       }       /*        * clean-up everything...        */       if (gl != null) {         mRenderer.shutdown(gl);       }       mEglHelper.finish();     }     private boolean needToWait() {       return (mPaused || (!mHasFocus) || (!mHasSurface) || mContextLost)           && (!mDone);     }     public void surfaceCreated() {       synchronized (this) {         mHasSurface = true;         mContextLost = false;         notify();       }     }     public void surfaceDestroyed() {       synchronized (this) {         mHasSurface = false;         notify();       }     }     public void onPause() {       synchronized (this) {         mPaused = true;       }     }     public void onResume() {       synchronized (this) {         mPaused = false;         notify();       }     }     public void onWindowFocusChanged(boolean hasFocus) {       synchronized (this) {         mHasFocus = hasFocus;         if (mHasFocus == true) {           notify();         }       }     }     public void onWindowResize(int w, int h) {       synchronized (this) {         mWidth = w;         mHeight = h;         mSizeChanged = true;       }     }     public void requestExitAndWait() {       // don't call this from GLThread thread or it is a guaranteed       // deadlock!       synchronized (this) {         mDone = true;         notify();       }       try {         join();       } catch (InterruptedException ex) {         Thread.currentThread().interrupt();       }     }     /**      * 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 setEvent(Runnable r) {       synchronized (this) {         mEvent = r;       }     }     public void clearEvent() {       synchronized (this) {         mEvent = null;       }     }     private boolean mDone;     private boolean mPaused;     private boolean mHasFocus;     private boolean mHasSurface;     private boolean mContextLost;     private int mWidth;     private int mHeight;     private Renderer mRenderer;     private Runnable mEvent;     private EglHelper mEglHelper;   }   private static final Semaphore sEglSemaphore = new Semaphore(1);   private boolean mSizeChanged = true;   private SurfaceHolder mHolder;   private GLThread mGLThread;   private GLWrapper mGLWrapper; }