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