An OpenGL ES renderer based on the GLSurfaceView rendering framework. : OpenGL « 2D Graphics « Android






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;
}

   
    
    
  








Related examples in the same category

1.Wrapper activity demonstrating the use of GLSurfaceView, a view that uses OpenGL drawing into a dedicated surface.
2.Check for OpenGL ES 2.0 support at runtime, and then use either OpenGL ES 1.0 or OpenGL ES 2.0, as appropriate.
3.A GLSurfaceView.Renderer that uses the Android-specific android.opengl.GLESXXX static OpenGL ES APIs.
4.Wrapper activity demonstrating the use of {@link GLSurfaceView}, a view that uses OpenGL drawing into a dedicated surface.
5.Demonstrate how to use the OES_texture_cube_map extension, available on some high-end OpenGL ES 1.x GPUs.
6.OpenGL objects
7.OpenGL Sprite Text Activity
8.OpenGL Utils
9.Graphics API supports both OpenGL and Android 2D rendering targets efficiently through the same interface
10.OpenGL ES version of a sprite
11.OpenGL Utils 2
12.Demonstrate how to use ETC1 format compressed textures.