Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e3924992 authored by Romain Guy's avatar Romain Guy
Browse files

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
parent 0a87ae79
Loading
Loading
Loading
Loading
+342 −114
Original line number Diff line number Diff line
@@ -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,91 +161,170 @@ 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;

        EGL10 mEgl;
        EGLDisplay mEglDisplay;
        EGLContext mEglContext;
        EGLSurface mEglSurface;
        EGLConfig mEglConfig;

        GL mGl;
        Canvas mCanvas;

        private Canvas mGlCanvas;
        int mGlVersion;

        private Gl10Renderer() {
        GlRenderer(int glVersion) {
            mGlVersion = glVersion;
        }

        /**
         * 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 error = mEgl.eglGetError();
                if (error != EGL10.EGL_SUCCESS) {
                    // something bad has happened revert to
                    // normal rendering.
                    destroy();
                    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()) {
                initializeEgl();
                mGl = createEglSurface(holder);

        private void initializeGL(SurfaceHolder holder) {
            initializeGLInner(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;
        }

        private void initializeGLInner(SurfaceHolder holder) {
            final EGL10 egl = (EGL10) EGLContext.getEGL();
            mEgl = egl;
        abstract Canvas createCanvas();

        void initializeEgl() {
            mEgl = (EGL10) EGLContext.getEGL();
            
            // Get to the default display.
            mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
            
            final EGLDisplay eglDisplay = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
            mEglDisplay = eglDisplay;
            if (mEglDisplay == EGL10.EGL_NO_DISPLAY) {
                throw new RuntimeException("eglGetDisplay failed");
            }
            
            // We can now initialize EGL for that display
            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];
            if (!mEgl.eglInitialize(mEglDisplay, version)) {
                throw new RuntimeException("eglInitialize failed");
            }
            mEglConfig = getConfigChooser(mGlVersion).chooseConfig(mEgl, mEglDisplay);
            
            /*
             * Create an OpenGL ES context. This must be done only once, an
             * OpenGL context is a somewhat heavy object.
            * Create an EGL context. We want to do this as rarely as we can, because an
            * EGL context is a somewhat heavy object.
            */
            final EGLContext context = egl.eglCreateContext(eglDisplay, config,
                    EGL10.EGL_NO_CONTEXT, null);
            mEglContext = context;
            mEglContext = createContext(mEgl, mEglDisplay, mEglConfig);
        }

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

            /*
             * Create an EGL surface we can render into.
             *  The window size has changed, so we need to create a new
             *  surface.
             */
            EGLSurface surface = egl.eglCreateWindowSurface(eglDisplay, config, holder, null);
            mEglSurface = surface;
            if (mEglSurface != null && mEglSurface != EGL10.EGL_NO_SURFACE) {

                /*
             * Before we can issue GL commands, we need to make sure
             * the context is current and bound to a surface.
                 * Unbind and destroy the old EGL surface, if
                 * there is one.
                 */
            egl.eglMakeCurrent(eglDisplay, surface, surface, context);
                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");
            }

            /*
             * Get to the appropriate GL interface.
             * This is simply done by casting the GL context to either
             * GL10 or GL11.
             * Before we can issue GL commands, we need to make sure
             * the context is current and bound to a surface.
             */
            final GL11 gl = (GL11) context.getGL();
            mGL = gl;
            mGlCanvas = new Canvas(gl);
            setEnabled(true);
            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
        void initializeIfNeeded(int width, int height, View.AttachInfo attachInfo,
                SurfaceHolder holder) {

            if (isRequested()) {
                checkErrors();
                super.initializeIfNeeded(width, height, attachInfo, holder);
            }
        }
        
        @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);
@@ -251,68 +335,57 @@ abstract class HardwareRenderer {
            mEglSurface = null;
            mEglDisplay = null;
            mEgl = null;
            mGlCanvas = null;
            mGL = null;
            mGl = null;
            mCanvas = null;

            setEnabled(false);
        }        
        
        private void checkErrors() {
            if (isEnabled()) {
                int err = mEgl.eglGetError();
                if (err != EGL10.EGL_SUCCESS) {
                    // something bad has happened revert to
                    // normal rendering.
                    destroy();
                    if (err != EGL11.EGL_CONTEXT_LOST) {
                        // we'll try again if it was context lost
                        setRequested(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));
        }
        
        @Override
        boolean initialize(SurfaceHolder holder) {
            if (isRequested() && !isEnabled()) {
                initializeGL(holder);
                return mGlCanvas != null;
        boolean canDraw() {
            return mGl != null && mCanvas != null;
        }        
            return false;
        
        void onPreDraw() {
        }

        @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));
        /**
         * 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) {

            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);
    
            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);
                    canvas.setScreenDensity(scalingRequired ? DisplayMetrics.DENSITY_DEVICE : 0);
    
                    view.draw(canvas);
    
                } finally {
                    canvas.restoreToCount(saveCount);
                }
@@ -324,18 +397,173 @@ abstract class HardwareRenderer {
            }
        }

        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
        void initializeIfNeeded(int width, int height, View.AttachInfo attachInfo,
                SurfaceHolder holder) {
            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;
            }

            if (isRequested()) {
                checkErrors();
                super.initializeIfNeeded(width, height, attachInfo, holder);
            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;
        }        
    }

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