From e3924992733234fd7d7a614c165a94e0a2cd6b84 Mon Sep 17 00:00:00 2001 From: Romain Guy Date: Thu, 10 Jun 2010 18:51:21 -0700 Subject: [PATCH] Refactor HardwareRenderer to allow the use of OpenGL ES 2.0. The current OpenGL ES 2.0 HardwareRenderer will fail and does not even attempt to create a Canvas. The next step will be to create a new native-backed Canvas that relies on OpenGL ES 2.0 and bypasses the current OpenGL ES 1.0 implementation done in Skia. Change-Id: I7a8e9f87f316e9137ef191bb5609213e160eaa4c --- core/java/android/view/HardwareRenderer.java | 516 +++++++++++++------ 1 file changed, 372 insertions(+), 144 deletions(-) diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java index d4e229ce2f247..bff4ecc5688a5 100644 --- a/core/java/android/view/HardwareRenderer.java +++ b/core/java/android/view/HardwareRenderer.java @@ -21,6 +21,7 @@ import android.content.res.CompatibilityInfo; import android.graphics.Canvas; import android.os.SystemClock; import android.util.DisplayMetrics; +import android.util.Log; import javax.microedition.khronos.egl.EGL10; import javax.microedition.khronos.egl.EGL11; @@ -28,6 +29,7 @@ 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.GL11; import static javax.microedition.khronos.opengles.GL10.GL_COLOR_BUFFER_BIT; @@ -41,6 +43,7 @@ import static javax.microedition.khronos.opengles.GL10.GL_SCISSOR_TEST; abstract class HardwareRenderer { private boolean mEnabled; private boolean mRequested = true; + private static final String LOG_TAG = "HardwareRenderer"; /** * Destroys the hardware rendering context. @@ -114,6 +117,8 @@ abstract class HardwareRenderer { switch (glVersion) { case 1: return new Gl10Renderer(); + case 2: + return new Gl20Renderer(); } throw new IllegalArgumentException("Unknown GL version: " + glVersion); } @@ -156,172 +161,154 @@ abstract class HardwareRenderer { mRequested = requested; } - /** - * Hardware renderer using OpenGL ES 1.0. - */ @SuppressWarnings({"deprecation"}) - static class Gl10Renderer extends HardwareRenderer { - private EGL10 mEgl; - private EGLDisplay mEglDisplay; - private EGLContext mEglContext; - private EGLSurface mEglSurface; - private GL11 mGL; + static abstract class GlRenderer extends HardwareRenderer { + private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098; - private Canvas mGlCanvas; + EGL10 mEgl; + EGLDisplay mEglDisplay; + EGLContext mEglContext; + EGLSurface mEglSurface; + EGLConfig mEglConfig; - private Gl10Renderer() { + GL mGl; + Canvas mCanvas; + + int mGlVersion; + + GlRenderer(int glVersion) { + mGlVersion = glVersion; } - private void initializeGL(SurfaceHolder holder) { - initializeGLInner(holder); - int err = mEgl.eglGetError(); - if (err != EGL10.EGL_SUCCESS) { - destroy(); - setRequested(false); - } - } - - private void initializeGLInner(SurfaceHolder holder) { - final EGL10 egl = (EGL10) EGLContext.getEGL(); - mEgl = egl; - - final EGLDisplay eglDisplay = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); - mEglDisplay = eglDisplay; - - int[] version = new int[2]; - egl.eglInitialize(eglDisplay, version); - - final int[] configSpec = { - EGL10.EGL_RED_SIZE, 8, - EGL10.EGL_GREEN_SIZE, 8, - EGL10.EGL_BLUE_SIZE, 8, - EGL10.EGL_DEPTH_SIZE, 0, - EGL10.EGL_NONE - }; - final EGLConfig[] configs = new EGLConfig[1]; - final int[] numConfig = new int[1]; - egl.eglChooseConfig(eglDisplay, configSpec, configs, 1, numConfig); - final EGLConfig config = configs[0]; - - /* - * Create an OpenGL ES context. This must be done only once, an - * OpenGL context is a somewhat heavy object. - */ - final EGLContext context = egl.eglCreateContext(eglDisplay, config, - EGL10.EGL_NO_CONTEXT, null); - mEglContext = context; - - /* - * Create an EGL surface we can render into. - */ - EGLSurface surface = egl.eglCreateWindowSurface(eglDisplay, config, holder, null); - mEglSurface = surface; - - /* - * Before we can issue GL commands, we need to make sure - * the context is current and bound to a surface. - */ - egl.eglMakeCurrent(eglDisplay, surface, surface, context); - - /* - * Get to the appropriate GL interface. - * This is simply done by casting the GL context to either - * GL10 or GL11. - */ - final GL11 gl = (GL11) context.getGL(); - mGL = gl; - mGlCanvas = new Canvas(gl); - setEnabled(true); - } - - @Override - void destroy() { - if (!isEnabled()) return; - - // inform skia that the context is gone - nativeAbandonGlCaches(); - - mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE, - EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); - mEgl.eglDestroyContext(mEglDisplay, mEglContext); - mEgl.eglDestroySurface(mEglDisplay, mEglSurface); - mEgl.eglTerminate(mEglDisplay); - - mEglContext = null; - mEglSurface = null; - mEglDisplay = null; - mEgl = null; - mGlCanvas = null; - mGL = null; - - setEnabled(false); - } - - private void checkErrors() { + /** + * Checks for OpenGL errors. If an error has occured, {@link #destroy()} + * is invoked and the requested flag is turned off. The error code is + * also logged as a warning. + */ + void checkErrors() { if (isEnabled()) { - int err = mEgl.eglGetError(); - if (err != EGL10.EGL_SUCCESS) { + int error = mEgl.eglGetError(); + if (error != EGL10.EGL_SUCCESS) { // something bad has happened revert to // normal rendering. destroy(); - if (err != EGL11.EGL_CONTEXT_LOST) { + if (error != EGL11.EGL_CONTEXT_LOST) { // we'll try again if it was context lost setRequested(false); } + Log.w(LOG_TAG, "OpenGL error: " + error); } } } - + @Override boolean initialize(SurfaceHolder holder) { if (isRequested() && !isEnabled()) { - initializeGL(holder); - return mGlCanvas != null; + initializeEgl(); + mGl = createEglSurface(holder); + + if (mGl != null) { + int err = mEgl.eglGetError(); + if (err != EGL10.EGL_SUCCESS) { + destroy(); + setRequested(false); + } else { + mCanvas = createCanvas(); + if (mCanvas != null) { + setEnabled(true); + } else { + Log.w(LOG_TAG, "Hardware accelerated Canvas could not be created"); + } + } + + return mCanvas != null; + } } return false; } - @Override - void setup(int width, int height, View.AttachInfo attachInfo) { - final float scale = attachInfo.mApplicationScale; - mGlCanvas.setViewport((int) (width * scale + 0.5f), (int) (height * scale + 0.5f)); + abstract Canvas createCanvas(); + + void initializeEgl() { + mEgl = (EGL10) EGLContext.getEGL(); + + // Get to the default display. + mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); + + if (mEglDisplay == EGL10.EGL_NO_DISPLAY) { + throw new RuntimeException("eglGetDisplay failed"); + } + + // We can now initialize EGL for that display + int[] version = new int[2]; + if (!mEgl.eglInitialize(mEglDisplay, version)) { + throw new RuntimeException("eglInitialize failed"); + } + mEglConfig = getConfigChooser(mGlVersion).chooseConfig(mEgl, mEglDisplay); + + /* + * Create an EGL context. We want to do this as rarely as we can, because an + * EGL context is a somewhat heavy object. + */ + mEglContext = createContext(mEgl, mEglDisplay, mEglConfig); } - @Override - void draw(View view, View.AttachInfo attachInfo, CompatibilityInfo.Translator translator, - int yoff, boolean scalingRequired) { - - Canvas canvas = mGlCanvas; - if (mGL != null && canvas != null) { - mGL.glDisable(GL_SCISSOR_TEST); - mGL.glClearColor(0, 0, 0, 0); - mGL.glClear(GL_COLOR_BUFFER_BIT); - mGL.glEnable(GL_SCISSOR_TEST); - - attachInfo.mDrawingTime = SystemClock.uptimeMillis(); - attachInfo.mIgnoreDirtyState = true; - view.mPrivateFlags |= View.DRAWN; - - int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG); - try { - canvas.translate(0, -yoff); - if (translator != null) { - translator.translateCanvas(canvas); - } - canvas.setScreenDensity(scalingRequired ? - DisplayMetrics.DENSITY_DEVICE : 0); - - view.draw(canvas); - - } finally { - canvas.restoreToCount(saveCount); - } - - attachInfo.mIgnoreDirtyState = false; - - mEgl.eglSwapBuffers(mEglDisplay, mEglSurface); - checkErrors(); + GL createEglSurface(SurfaceHolder holder) { + // Check preconditions. + if (mEgl == null) { + throw new RuntimeException("egl not initialized"); } + if (mEglDisplay == null) { + throw new RuntimeException("eglDisplay not initialized"); + } + if (mEglConfig == null) { + throw new RuntimeException("mEglConfig not initialized"); + } + + /* + * 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); + mEgl.eglDestroySurface(mEglDisplay, mEglSurface); + } + + // Create an EGL surface we can render into. + mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig, holder, null); + + if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) { + int error = mEgl.eglGetError(); + if (error == EGL10.EGL_BAD_NATIVE_WINDOW) { + Log.e("EglHelper", "createWindowSurface returned EGL_BAD_NATIVE_WINDOW."); + return null; + } + 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"); + + } + + return mEglContext.getGL(); + } + + EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) { + int[] attrib_list = { EGL_CONTEXT_CLIENT_VERSION, mGlVersion, EGL10.EGL_NONE }; + + return egl.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, + mGlVersion != 0 ? attrib_list : null); } @Override @@ -333,9 +320,250 @@ abstract class HardwareRenderer { super.initializeIfNeeded(width, height, attachInfo, holder); } } + + @Override + void destroy() { + if (!isEnabled()) return; + + mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE, + EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); + mEgl.eglDestroyContext(mEglDisplay, mEglContext); + mEgl.eglDestroySurface(mEglDisplay, mEglSurface); + mEgl.eglTerminate(mEglDisplay); + + mEglContext = null; + mEglSurface = null; + mEglDisplay = null; + mEgl = null; + mGl = null; + mCanvas = null; + + setEnabled(false); + } + + @Override + void setup(int width, int height, View.AttachInfo attachInfo) { + final float scale = attachInfo.mApplicationScale; + mCanvas.setViewport((int) (width * scale + 0.5f), (int) (height * scale + 0.5f)); + } + + boolean canDraw() { + return mGl != null && mCanvas != null; + } + + void onPreDraw() { + } + + /** + * Defines the EGL configuration for this renderer. The default configuration + * is RGBX, no depth, no stencil. + * + * @return An {@link android.view.HardwareRenderer.GlRenderer.EglConfigChooser}. + * @param glVersion + */ + EglConfigChooser getConfigChooser(int glVersion) { + return new ComponentSizeChooser(glVersion, 8, 8, 8, 0, 0, 0); + } + + @Override + void draw(View view, View.AttachInfo attachInfo, CompatibilityInfo.Translator translator, + int yoff, boolean scalingRequired) { + + if (canDraw()) { + attachInfo.mDrawingTime = SystemClock.uptimeMillis(); + attachInfo.mIgnoreDirtyState = true; + view.mPrivateFlags |= View.DRAWN; + + onPreDraw(); + + Canvas canvas = mCanvas; + int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG); + try { + canvas.translate(0, -yoff); + if (translator != null) { + translator.translateCanvas(canvas); + } + canvas.setScreenDensity(scalingRequired ? DisplayMetrics.DENSITY_DEVICE : 0); + + view.draw(canvas); + } finally { + canvas.restoreToCount(saveCount); + } + + attachInfo.mIgnoreDirtyState = false; + + mEgl.eglSwapBuffers(mEglDisplay, mEglSurface); + checkErrors(); + } + } + + static abstract class EglConfigChooser { + final int[] mConfigSpec; + private final int mGlVersion; + + EglConfigChooser(int glVersion, int[] configSpec) { + mGlVersion = glVersion; + mConfigSpec = filterConfigSpec(configSpec); + } + + EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) { + int[] num_config = new int[1]; + if (!egl.eglChooseConfig(display, mConfigSpec, null, 0, + num_config)) { + throw new IllegalArgumentException("eglChooseConfig failed"); + } + + int numConfigs = num_config[0]; + + if (numConfigs <= 0) { + throw new IllegalArgumentException( + "No configs match configSpec"); + } + + EGLConfig[] configs = new EGLConfig[numConfigs]; + if (!egl.eglChooseConfig(display, mConfigSpec, configs, numConfigs, + num_config)) { + throw new IllegalArgumentException("eglChooseConfig#2 failed"); + } + 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); + + private int[] filterConfigSpec(int[] configSpec) { + if (mGlVersion != 2) { + return configSpec; + } + /* We know none of the subclasses define EGL_RENDERABLE_TYPE. + * And we know the configSpec is well formed. + */ + int len = configSpec.length; + int[] newConfigSpec = new int[len + 2]; + System.arraycopy(configSpec, 0, newConfigSpec, 0, len-1); + newConfigSpec[len-1] = EGL10.EGL_RENDERABLE_TYPE; + newConfigSpec[len] = 4; /* EGL_OPENGL_ES2_BIT */ + newConfigSpec[len+1] = EGL10.EGL_NONE; + return newConfigSpec; + } + } + + /** + * Choose a configuration with exactly the specified r,g,b,a sizes, + * and at least the specified depth and stencil sizes. + */ + static class ComponentSizeChooser extends EglConfigChooser { + private int[] mValue; + + private int mRedSize; + private int mGreenSize; + private int mBlueSize; + private int mAlphaSize; + private int mDepthSize; + private int mStencilSize; + + ComponentSizeChooser(int glVersion, int redSize, int greenSize, int blueSize, + int alphaSize, int depthSize, int stencilSize) { + super(glVersion, 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 + EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, EGLConfig[] configs) { + 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); + if ((r == mRedSize) && (g == mGreenSize) && (b == mBlueSize) && + (a == mAlphaSize)) { + return config; + } + } + } + return null; + } + + 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; + } + } + } + + /** + * Hardware renderer using OpenGL ES 2.0. + */ + static class Gl20Renderer extends GlRenderer { + Gl20Renderer() { + super(2); + } + + @Override + Canvas createCanvas() { + return null; + } } - // inform Skia to just abandon its texture cache IDs - // doesn't call glDeleteTextures - private static native void nativeAbandonGlCaches(); + /** + * Hardware renderer using OpenGL ES 1.0. + */ + @SuppressWarnings({"deprecation"}) + static class Gl10Renderer extends GlRenderer { + Gl10Renderer() { + super(1); + } + + @Override + Canvas createCanvas() { + return new Canvas(mGl); + } + + @Override + void destroy() { + if (isEnabled()) { + nativeAbandonGlCaches(); + } + + super.destroy(); + } + + @Override + void onPreDraw() { + GL11 gl = (GL11) mGl; + gl.glDisable(GL_SCISSOR_TEST); + gl.glClearColor(0, 0, 0, 0); + gl.glClear(GL_COLOR_BUFFER_BIT); + gl.glEnable(GL_SCISSOR_TEST); + } + } + + // Inform Skia to just abandon its texture cache IDs doesn't call glDeleteTextures + // Used only by the native Skia OpenGL ES 1.x implementation + private static native void nativeAbandonGlCaches(); }