Android OpenGL ES(五)GLSurfaceView .
Android OpenGL ES 相关的包主要定义在
- javax.microedition.khronos.opengles GL 绘图指令
- javax.microedition.khronos.egl EGL 管理Display, surface等
- android.opengl Android GL辅助类,连接OpenGL 与Android View,Activity
- javax.nio Buffer类
其中GLSurfaceView 为android.opengl 包中核心类:
- 起到连接OpenGL ES与Android 的View层次结构之间的桥梁作用。
- 使得Open GL ES库适应于Anndroid系统的Activity生命周期。
- 使得选择合适的Frame buffer像素格式变得容易。
- 创建和管理单独绘图线程以达到平滑动画效果。
- 提供了方便使用的调试工具来跟踪OpenGL ES函数调用以帮助检查错误。
使用过Java ME ,JSR 239 开发过OpenGL ES可以看到 Android 包javax.microedition.khronos.egl ,javax.microedition.khronos.opengles 和JSR239 基本一致,因此理论上不使用android.opengl 包中的类也可以开发Android上OpenGL ES应用,但此时就需要自己使用EGL来管理Display,Context, Surfaces 的创建,释放,捆绑。
使用EGL 实现GLSurfaceView一个可能的实现如下:
class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable { public GLSurfaceView(Context context) { super(context); mHolder = getHolder(); mHolder.addCallback(this); mHolder.setType(SurfaceHolder.SURFACE_TYPE_GPU); } public void setRenderer(Renderer renderer) { mRenderer = renderer; } public void surfaceCreated(SurfaceHolder holder) { } public void surfaceDestroyed(SurfaceHolder holder) { running = false; try { thread.join(); } catch (InterruptedException e) { } thread = null; } public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { synchronized(this){ mWidth = w; mHeight = h; thread = new Thread(this); thread.start(); } } public interface Renderer { void EGLCreate(SurfaceHolder holder); void EGLDestroy(); int Initialize(int width, int height); void DrawScene(int width, int height); } public void run() { synchronized(this) { mRenderer.EGLCreate(mHolder); mRenderer.Initialize(mWidth, mHeight); running=true; while (running) { mRenderer.DrawScene(mWidth, mHeight); } mRenderer.EGLDestroy(); } } private SurfaceHolder mHolder; private Thread thread; private boolean running; private Renderer mRenderer; private int mWidth; private int mHeight; } class GLRenderer implements GLSurfaceView.Renderer { public GLRenderer() { } public int Initialize(int width, int height){ gl.glClearColor(1.0f, 0.0f, 0.0f, 0.0f); return 1; } public void DrawScene(int width, int height){ gl.glClear(GL10.GL_COLOR_BUFFER_BIT); egl.eglSwapBuffers(eglDisplay, eglSurface); } public void EGLCreate(SurfaceHolder holder){ int[] num_config = new int[1]; EGLConfig[] configs = new EGLConfig[1]; int[] configSpec = { EGL10.EGL_RED_SIZE, 8, EGL10.EGL_GREEN_SIZE, 8, EGL10.EGL_BLUE_SIZE, 8, EGL10.EGL_SURFACE_TYPE, EGL10.EGL_WINDOW_BIT, EGL10.EGL_NONE }; this.egl = (EGL10) EGLContext.getEGL(); eglDisplay = this.egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); this.egl.eglInitialize(eglDisplay, null); this.egl.eglChooseConfig(eglDisplay, configSpec, configs, 1, num_config); eglConfig = configs[0]; eglContext = this.egl.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, null); eglSurface = this.egl.eglCreateWindowSurface(eglDisplay, eglConfig, holder, null); this.egl.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext); gl = (GL10)eglContext.getGL(); } public void EGLDestroy(){ if (eglSurface != null) { egl.eglMakeCurrent(eglDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); egl.eglDestroySurface(eglDisplay, eglSurface); eglSurface = null; } if (eglContext != null) { egl.eglDestroyContext(eglDisplay, eglContext); eglContext = null; } if (eglDisplay != null) { egl.eglTerminate(eglDisplay); eglDisplay = null; } } private EGL10 egl; private GL10 gl; private EGLDisplay eglDisplay; private EGLConfig eglConfig; private EGLContext eglContext; private EGLSurface eglSurface; }
可以看到需要派生SurfaceView ,并手工创建,销毁Display,Context ,工作繁琐。
使用GLSurfaceView 内部提供了上面类似的实现,对于大部分应用只需调用一个方法来设置OpenGLView用到的GLSurfaceView.Renderer.
public void setRenderer(GLSurfaceView.Renderer renderer)
GLSurfaceView.Renderer定义了一个统一图形绘制的接口,它定义了如下三个接口函数:
// Called when the surface is created or recreated. public void onSurfaceCreated(GL10 gl, EGLConfig config) // Called to draw the current frame. public void onDrawFrame(GL10 gl) // Called when the surface changed size. public void onSurfaceChanged(GL10 gl, int width, int height)
- onSurfaceCreated : 在这个方法中主要用来设置一些绘制时不常变化的参数,比如:背景色,是否打开 z-buffer等。
- onDrawFrame: 定义实际的绘图操作。
- onSurfaceChanged: 如果设备支持屏幕横向和纵向切换,这个方法将发生在横向<->纵向互换时。此时可以重新设置绘制的纵横比率。
如果有需要,也可以通过函数来修改GLSurfaceView一些缺省设置:
- setDebugFlags(int) 设置Debug标志。
- setEGLConfigChooser (boolean) 选择一个Config接近16bitRGB颜色模式,可以打开或关闭深度(Depth)Buffer ,缺省为RGB_565 并打开至少有16bit 的 depth Buffer.
- setEGLConfigChooser(EGLConfigChooser) 选择自定义EGLConfigChooser。
- setEGLConfigChooser(int, int, int, int, int, int) 指定red ,green, blue, alpha, depth ,stencil 支持的位数,缺省为RGB_565 ,16 bit depth buffer.
GLSurfaceView 缺省创建为RGB_565 颜色格式的Surface ,如果需要支持透明度,可以调用getHolder().setFormat(PixelFormat.TRANSLUCENT).
GLSurfaceView 的渲染模式有两种,一种是连续不断的更新屏幕,另一种为on-demand ,只有在调用requestRender() 在更新屏幕。 缺省为RENDERMODE_CONTINUOUSLY 持续刷新屏幕。
GLSurfaceView是我认为Android源码体系中一个非常值得研究和学习的源代码,当你完全看懂GLSurfaceView的大部分代码和代码结构设计对你在以后的编码过程中设计自己的功能模块时有很好的借鉴作用。
如下代码是完全按照系统的GLSurfaceView的代码结构设计的动态GL壁纸Engine,能够非常方便的实现OpenGL方式的动态壁纸:
public class GLWallpaperService extends WallpaperService { private static final String TAG = "GLWallpaperService"; @Override public Engine onCreateEngine() { return new GLEngine(); } public class GLEngine extends Engine { public final static int RENDERMODE_WHEN_DIRTY = 0; public final static int RENDERMODE_CONTINUOUSLY = 1; private GLThread mGLThread; private EGLConfigChooser mEGLConfigChooser; private EGLContextFactory mEGLContextFactory; private EGLWindowSurfaceFactory mEGLWindowSurfaceFactory; private GLWrapper mGLWrapper; private int mDebugFlags; public GLEngine() { super(); } @Override public void onVisibilityChanged(boolean visible) { if (visible) { onResume(); } else { onPause(); } super.onVisibilityChanged(visible); } @Override public void onCreate(SurfaceHolder surfaceHolder) { super.onCreate(surfaceHolder); // Log.d(TAG, "GLEngine.onCreate()"); } @Override public void onDestroy() { super.onDestroy(); // Log.d(TAG, "GLEngine.onDestroy()"); mGLThread.requestExitAndWait(); } @Override public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) { // Log.d(TAG, "onSurfaceChanged()"); mGLThread.onWindowResize(width, height); super.onSurfaceChanged(holder, format, width, height); } @Override public void onSurfaceCreated(SurfaceHolder holder) { Log.d(TAG, "onSurfaceCreated()"); mGLThread.surfaceCreated(holder); super.onSurfaceCreated(holder); } @Override public void onSurfaceDestroyed(SurfaceHolder holder) { Log.d(TAG, "onSurfaceDestroyed()"); mGLThread.surfaceDestroyed(); super.onSurfaceDestroyed(holder); } /** * An EGL helper class. */ public void setGLWrapper(GLWrapper glWrapper) { mGLWrapper = glWrapper; } public void setDebugFlags(int debugFlags) { mDebugFlags = debugFlags; } public int getDebugFlags() { return mDebugFlags; } public void setRenderer(Renderer renderer) { checkRenderThreadState(); if (mEGLConfigChooser == null) { mEGLConfigChooser = new SimpleEGLConfigChooser(true); } if (mEGLContextFactory == null) { mEGLContextFactory = new DefaultContextFactory(); } if (mEGLWindowSurfaceFactory == null) { mEGLWindowSurfaceFactory = new DefaultWindowSurfaceFactory(); } mGLThread = new GLThread(renderer, mEGLConfigChooser, mEGLContextFactory, mEGLWindowSurfaceFactory, mGLWrapper); mGLThread.start(); } public void setEGLContextFactory(EGLContextFactory factory) { checkRenderThreadState(); mEGLContextFactory = factory; } public void setEGLWindowSurfaceFactory(EGLWindowSurfaceFactory factory) { checkRenderThreadState(); mEGLWindowSurfaceFactory = factory; } public void setEGLConfigChooser(EGLConfigChooser configChooser) { checkRenderThreadState(); mEGLConfigChooser = configChooser; } public void setEGLConfigChooser(boolean needDepth) { setEGLConfigChooser(new SimpleEGLConfigChooser(needDepth)); } public void setEGLConfigChooser(int redSize, int greenSize, int blueSize, int alphaSize, int depthSize, int stencilSize) { setEGLConfigChooser(new ComponentSizeChooser(redSize, greenSize, blueSize, alphaSize, depthSize, stencilSize)); } public void setRenderMode(int renderMode) { mGLThread.setRenderMode(renderMode); } public int getRenderMode() { return mGLThread.getRenderMode(); } public void requestRender() { mGLThread.requestRender(); } public void onPause() { mGLThread.onPause(); } public void onResume() { mGLThread.onResume(); } public void queueEvent(Runnable r) { mGLThread.queueEvent(r); } private void checkRenderThreadState() { if (mGLThread != null) { throw new IllegalStateException("setRenderer has already been called for this instance."); } } } public interface Renderer { public void onSurfaceCreated(GL10 gl, EGLConfig config); public void onSurfaceChanged(GL10 gl, int width, int height); public void onDrawFrame(GL10 gl); } } class LogWriter extends Writer { private StringBuilder mBuilder = new StringBuilder(); @Override public void close() { flushBuilder(); } @Override public void flush() { flushBuilder(); } @Override public void write(char[] buf, int offset, int count) { for (int i = 0; i < count; i++) { char c = buf[offset + i]; if (c == '\n') { flushBuilder(); } else { mBuilder.append(c); } } } private void flushBuilder() { if (mBuilder.length() > 0) { Log.v("GLSurfaceView", mBuilder.toString()); mBuilder.delete(0, mBuilder.length()); } } } // ---------------------------------------------------------------------- /** * An interface for customizing the eglCreateContext and eglDestroyContext calls. * * This interface must be implemented by clients wishing to call * {@link GLWallpaperService#setEGLContextFactory(EGLContextFactory)} */ interface EGLContextFactory { EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig eglConfig); void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context); } class DefaultContextFactory implements EGLContextFactory { public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig config) { return egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT, null); } public void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context) { egl.eglDestroyContext(display, context); } } /** * An interface for customizing the eglCreateWindowSurface and eglDestroySurface calls. * * This interface must be implemented by clients wishing to call * {@link GLWallpaperService#setEGLWindowSurfaceFactory(EGLWindowSurfaceFactory)} */ interface EGLWindowSurfaceFactory { EGLSurface createWindowSurface(EGL10 egl, EGLDisplay display, EGLConfig config, Object nativeWindow); void destroySurface(EGL10 egl, EGLDisplay display, EGLSurface surface); } class DefaultWindowSurfaceFactory implements EGLWindowSurfaceFactory { public EGLSurface createWindowSurface(EGL10 egl, EGLDisplay display, EGLConfig config, Object nativeWindow) { // this is a bit of a hack to work around Droid init problems - if you don't have this, it'll get hung up on orientation changes EGLSurface eglSurface = null; while (eglSurface == null) { try { eglSurface = egl.eglCreateWindowSurface(display, config, nativeWindow, null); } catch (Throwable t) { } finally { if (eglSurface == null) { try { Thread.sleep(10); } catch (InterruptedException t) { } } } } return eglSurface; } public void destroySurface(EGL10 egl, EGLDisplay display, EGLSurface surface) { egl.eglDestroySurface(display, surface); } } interface GLWrapper { /** * Wraps a gl interface in another gl interface. * * @param gl * a GL interface that is to be wrapped. * @return either the input argument or another GL object that wraps the input argument. */ GL wrap(GL gl); } class EglHelper { private EGL10 mEgl; private EGLDisplay mEglDisplay; private EGLSurface mEglSurface; private EGLContext mEglContext; EGLConfig mEglConfig; private EGLConfigChooser mEGLConfigChooser; private EGLContextFactory mEGLContextFactory; private EGLWindowSurfaceFactory mEGLWindowSurfaceFactory; private GLWrapper mGLWrapper; public EglHelper(EGLConfigChooser chooser, EGLContextFactory contextFactory, EGLWindowSurfaceFactory surfaceFactory, GLWrapper wrapper) { this.mEGLConfigChooser = chooser; this.mEGLContextFactory = contextFactory; this.mEGLWindowSurfaceFactory = surfaceFactory; this.mGLWrapper = wrapper; } /** * Initialize EGL for a given configuration spec. * * @param configSpec */ public void start() { // Log.d("EglHelper" + instanceId, "start()"); if (mEgl == null) { // Log.d("EglHelper" + instanceId, "getting new EGL"); /* * Get an EGL instance */ mEgl = (EGL10) EGLContext.getEGL(); } else { // Log.d("EglHelper" + instanceId, "reusing EGL"); } if (mEglDisplay == null) { // Log.d("EglHelper" + instanceId, "getting new display"); /* * Get to the default display. */ mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); } else { // Log.d("EglHelper" + instanceId, "reusing display"); } if (mEglConfig == null) { // Log.d("EglHelper" + instanceId, "getting new config"); /* * We can now initialize EGL for that display */ int[] version = new int[2]; mEgl.eglInitialize(mEglDisplay, version); mEglConfig = mEGLConfigChooser.chooseConfig(mEgl, mEglDisplay); } else { // Log.d("EglHelper" + instanceId, "reusing config"); } if (mEglContext == null) { // Log.d("EglHelper" + instanceId, "creating new context"); /* * Create an OpenGL ES context. This must be done only once, an OpenGL context is a somewhat heavy object. */ mEglContext = mEGLContextFactory.createContext(mEgl, mEglDisplay, mEglConfig); if (mEglContext == null || mEglContext == EGL10.EGL_NO_CONTEXT) { throw new RuntimeException("createContext failed"); } } else { // Log.d("EglHelper" + instanceId, "reusing context"); } mEglSurface = null; } /* * React to the creation of a new surface by creating and returning an OpenGL interface that renders to that * surface. */ public GL createSurface(SurfaceHolder holder) { /* * The window size has changed, so we need to create a new surface. */ if (mEglSurface != null && mEglSurface != EGL10.EGL_NO_SURFACE) { /* * 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); mEGLWindowSurfaceFactory.destroySurface(mEgl, mEglDisplay, mEglSurface); } /* * Create an EGL surface we can render into. */ mEglSurface = mEGLWindowSurfaceFactory.createWindowSurface(mEgl, mEglDisplay, mEglConfig, holder); if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) { throw new RuntimeException("createWindowSurface failed"); } /* * Before we can issue GL commands, we need to make sure the context is current and bound to a surface. */ if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) { throw new RuntimeException("eglMakeCurrent failed."); } GL gl = mEglContext.getGL(); if (mGLWrapper != null) { gl = mGLWrapper.wrap(gl); } /* * if ((mDebugFlags & (DEBUG_CHECK_GL_ERROR | DEBUG_LOG_GL_CALLS))!= 0) { int configFlags = 0; Writer log = * null; if ((mDebugFlags & DEBUG_CHECK_GL_ERROR) != 0) { configFlags |= GLDebugHelper.CONFIG_CHECK_GL_ERROR; } * if ((mDebugFlags & DEBUG_LOG_GL_CALLS) != 0) { log = new LogWriter(); } gl = GLDebugHelper.wrap(gl, * configFlags, log); } */ 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 destroySurface() { if (mEglSurface != null && mEglSurface != EGL10.EGL_NO_SURFACE) { mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); mEGLWindowSurfaceFactory.destroySurface(mEgl, mEglDisplay, mEglSurface); mEglSurface = null; } } public void finish() { if (mEglContext != null) { mEGLContextFactory.destroyContext(mEgl, mEglDisplay, mEglContext); mEglContext = null; } if (mEglDisplay != null) { mEgl.eglTerminate(mEglDisplay); mEglDisplay = null; } } } class GLThread extends Thread { private final static boolean LOG_THREADS = false; public final static int DEBUG_CHECK_GL_ERROR = 1; public final static int DEBUG_LOG_GL_CALLS = 2; private final GLThreadManager sGLThreadManager = new GLThreadManager(); private GLThread mEglOwner; private EGLConfigChooser mEGLConfigChooser; private EGLContextFactory mEGLContextFactory; private EGLWindowSurfaceFactory mEGLWindowSurfaceFactory; private GLWrapper mGLWrapper; public SurfaceHolder mHolder; private boolean mSizeChanged = true; // Once the thread is started, all accesses to the following member // variables are protected by the sGLThreadManager monitor public boolean mDone; private boolean mPaused; private boolean mHasSurface; private boolean mWaitingForSurface; private boolean mHaveEgl; private int mWidth; private int mHeight; private int mRenderMode; private boolean mRequestRender; private boolean mEventsWaiting; // End of member variables protected by the sGLThreadManager monitor. private GLWallpaperService.Renderer mRenderer; private ArrayList<Runnable> mEventQueue = new ArrayList<Runnable>(); private EglHelper mEglHelper; GLThread(GLWallpaperService.Renderer renderer, EGLConfigChooser chooser, EGLContextFactory contextFactory, EGLWindowSurfaceFactory surfaceFactory, GLWrapper wrapper) { super(); mDone = false; mWidth = 0; mHeight = 0; mRequestRender = true; mRenderMode = GLWallpaperService.GLEngine.RENDERMODE_CONTINUOUSLY; mRenderer = renderer; this.mEGLConfigChooser = chooser; this.mEGLContextFactory = contextFactory; this.mEGLWindowSurfaceFactory = surfaceFactory; this.mGLWrapper = wrapper; } @Override public void run() { setName("GLThread " + getId()); if (LOG_THREADS) { Log.i("GLThread", "starting tid=" + getId()); } try { guardedRun(); } catch (InterruptedException e) { // fall thru and exit normally } finally { sGLThreadManager.threadExiting(this); } } /* * This private method should only be called inside a synchronized(sGLThreadManager) block. */ private void stopEglLocked() { if (mHaveEgl) { mHaveEgl = false; mEglHelper.destroySurface(); sGLThreadManager.releaseEglSurface(this); } } private void guardedRun() throws InterruptedException { mEglHelper = new EglHelper(mEGLConfigChooser, mEGLContextFactory, mEGLWindowSurfaceFactory, mGLWrapper); try { GL10 gl = null; boolean tellRendererSurfaceCreated = true; boolean tellRendererSurfaceChanged = true; /* * This is our main activity thread's loop, we go until asked to quit. */ while (!isDone()) { /* * Update the asynchronous state (window size) */ int w = 0; int h = 0; boolean changed = false; boolean needStart = false; boolean eventsWaiting = false; synchronized (sGLThreadManager) { while (true) { // Manage acquiring and releasing the SurfaceView // surface and the EGL surface. if (mPaused) { stopEglLocked(); } if (!mHasSurface) { if (!mWaitingForSurface) { stopEglLocked(); mWaitingForSurface = true; sGLThreadManager.notifyAll(); } } else { if (!mHaveEgl) { if (sGLThreadManager.tryAcquireEglSurface(this)) { mHaveEgl = true; mEglHelper.start(); mRequestRender = true; needStart = true; } } } // Check if we need to wait. If not, update any state // that needs to be updated, copy any state that // needs to be copied, and use "break" to exit the // wait loop. if (mDone) { return; } if (mEventsWaiting) { eventsWaiting = true; mEventsWaiting = false; break; } if ((!mPaused) && mHasSurface && mHaveEgl && (mWidth > 0) && (mHeight > 0) && (mRequestRender || (mRenderMode == GLWallpaperService.GLEngine.RENDERMODE_CONTINUOUSLY))) { changed = mSizeChanged; w = mWidth; h = mHeight; mSizeChanged = false; mRequestRender = false; if (mHasSurface && mWaitingForSurface) { changed = true; mWaitingForSurface = false; sGLThreadManager.notifyAll(); } break; } // By design, this is the only place where we wait(). if (LOG_THREADS) { Log.i("GLThread", "waiting tid=" + getId()); } sGLThreadManager.wait(); } } // end of synchronized(sGLThreadManager) /* * Handle queued events */ if (eventsWaiting) { Runnable r; while ((r = getEvent()) != null) { r.run(); if (isDone()) { return; } } // Go back and see if we need to wait to render. continue; } if (needStart) { tellRendererSurfaceCreated = true; changed = true; } if (changed) { gl = (GL10) mEglHelper.createSurface(mHolder); tellRendererSurfaceChanged = true; } if (tellRendererSurfaceCreated) { mRenderer.onSurfaceCreated(gl, mEglHelper.mEglConfig); tellRendererSurfaceCreated = false; } if (tellRendererSurfaceChanged) { mRenderer.onSurfaceChanged(gl, w, h); tellRendererSurfaceChanged = false; } if ((w > 0) && (h > 0)) { /* draw a frame here */ mRenderer.onDrawFrame(gl); /* * Once we're done with GL, we need to call swapBuffers() to instruct the system to display the * rendered frame */ mEglHelper.swap(); Thread.sleep(10); } } } finally { /* * clean-up everything... */ synchronized (sGLThreadManager) { stopEglLocked(); mEglHelper.finish(); } } } private boolean isDone() { synchronized (sGLThreadManager) { return mDone; } } public void setRenderMode(int renderMode) { if (!((GLWallpaperService.GLEngine.RENDERMODE_WHEN_DIRTY <= renderMode) && (renderMode <= GLWallpaperService.GLEngine.RENDERMODE_CONTINUOUSLY))) { throw new IllegalArgumentException("renderMode"); } synchronized (sGLThreadManager) { mRenderMode = renderMode; if (renderMode == GLWallpaperService.GLEngine.RENDERMODE_CONTINUOUSLY) { sGLThreadManager.notifyAll(); } } } public int getRenderMode() { synchronized (sGLThreadManager) { return mRenderMode; } } public void requestRender() { synchronized (sGLThreadManager) { mRequestRender = true; sGLThreadManager.notifyAll(); } } public void surfaceCreated(SurfaceHolder holder) { mHolder = holder; synchronized (sGLThreadManager) { if (LOG_THREADS) { Log.i("GLThread", "surfaceCreated tid=" + getId()); } mHasSurface = true; sGLThreadManager.notifyAll(); } } public void surfaceDestroyed() { synchronized (sGLThreadManager) { if (LOG_THREADS) { Log.i("GLThread", "surfaceDestroyed tid=" + getId()); } mHasSurface = false; sGLThreadManager.notifyAll(); while (!mWaitingForSurface && isAlive() && !mDone) { try { sGLThreadManager.wait(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } } public void onPause() { synchronized (sGLThreadManager) { mPaused = true; sGLThreadManager.notifyAll(); } } public void onResume() { synchronized (sGLThreadManager) { mPaused = false; mRequestRender = true; sGLThreadManager.notifyAll(); } } public void onWindowResize(int w, int h) { synchronized (sGLThreadManager) { mWidth = w; mHeight = h; mSizeChanged = true; sGLThreadManager.notifyAll(); } } public void requestExitAndWait() { // don't call this from GLThread thread or it is a guaranteed // deadlock! synchronized (sGLThreadManager) { mDone = true; sGLThreadManager.notifyAll(); } 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 queueEvent(Runnable r) { synchronized (this) { mEventQueue.add(r); synchronized (sGLThreadManager) { mEventsWaiting = true; sGLThreadManager.notifyAll(); } } } private Runnable getEvent() { synchronized (this) { if (mEventQueue.size() > 0) { return mEventQueue.remove(0); } } return null; } private class GLThreadManager { public synchronized void threadExiting(GLThread thread) { if (LOG_THREADS) { Log.i("GLThread", "exiting tid=" + thread.getId()); } thread.mDone = true; if (mEglOwner == thread) { mEglOwner = null; } notifyAll(); } /* * Tries once to acquire the right to use an EGL surface. Does not block. * * @return true if the right to use an EGL surface was acquired. */ public synchronized boolean tryAcquireEglSurface(GLThread thread) { if (mEglOwner == thread || mEglOwner == null) { mEglOwner = thread; notifyAll(); return true; } return false; } public synchronized void releaseEglSurface(GLThread thread) { if (mEglOwner == thread) { mEglOwner = null; } notifyAll(); } } } interface EGLConfigChooser { EGLConfig chooseConfig(EGL10 egl, EGLDisplay display); } abstract class BaseConfigChooser implements EGLConfigChooser { public BaseConfigChooser(int[] configSpec) { mConfigSpec = configSpec; } public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) { int[] num_config = new int[1]; egl.eglChooseConfig(display, mConfigSpec, null, 0, num_config); int numConfigs = num_config[0]; if (numConfigs <= 0) { throw new IllegalArgumentException("No configs match configSpec"); } EGLConfig[] configs = new EGLConfig[numConfigs]; egl.eglChooseConfig(display, mConfigSpec, configs, numConfigs, num_config); EGLConfig config = chooseConfig(egl, display, configs); if (config == null) { throw new IllegalArgumentException("No config chosen"); } return config; } abstract EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, EGLConfig[] configs); protected int[] mConfigSpec; public static class ComponentSizeChooser extends BaseConfigChooser { public ComponentSizeChooser(int redSize, int greenSize, int blueSize, int alphaSize, int depthSize, int stencilSize) { super(new int[] { EGL10.EGL_RED_SIZE, redSize, EGL10.EGL_GREEN_SIZE, greenSize, EGL10.EGL_BLUE_SIZE, blueSize, EGL10.EGL_ALPHA_SIZE, alphaSize, EGL10.EGL_DEPTH_SIZE, depthSize, EGL10.EGL_STENCIL_SIZE, stencilSize, EGL10.EGL_NONE }); mValue = new int[1]; mRedSize = redSize; mGreenSize = greenSize; mBlueSize = blueSize; mAlphaSize = alphaSize; mDepthSize = depthSize; mStencilSize = stencilSize; } @Override public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, EGLConfig[] configs) { EGLConfig closestConfig = null; int closestDistance = 1000; for (EGLConfig config : configs) { int d = findConfigAttrib(egl, display, config, EGL10.EGL_DEPTH_SIZE, 0); int s = findConfigAttrib(egl, display, config, EGL10.EGL_STENCIL_SIZE, 0); if (d >= mDepthSize && s >= mStencilSize) { int r = findConfigAttrib(egl, display, config, EGL10.EGL_RED_SIZE, 0); int g = findConfigAttrib(egl, display, config, EGL10.EGL_GREEN_SIZE, 0); int b = findConfigAttrib(egl, display, config, EGL10.EGL_BLUE_SIZE, 0); int a = findConfigAttrib(egl, display, config, EGL10.EGL_ALPHA_SIZE, 0); int distance = Math.abs(r - mRedSize) + Math.abs(g - mGreenSize) + Math.abs(b - mBlueSize) + Math.abs(a - mAlphaSize); if (distance < closestDistance) { closestDistance = distance; closestConfig = config; } } } return closestConfig; } private int findConfigAttrib(EGL10 egl, EGLDisplay display, EGLConfig config, int attribute, int defaultValue) { if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) { return mValue[0]; } return defaultValue; } private int[] mValue; // Subclasses can adjust these values: protected int mRedSize; protected int mGreenSize; protected int mBlueSize; protected int mAlphaSize; protected int mDepthSize; protected int mStencilSize; } /** * This class will choose a supported surface as close to RGB565 as possible, with or without a depth buffer. * */ public static class SimpleEGLConfigChooser extends ComponentSizeChooser { public SimpleEGLConfigChooser(boolean withDepthBuffer) { super(4, 4, 4, 0, withDepthBuffer ? 16 : 0, 0); // Adjust target values. This way we'll accept a 4444 or // 555 buffer if there's no 565 buffer available. mRedSize = 5; mGreenSize = 6; mBlueSize = 5; } } }