http://www.java2s.com/Code/Android/2D-Graphics/AnOpenGLESrendererbasedontheGLSurfaceViewrenderingframework.htm
/* * 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; }