/* * Copyright (C) 2013 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. */ package android.view; import static javax.microedition.khronos.egl.EGL10.EGL_ALPHA_SIZE; import static javax.microedition.khronos.egl.EGL10.EGL_BAD_NATIVE_WINDOW; import static javax.microedition.khronos.egl.EGL10.EGL_BLUE_SIZE; import static javax.microedition.khronos.egl.EGL10.EGL_CONFIG_CAVEAT; import static javax.microedition.khronos.egl.EGL10.EGL_DEFAULT_DISPLAY; import static javax.microedition.khronos.egl.EGL10.EGL_DEPTH_SIZE; import static javax.microedition.khronos.egl.EGL10.EGL_DRAW; import static javax.microedition.khronos.egl.EGL10.EGL_GREEN_SIZE; import static javax.microedition.khronos.egl.EGL10.EGL_HEIGHT; import static javax.microedition.khronos.egl.EGL10.EGL_NONE; import static javax.microedition.khronos.egl.EGL10.EGL_NO_CONTEXT; import static javax.microedition.khronos.egl.EGL10.EGL_NO_DISPLAY; import static javax.microedition.khronos.egl.EGL10.EGL_NO_SURFACE; import static javax.microedition.khronos.egl.EGL10.EGL_RED_SIZE; import static javax.microedition.khronos.egl.EGL10.EGL_RENDERABLE_TYPE; import static javax.microedition.khronos.egl.EGL10.EGL_SAMPLES; import static javax.microedition.khronos.egl.EGL10.EGL_SAMPLE_BUFFERS; import static javax.microedition.khronos.egl.EGL10.EGL_STENCIL_SIZE; import static javax.microedition.khronos.egl.EGL10.EGL_SUCCESS; import static javax.microedition.khronos.egl.EGL10.EGL_SURFACE_TYPE; import static javax.microedition.khronos.egl.EGL10.EGL_WIDTH; import static javax.microedition.khronos.egl.EGL10.EGL_WINDOW_BIT; import android.content.ComponentCallbacks2; import android.graphics.Bitmap; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.SurfaceTexture; import android.opengl.EGL14; import android.opengl.GLUtils; import android.opengl.ManagedEGLContext; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; import android.os.Trace; import android.util.DisplayMetrics; import android.util.Log; import android.view.Surface.OutOfResourcesException; import com.google.android.gles_jni.EGLImpl; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import java.util.concurrent.locks.ReentrantLock; 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; /** * Hardware renderer using OpenGL * * @hide */ public class GLRenderer extends HardwareRenderer { static final int SURFACE_STATE_ERROR = 0; static final int SURFACE_STATE_SUCCESS = 1; static final int SURFACE_STATE_UPDATED = 2; static final int FUNCTOR_PROCESS_DELAY = 4; /** * Number of frames to profile. */ private static final int PROFILE_MAX_FRAMES = 128; /** * Number of floats per profiled frame. */ private static final int PROFILE_FRAME_DATA_COUNT = 3; private static final int PROFILE_DRAW_MARGIN = 0; private static final int PROFILE_DRAW_WIDTH = 3; private static final int[] PROFILE_DRAW_COLORS = { 0xcf3e66cc, 0xcfdc3912, 0xcfe69800 }; private static final int PROFILE_DRAW_CURRENT_FRAME_COLOR = 0xcf5faa4d; private static final int PROFILE_DRAW_THRESHOLD_COLOR = 0xff5faa4d; private static final int PROFILE_DRAW_THRESHOLD_STROKE_WIDTH = 2; private static final int PROFILE_DRAW_DP_PER_MS = 7; private static final String[] VISUALIZERS = { PROFILE_PROPERTY_VISUALIZE_BARS, }; private static final String[] OVERDRAW = { OVERDRAW_PROPERTY_SHOW, }; private static final int GL_VERSION = 2; static EGL10 sEgl; static EGLDisplay sEglDisplay; static EGLConfig sEglConfig; static final Object[] sEglLock = new Object[0]; int mWidth = -1, mHeight = -1; static final ThreadLocal sEglContextStorage = new ThreadLocal(); EGLContext mEglContext; Thread mEglThread; EGLSurface mEglSurface; GL mGl; HardwareCanvas mCanvas; String mName; long mFrameCount; Paint mDebugPaint; static boolean sDirtyRegions; static final boolean sDirtyRegionsRequested; static { String dirtyProperty = SystemProperties.get(RENDER_DIRTY_REGIONS_PROPERTY, "true"); //noinspection PointlessBooleanExpression,ConstantConditions sDirtyRegions = "true".equalsIgnoreCase(dirtyProperty); sDirtyRegionsRequested = sDirtyRegions; } boolean mDirtyRegionsEnabled; boolean mUpdateDirtyRegions; boolean mProfileEnabled; int mProfileVisualizerType = -1; float[] mProfileData; ReentrantLock mProfileLock; int mProfileCurrentFrame = -PROFILE_FRAME_DATA_COUNT; GraphDataProvider mDebugDataProvider; float[][] mProfileShapes; Paint mProfilePaint; boolean mDebugDirtyRegions; int mDebugOverdraw = -1; final boolean mTranslucent; private boolean mDestroyed; private final Rect mRedrawClip = new Rect(); private final int[] mSurfaceSize = new int[2]; private long mDrawDelta = Long.MAX_VALUE; private GLES20Canvas mGlCanvas; private DisplayMetrics mDisplayMetrics; private static EGLSurface sPbuffer; private static final Object[] sPbufferLock = new Object[0]; private List mAttachedLayers = new ArrayList(); private static class GLRendererEglContext extends ManagedEGLContext { final Handler mHandler = new Handler(); public GLRendererEglContext(EGLContext context) { super(context); } @Override public void onTerminate(final EGLContext eglContext) { // Make sure we do this on the correct thread. if (mHandler.getLooper() != Looper.myLooper()) { mHandler.post(new Runnable() { @Override public void run() { onTerminate(eglContext); } }); return; } synchronized (sEglLock) { if (sEgl == null) return; if (EGLImpl.getInitCount(sEglDisplay) == 1) { usePbufferSurface(eglContext); GLES20Canvas.terminateCaches(); sEgl.eglDestroyContext(sEglDisplay, eglContext); sEglContextStorage.set(null); sEglContextStorage.remove(); sEgl.eglDestroySurface(sEglDisplay, sPbuffer); sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); sEgl.eglReleaseThread(); sEgl.eglTerminate(sEglDisplay); sEgl = null; sEglDisplay = null; sEglConfig = null; sPbuffer = null; } } } } HardwareCanvas createCanvas() { return mGlCanvas = new GLES20Canvas(mTranslucent); } ManagedEGLContext createManagedContext(EGLContext eglContext) { return new GLRendererEglContext(mEglContext); } int[] getConfig(boolean dirtyRegions) { //noinspection PointlessBooleanExpression,ConstantConditions final int stencilSize = GLES20Canvas.getStencilSize(); final int swapBehavior = dirtyRegions ? EGL14.EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0; return new int[] { EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT, EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, EGL_DEPTH_SIZE, 0, EGL_CONFIG_CAVEAT, EGL_NONE, EGL_STENCIL_SIZE, stencilSize, EGL_SURFACE_TYPE, EGL_WINDOW_BIT | swapBehavior, EGL_NONE }; } void initCaches() { if (GLES20Canvas.initCaches()) { // Caches were (re)initialized, rebind atlas initAtlas(); } } void initAtlas() { IBinder binder = ServiceManager.getService("assetatlas"); if (binder == null) return; IAssetAtlas atlas = IAssetAtlas.Stub.asInterface(binder); try { if (atlas.isCompatible(android.os.Process.myPpid())) { GraphicBuffer buffer = atlas.getBuffer(); if (buffer != null) { long[] map = atlas.getMap(); if (map != null) { GLES20Canvas.initAtlas(buffer, map); } // If IAssetAtlas is not the same class as the IBinder // we are using a remote service and we can safely // destroy the graphic buffer if (atlas.getClass() != binder.getClass()) { buffer.destroy(); } } } } catch (RemoteException e) { Log.w(LOG_TAG, "Could not acquire atlas", e); } } boolean canDraw() { return mGl != null && mCanvas != null && mGlCanvas != null; } int onPreDraw(Rect dirty) { return mGlCanvas.onPreDraw(dirty); } void onPostDraw() { mGlCanvas.onPostDraw(); } void drawProfileData(View.AttachInfo attachInfo) { if (mDebugDataProvider != null) { final GraphDataProvider provider = mDebugDataProvider; initProfileDrawData(attachInfo, provider); final int height = provider.getVerticalUnitSize(); final int margin = provider.getHorizontaUnitMargin(); final int width = provider.getHorizontalUnitSize(); int x = 0; int count = 0; int current = 0; final float[] data = provider.getData(); final int elementCount = provider.getElementCount(); final int graphType = provider.getGraphType(); int totalCount = provider.getFrameCount() * elementCount; if (graphType == GraphDataProvider.GRAPH_TYPE_LINES) { totalCount -= elementCount; } for (int i = 0; i < totalCount; i += elementCount) { if (data[i] < 0.0f) break; int index = count * 4; if (i == provider.getCurrentFrame() * elementCount) current = index; x += margin; int x2 = x + width; int y2 = mHeight; int y1 = (int) (y2 - data[i] * height); switch (graphType) { case GraphDataProvider.GRAPH_TYPE_BARS: { for (int j = 0; j < elementCount; j++) { //noinspection MismatchedReadAndWriteOfArray final float[] r = mProfileShapes[j]; r[index] = x; r[index + 1] = y1; r[index + 2] = x2; r[index + 3] = y2; y2 = y1; if (j < elementCount - 1) { y1 = (int) (y2 - data[i + j + 1] * height); } } } break; case GraphDataProvider.GRAPH_TYPE_LINES: { for (int j = 0; j < elementCount; j++) { //noinspection MismatchedReadAndWriteOfArray final float[] r = mProfileShapes[j]; r[index] = (x + x2) * 0.5f; r[index + 1] = index == 0 ? y1 : r[index - 1]; r[index + 2] = r[index] + width; r[index + 3] = y1; y2 = y1; if (j < elementCount - 1) { y1 = (int) (y2 - data[i + j + 1] * height); } } } break; } x += width; count++; } x += margin; drawGraph(graphType, count); drawCurrentFrame(graphType, current); drawThreshold(x, height); } } private void drawGraph(int graphType, int count) { for (int i = 0; i < mProfileShapes.length; i++) { mDebugDataProvider.setupGraphPaint(mProfilePaint, i); switch (graphType) { case GraphDataProvider.GRAPH_TYPE_BARS: mGlCanvas.drawRects(mProfileShapes[i], count * 4, mProfilePaint); break; case GraphDataProvider.GRAPH_TYPE_LINES: mGlCanvas.drawLines(mProfileShapes[i], 0, count * 4, mProfilePaint); break; } } } private void drawCurrentFrame(int graphType, int index) { if (index >= 0) { mDebugDataProvider.setupCurrentFramePaint(mProfilePaint); switch (graphType) { case GraphDataProvider.GRAPH_TYPE_BARS: mGlCanvas.drawRect(mProfileShapes[2][index], mProfileShapes[2][index + 1], mProfileShapes[2][index + 2], mProfileShapes[0][index + 3], mProfilePaint); break; case GraphDataProvider.GRAPH_TYPE_LINES: mGlCanvas.drawLine(mProfileShapes[2][index], mProfileShapes[2][index + 1], mProfileShapes[2][index], mHeight, mProfilePaint); break; } } } private void drawThreshold(int x, int height) { float threshold = mDebugDataProvider.getThreshold(); if (threshold > 0.0f) { mDebugDataProvider.setupThresholdPaint(mProfilePaint); int y = (int) (mHeight - threshold * height); mGlCanvas.drawLine(0.0f, y, x, y, mProfilePaint); } } private void initProfileDrawData(View.AttachInfo attachInfo, GraphDataProvider provider) { if (mProfileShapes == null) { final int elementCount = provider.getElementCount(); final int frameCount = provider.getFrameCount(); mProfileShapes = new float[elementCount][]; for (int i = 0; i < elementCount; i++) { mProfileShapes[i] = new float[frameCount * 4]; } mProfilePaint = new Paint(); } mProfilePaint.reset(); if (provider.getGraphType() == GraphDataProvider.GRAPH_TYPE_LINES) { mProfilePaint.setAntiAlias(true); } if (mDisplayMetrics == null) { mDisplayMetrics = new DisplayMetrics(); } attachInfo.mDisplay.getMetrics(mDisplayMetrics); provider.prepare(mDisplayMetrics); } @Override void destroy(boolean full) { try { if (full && mCanvas != null) { mCanvas = null; } if (!isEnabled() || mDestroyed) { setEnabled(false); return; } destroySurface(); setEnabled(false); mDestroyed = true; mGl = null; } finally { if (full && mGlCanvas != null) { mGlCanvas = null; } } } @Override void pushLayerUpdate(HardwareLayer layer) { mGlCanvas.pushLayerUpdate(layer); } @Override void flushLayerUpdates() { if (validate()) { flushLayerChanges(); mGlCanvas.flushLayerUpdates(); } } @Override HardwareLayer createTextureLayer() { validate(); return HardwareLayer.createTextureLayer(this); } @Override public HardwareLayer createDisplayListLayer(int width, int height) { validate(); return HardwareLayer.createDisplayListLayer(this, width, height); } @Override void onLayerCreated(HardwareLayer hardwareLayer) { mAttachedLayers.add(hardwareLayer); } boolean hasContext() { return sEgl != null && mEglContext != null && mEglContext.equals(sEgl.eglGetCurrentContext()); } @Override void onLayerDestroyed(HardwareLayer layer) { if (mGlCanvas != null) { mGlCanvas.cancelLayerUpdate(layer); } if (hasContext()) { long backingLayer = layer.detachBackingLayer(); nDestroyLayer(backingLayer); } mAttachedLayers.remove(layer); } @Override public SurfaceTexture createSurfaceTexture(HardwareLayer layer) { return layer.createSurfaceTexture(); } @Override boolean copyLayerInto(HardwareLayer layer, Bitmap bitmap) { if (!validate()) { throw new IllegalStateException("Could not acquire hardware rendering context"); } layer.flushChanges(); return GLES20Canvas.nCopyLayer(layer.getLayer(), bitmap.mNativeBitmap); } @Override boolean safelyRun(Runnable action) { boolean needsContext = !isEnabled() || checkRenderContext() == SURFACE_STATE_ERROR; if (needsContext) { GLRendererEglContext managedContext = (GLRendererEglContext) sEglContextStorage.get(); if (managedContext == null) return false; usePbufferSurface(managedContext.getContext()); } try { action.run(); } finally { if (needsContext) { sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); } } return true; } @Override void invokeFunctor(long functor, boolean waitForCompletion) { boolean needsContext = !isEnabled() || checkRenderContext() == SURFACE_STATE_ERROR; boolean hasContext = !needsContext; if (needsContext) { GLRendererEglContext managedContext = (GLRendererEglContext) sEglContextStorage.get(); if (managedContext != null) { usePbufferSurface(managedContext.getContext()); hasContext = true; } } try { nInvokeFunctor(functor, hasContext); } finally { if (needsContext) { sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); } } } private static native void nInvokeFunctor(long functor, boolean hasContext); @Override void destroyHardwareResources(final View view) { if (view != null) { safelyRun(new Runnable() { @Override public void run() { if (mCanvas != null) { mCanvas.clearLayerUpdates(); } destroyResources(view); GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_LAYERS); } }); } } private static void destroyResources(View view) { view.destroyHardwareResources(); if (view instanceof ViewGroup) { ViewGroup group = (ViewGroup) view; int count = group.getChildCount(); for (int i = 0; i < count; i++) { destroyResources(group.getChildAt(i)); } } } static void startTrimMemory(int level) { if (sEgl == null || sEglConfig == null) return; GLRendererEglContext managedContext = (GLRendererEglContext) sEglContextStorage.get(); // We do not have OpenGL objects if (managedContext == null) { return; } else { usePbufferSurface(managedContext.getContext()); } if (level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE) { GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_FULL); } else if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) { GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_MODERATE); } } static void endTrimMemory() { if (sEgl != null && sEglDisplay != null) { sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); } } private static void usePbufferSurface(EGLContext eglContext) { synchronized (sPbufferLock) { // Create a temporary 1x1 pbuffer so we have a context // to clear our OpenGL objects if (sPbuffer == null) { sPbuffer = sEgl.eglCreatePbufferSurface(sEglDisplay, sEglConfig, new int[] { EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE }); } } sEgl.eglMakeCurrent(sEglDisplay, sPbuffer, sPbuffer, eglContext); } GLRenderer(boolean translucent) { mTranslucent = translucent; loadSystemProperties(); } @Override void setOpaque(boolean opaque) { // Not supported } @Override boolean loadSystemProperties() { boolean value; boolean changed = false; String profiling = SystemProperties.get(PROFILE_PROPERTY); int graphType = search(VISUALIZERS, profiling); value = graphType >= 0; if (graphType != mProfileVisualizerType) { changed = true; mProfileVisualizerType = graphType; mProfileShapes = null; mProfilePaint = null; if (value) { mDebugDataProvider = new GraphDataProvider(graphType); } else { mDebugDataProvider = null; } } // If on-screen profiling is not enabled, we need to check whether // console profiling only is enabled if (!value) { value = Boolean.parseBoolean(profiling); } if (value != mProfileEnabled) { changed = true; mProfileEnabled = value; if (mProfileEnabled) { Log.d(LOG_TAG, "Profiling hardware renderer"); int maxProfileFrames = SystemProperties.getInt(PROFILE_MAXFRAMES_PROPERTY, PROFILE_MAX_FRAMES); mProfileData = new float[maxProfileFrames * PROFILE_FRAME_DATA_COUNT]; for (int i = 0; i < mProfileData.length; i += PROFILE_FRAME_DATA_COUNT) { mProfileData[i] = mProfileData[i + 1] = mProfileData[i + 2] = -1; } mProfileLock = new ReentrantLock(); } else { mProfileData = null; mProfileLock = null; mProfileVisualizerType = -1; } mProfileCurrentFrame = -PROFILE_FRAME_DATA_COUNT; } value = SystemProperties.getBoolean(DEBUG_DIRTY_REGIONS_PROPERTY, false); if (value != mDebugDirtyRegions) { changed = true; mDebugDirtyRegions = value; if (mDebugDirtyRegions) { Log.d(LOG_TAG, "Debugging dirty regions"); } } String overdraw = SystemProperties.get(HardwareRenderer.DEBUG_OVERDRAW_PROPERTY); int debugOverdraw = search(OVERDRAW, overdraw); if (debugOverdraw != mDebugOverdraw) { changed = true; mDebugOverdraw = debugOverdraw; } if (loadProperties()) { changed = true; } return changed; } private static int search(String[] values, String value) { for (int i = 0; i < values.length; i++) { if (values[i].equals(value)) return i; } return -1; } @Override void dumpGfxInfo(PrintWriter pw, FileDescriptor fd) { if (mProfileEnabled) { pw.printf("\n\tDraw\tProcess\tExecute\n"); mProfileLock.lock(); try { for (int i = 0; i < mProfileData.length; i += PROFILE_FRAME_DATA_COUNT) { if (mProfileData[i] < 0) { break; } pw.printf("\t%3.2f\t%3.2f\t%3.2f\n", mProfileData[i], mProfileData[i + 1], mProfileData[i + 2]); mProfileData[i] = mProfileData[i + 1] = mProfileData[i + 2] = -1; } mProfileCurrentFrame = mProfileData.length; } finally { mProfileLock.unlock(); } } } /** * Indicates whether this renderer instance can track and update dirty regions. */ boolean hasDirtyRegions() { return mDirtyRegionsEnabled; } /** * Checks for OpenGL errors. If an error has occured, {@link #destroy(boolean)} * is invoked and the requested flag is turned off. The error code is * also logged as a warning. */ void checkEglErrors() { if (isEnabled()) { checkEglErrorsForced(); } } private void checkEglErrorsForced() { int error = sEgl.eglGetError(); if (error != EGL_SUCCESS) { // something bad has happened revert to // normal rendering. Log.w(LOG_TAG, "EGL error: " + GLUtils.getEGLErrorString(error)); fallback(error != EGL11.EGL_CONTEXT_LOST); } } private void fallback(boolean fallback) { destroy(true); if (fallback) { // we'll try again if it was context lost setRequested(false); Log.w(LOG_TAG, "Mountain View, we've had a problem here. " + "Switching back to software rendering."); } } @Override boolean initialize(Surface surface) throws OutOfResourcesException { if (isRequested() && !isEnabled()) { boolean contextCreated = initializeEgl(); mGl = createEglSurface(surface); mDestroyed = false; if (mGl != null) { int err = sEgl.eglGetError(); if (err != EGL_SUCCESS) { destroy(true); setRequested(false); } else { if (mCanvas == null) { mCanvas = createCanvas(); } setEnabled(true); if (contextCreated) { initAtlas(); } } return mCanvas != null; } } return false; } @Override void updateSurface(Surface surface) throws OutOfResourcesException { if (isRequested() && isEnabled()) { createEglSurface(surface); } } @Override void pauseSurface(Surface surface) { // No-op } boolean initializeEgl() { synchronized (sEglLock) { if (sEgl == null && sEglConfig == null) { sEgl = (EGL10) EGLContext.getEGL(); // Get to the default display. sEglDisplay = sEgl.eglGetDisplay(EGL_DEFAULT_DISPLAY); if (sEglDisplay == EGL_NO_DISPLAY) { throw new RuntimeException("eglGetDisplay failed " + GLUtils.getEGLErrorString(sEgl.eglGetError())); } // We can now initialize EGL for that display int[] version = new int[2]; if (!sEgl.eglInitialize(sEglDisplay, version)) { throw new RuntimeException("eglInitialize failed " + GLUtils.getEGLErrorString(sEgl.eglGetError())); } checkEglErrorsForced(); sEglConfig = loadEglConfig(); } } ManagedEGLContext managedContext = sEglContextStorage.get(); mEglContext = managedContext != null ? managedContext.getContext() : null; mEglThread = Thread.currentThread(); if (mEglContext == null) { mEglContext = createContext(sEgl, sEglDisplay, sEglConfig); sEglContextStorage.set(createManagedContext(mEglContext)); return true; } return false; } private EGLConfig loadEglConfig() { EGLConfig eglConfig = chooseEglConfig(); if (eglConfig == null) { // We tried to use EGL_SWAP_BEHAVIOR_PRESERVED_BIT, try again without if (sDirtyRegions) { sDirtyRegions = false; eglConfig = chooseEglConfig(); if (eglConfig == null) { throw new RuntimeException("eglConfig not initialized"); } } else { throw new RuntimeException("eglConfig not initialized"); } } return eglConfig; } private EGLConfig chooseEglConfig() { EGLConfig[] configs = new EGLConfig[1]; int[] configsCount = new int[1]; int[] configSpec = getConfig(sDirtyRegions); // Debug final String debug = SystemProperties.get(PRINT_CONFIG_PROPERTY, ""); if ("all".equalsIgnoreCase(debug)) { sEgl.eglChooseConfig(sEglDisplay, configSpec, null, 0, configsCount); EGLConfig[] debugConfigs = new EGLConfig[configsCount[0]]; sEgl.eglChooseConfig(sEglDisplay, configSpec, debugConfigs, configsCount[0], configsCount); for (EGLConfig config : debugConfigs) { printConfig(config); } } if (!sEgl.eglChooseConfig(sEglDisplay, configSpec, configs, 1, configsCount)) { throw new IllegalArgumentException("eglChooseConfig failed " + GLUtils.getEGLErrorString(sEgl.eglGetError())); } else if (configsCount[0] > 0) { if ("choice".equalsIgnoreCase(debug)) { printConfig(configs[0]); } return configs[0]; } return null; } private static void printConfig(EGLConfig config) { int[] value = new int[1]; Log.d(LOG_TAG, "EGL configuration " + config + ":"); sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_RED_SIZE, value); Log.d(LOG_TAG, " RED_SIZE = " + value[0]); sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_GREEN_SIZE, value); Log.d(LOG_TAG, " GREEN_SIZE = " + value[0]); sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_BLUE_SIZE, value); Log.d(LOG_TAG, " BLUE_SIZE = " + value[0]); sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_ALPHA_SIZE, value); Log.d(LOG_TAG, " ALPHA_SIZE = " + value[0]); sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_DEPTH_SIZE, value); Log.d(LOG_TAG, " DEPTH_SIZE = " + value[0]); sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_STENCIL_SIZE, value); Log.d(LOG_TAG, " STENCIL_SIZE = " + value[0]); sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_SAMPLE_BUFFERS, value); Log.d(LOG_TAG, " SAMPLE_BUFFERS = " + value[0]); sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_SAMPLES, value); Log.d(LOG_TAG, " SAMPLES = " + value[0]); sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_SURFACE_TYPE, value); Log.d(LOG_TAG, " SURFACE_TYPE = 0x" + Integer.toHexString(value[0])); sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_CONFIG_CAVEAT, value); Log.d(LOG_TAG, " CONFIG_CAVEAT = 0x" + Integer.toHexString(value[0])); } GL createEglSurface(Surface surface) throws OutOfResourcesException { // Check preconditions. if (sEgl == null) { throw new RuntimeException("egl not initialized"); } if (sEglDisplay == null) { throw new RuntimeException("eglDisplay not initialized"); } if (sEglConfig == null) { throw new RuntimeException("eglConfig not initialized"); } if (Thread.currentThread() != mEglThread) { throw new IllegalStateException("HardwareRenderer cannot be used " + "from multiple threads"); } // In case we need to destroy an existing surface destroySurface(); // Create an EGL surface we can render into. if (!createSurface(surface)) { return null; } initCaches(); return mEglContext.getGL(); } private void enableDirtyRegions() { // If mDirtyRegions is set, this means we have an EGL configuration // with EGL_SWAP_BEHAVIOR_PRESERVED_BIT set if (sDirtyRegions) { if (!(mDirtyRegionsEnabled = preserveBackBuffer())) { Log.w(LOG_TAG, "Backbuffer cannot be preserved"); } } else if (sDirtyRegionsRequested) { // If mDirtyRegions is not set, our EGL configuration does not // have EGL_SWAP_BEHAVIOR_PRESERVED_BIT; however, the default // swap behavior might be EGL_BUFFER_PRESERVED, which means we // want to set mDirtyRegions. We try to do this only if dirty // regions were initially requested as part of the device // configuration (see RENDER_DIRTY_REGIONS) mDirtyRegionsEnabled = isBackBufferPreserved(); } } EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) { final int[] attribs = { EGL14.EGL_CONTEXT_CLIENT_VERSION, GL_VERSION, EGL_NONE }; EGLContext context = egl.eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT, attribs); if (context == null || context == EGL_NO_CONTEXT) { //noinspection ConstantConditions throw new IllegalStateException( "Could not create an EGL context. eglCreateContext failed with error: " + GLUtils.getEGLErrorString(sEgl.eglGetError())); } return context; } void destroySurface() { if (mEglSurface != null && mEglSurface != EGL_NO_SURFACE) { if (mEglSurface.equals(sEgl.eglGetCurrentSurface(EGL_DRAW))) { sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); } sEgl.eglDestroySurface(sEglDisplay, mEglSurface); mEglSurface = null; } } @Override void invalidate(Surface surface) { // Cancels any existing buffer to ensure we'll get a buffer // of the right size before we call eglSwapBuffers sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); if (mEglSurface != null && mEglSurface != EGL_NO_SURFACE) { sEgl.eglDestroySurface(sEglDisplay, mEglSurface); mEglSurface = null; setEnabled(false); } if (surface.isValid()) { if (!createSurface(surface)) { return; } mUpdateDirtyRegions = true; if (mCanvas != null) { setEnabled(true); } } } private boolean createSurface(Surface surface) { mEglSurface = sEgl.eglCreateWindowSurface(sEglDisplay, sEglConfig, surface, null); if (mEglSurface == null || mEglSurface == EGL_NO_SURFACE) { int error = sEgl.eglGetError(); if (error == EGL_BAD_NATIVE_WINDOW) { Log.e(LOG_TAG, "createWindowSurface returned EGL_BAD_NATIVE_WINDOW."); return false; } throw new RuntimeException("createWindowSurface failed " + GLUtils.getEGLErrorString(error)); } if (!sEgl.eglMakeCurrent(sEglDisplay, mEglSurface, mEglSurface, mEglContext)) { throw new IllegalStateException("eglMakeCurrent failed " + GLUtils.getEGLErrorString(sEgl.eglGetError())); } enableDirtyRegions(); return true; } boolean validate() { return checkRenderContext() != SURFACE_STATE_ERROR; } @Override void setup(int width, int height, float lightX, float lightY, float lightZ, float lightRadius) { if (validate()) { mCanvas.setViewport(width, height); mCanvas.initializeLight(lightX, lightY, lightZ, lightRadius); mWidth = width; mHeight = height; } } @Override int getWidth() { return mWidth; } @Override int getHeight() { return mHeight; } @Override void setName(String name) { mName = name; } @Override void draw(View view, View.AttachInfo attachInfo, HardwareDrawCallbacks callbacks, Rect dirty) { if (canDraw()) { if (!hasDirtyRegions()) { dirty = null; } attachInfo.mIgnoreDirtyState = true; attachInfo.mDrawingTime = SystemClock.uptimeMillis(); view.mPrivateFlags |= View.PFLAG_DRAWN; // We are already on the correct thread final int surfaceState = checkRenderContextUnsafe(); if (surfaceState != SURFACE_STATE_ERROR) { HardwareCanvas canvas = mCanvas; if (mProfileEnabled) { mProfileLock.lock(); } dirty = beginFrame(canvas, dirty, surfaceState); RenderNode displayList = buildDisplayList(view, canvas); flushLayerChanges(); // buildDisplayList() calls into user code which can cause // an eglMakeCurrent to happen with a different surface/context. // We must therefore check again here. if (checkRenderContextUnsafe() == SURFACE_STATE_ERROR) { return; } int saveCount = 0; int status = RenderNode.STATUS_DONE; long start = getSystemTime(); try { status = prepareFrame(dirty); saveCount = canvas.save(); callbacks.onHardwarePreDraw(canvas); if (displayList != null) { status |= drawDisplayList(canvas, displayList, status); } else { // Shouldn't reach here view.draw(canvas); } } catch (Exception e) { Log.e(LOG_TAG, "An error has occurred while drawing:", e); } finally { callbacks.onHardwarePostDraw(canvas); canvas.restoreToCount(saveCount); view.mRecreateDisplayList = false; mDrawDelta = getSystemTime() - start; if (mDrawDelta > 0) { mFrameCount++; debugDirtyRegions(dirty, canvas); drawProfileData(attachInfo); } } onPostDraw(); swapBuffers(status); if (mProfileEnabled) { mProfileLock.unlock(); } attachInfo.mIgnoreDirtyState = false; } } } private void flushLayerChanges() { // Loop through and apply any pending layer changes for (int i = 0; i < mAttachedLayers.size(); i++) { HardwareLayer layer = mAttachedLayers.get(i); layer.flushChanges(); if (!layer.isValid()) { // The layer was removed from mAttachedLayers, rewind i by 1 // Note that this shouldn't actually happen as View.getHardwareLayer() // is already flushing for error checking reasons i--; } } } @Override void fence() { // Everything is immediate, so this is a no-op } private RenderNode buildDisplayList(View view, HardwareCanvas canvas) { view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED) == View.PFLAG_INVALIDATED; view.mPrivateFlags &= ~View.PFLAG_INVALIDATED; long buildDisplayListStartTime = startBuildDisplayListProfiling(); canvas.clearLayerUpdates(); Trace.traceBegin(Trace.TRACE_TAG_VIEW, "getDisplayList"); RenderNode renderNode = view.getDisplayList(); Trace.traceEnd(Trace.TRACE_TAG_VIEW); endBuildDisplayListProfiling(buildDisplayListStartTime); return renderNode; } private Rect beginFrame(HardwareCanvas canvas, Rect dirty, int surfaceState) { // We had to change the current surface and/or context, redraw everything if (surfaceState == SURFACE_STATE_UPDATED) { dirty = null; beginFrame(null); } else { int[] size = mSurfaceSize; beginFrame(size); if (size[1] != mHeight || size[0] != mWidth) { mWidth = size[0]; mHeight = size[1]; canvas.setViewport(mWidth, mHeight); dirty = null; } } if (mDebugDataProvider != null) dirty = null; return dirty; } private long startBuildDisplayListProfiling() { if (mProfileEnabled) { mProfileCurrentFrame += PROFILE_FRAME_DATA_COUNT; if (mProfileCurrentFrame >= mProfileData.length) { mProfileCurrentFrame = 0; } return System.nanoTime(); } return 0; } private void endBuildDisplayListProfiling(long getDisplayListStartTime) { if (mProfileEnabled) { long now = System.nanoTime(); float total = (now - getDisplayListStartTime) * 0.000001f; //noinspection PointlessArithmeticExpression mProfileData[mProfileCurrentFrame] = total; } } private int prepareFrame(Rect dirty) { int status; Trace.traceBegin(Trace.TRACE_TAG_VIEW, "prepareFrame"); try { status = onPreDraw(dirty); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } return status; } private int drawDisplayList(HardwareCanvas canvas, RenderNode displayList, int status) { long drawDisplayListStartTime = 0; if (mProfileEnabled) { drawDisplayListStartTime = System.nanoTime(); } Trace.traceBegin(Trace.TRACE_TAG_VIEW, "drawDisplayList"); nPrepareTree(displayList.getNativeDisplayList()); try { status |= canvas.drawDisplayList(displayList, mRedrawClip, RenderNode.FLAG_CLIP_CHILDREN); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } if (mProfileEnabled) { long now = System.nanoTime(); float total = (now - drawDisplayListStartTime) * 0.000001f; mProfileData[mProfileCurrentFrame + 1] = total; } return status; } private void swapBuffers(int status) { if ((status & RenderNode.STATUS_DREW) == RenderNode.STATUS_DREW) { long eglSwapBuffersStartTime = 0; if (mProfileEnabled) { eglSwapBuffersStartTime = System.nanoTime(); } sEgl.eglSwapBuffers(sEglDisplay, mEglSurface); if (mProfileEnabled) { long now = System.nanoTime(); float total = (now - eglSwapBuffersStartTime) * 0.000001f; mProfileData[mProfileCurrentFrame + 2] = total; } checkEglErrors(); } } private void debugDirtyRegions(Rect dirty, HardwareCanvas canvas) { if (mDebugDirtyRegions) { if (mDebugPaint == null) { mDebugPaint = new Paint(); mDebugPaint.setColor(0x7fff0000); } if (dirty != null && (mFrameCount & 1) == 0) { canvas.drawRect(dirty, mDebugPaint); } } } /** * Ensures the current EGL context and surface are the ones we expect. * This method throws an IllegalStateException if invoked from a thread * that did not initialize EGL. * * @return {@link #SURFACE_STATE_ERROR} if the correct EGL context cannot be made current, * {@link #SURFACE_STATE_UPDATED} if the EGL context was changed or * {@link #SURFACE_STATE_SUCCESS} if the EGL context was the correct one * * @see #checkRenderContextUnsafe() */ int checkRenderContext() { if (mEglThread != Thread.currentThread()) { throw new IllegalStateException("Hardware acceleration can only be used with a " + "single UI thread.\nOriginal thread: " + mEglThread + "\n" + "Current thread: " + Thread.currentThread()); } return checkRenderContextUnsafe(); } /** * Ensures the current EGL context and surface are the ones we expect. * This method does not check the current thread. * * @return {@link #SURFACE_STATE_ERROR} if the correct EGL context cannot be made current, * {@link #SURFACE_STATE_UPDATED} if the EGL context was changed or * {@link #SURFACE_STATE_SUCCESS} if the EGL context was the correct one * * @see #checkRenderContext() */ private int checkRenderContextUnsafe() { if (!mEglSurface.equals(sEgl.eglGetCurrentSurface(EGL_DRAW)) || !mEglContext.equals(sEgl.eglGetCurrentContext())) { if (!sEgl.eglMakeCurrent(sEglDisplay, mEglSurface, mEglSurface, mEglContext)) { Log.e(LOG_TAG, "eglMakeCurrent failed " + GLUtils.getEGLErrorString(sEgl.eglGetError())); fallback(true); return SURFACE_STATE_ERROR; } else { if (mUpdateDirtyRegions) { enableDirtyRegions(); mUpdateDirtyRegions = false; } return SURFACE_STATE_UPDATED; } } return SURFACE_STATE_SUCCESS; } private static int dpToPx(int dp, float density) { return (int) (dp * density + 0.5f); } static native boolean loadProperties(); static native void setupShadersDiskCache(String cacheFile); /** * Notifies EGL that the frame is about to be rendered. * @param size */ static native void beginFrame(int[] size); /** * Returns the current system time according to the renderer. * This method is used for debugging only and should not be used * as a clock. */ static native long getSystemTime(); /** * Preserves the back buffer of the current surface after a buffer swap. * Calling this method sets the EGL_SWAP_BEHAVIOR attribute of the current * surface to EGL_BUFFER_PRESERVED. Calling this method requires an EGL * config that supports EGL_SWAP_BEHAVIOR_PRESERVED_BIT. * * @return True if the swap behavior was successfully changed, * false otherwise. */ static native boolean preserveBackBuffer(); /** * Indicates whether the current surface preserves its back buffer * after a buffer swap. * * @return True, if the surface's EGL_SWAP_BEHAVIOR is EGL_BUFFER_PRESERVED, * false otherwise */ static native boolean isBackBufferPreserved(); static native void nDestroyLayer(long layerPtr); private static native void nPrepareTree(long displayListPtr); class GraphDataProvider { /** * Draws the graph as bars. Frame elements are stacked on top of * each other. */ public static final int GRAPH_TYPE_BARS = 0; /** * Draws the graph as lines. The number of series drawn corresponds * to the number of elements. */ public static final int GRAPH_TYPE_LINES = 1; private final int mGraphType; private int mVerticalUnit; private int mHorizontalUnit; private int mHorizontalMargin; private int mThresholdStroke; public GraphDataProvider(int graphType) { mGraphType = graphType; } void prepare(DisplayMetrics metrics) { final float density = metrics.density; mVerticalUnit = dpToPx(PROFILE_DRAW_DP_PER_MS, density); mHorizontalUnit = dpToPx(PROFILE_DRAW_WIDTH, density); mHorizontalMargin = dpToPx(PROFILE_DRAW_MARGIN, density); mThresholdStroke = dpToPx(PROFILE_DRAW_THRESHOLD_STROKE_WIDTH, density); } int getGraphType() { return mGraphType; } int getVerticalUnitSize() { return mVerticalUnit; } int getHorizontalUnitSize() { return mHorizontalUnit; } int getHorizontaUnitMargin() { return mHorizontalMargin; } float[] getData() { return mProfileData; } float getThreshold() { return 16; } int getFrameCount() { return mProfileData.length / PROFILE_FRAME_DATA_COUNT; } int getElementCount() { return PROFILE_FRAME_DATA_COUNT; } int getCurrentFrame() { return mProfileCurrentFrame / PROFILE_FRAME_DATA_COUNT; } void setupGraphPaint(Paint paint, int elementIndex) { paint.setColor(PROFILE_DRAW_COLORS[elementIndex]); if (mGraphType == GRAPH_TYPE_LINES) paint.setStrokeWidth(mThresholdStroke); } void setupThresholdPaint(Paint paint) { paint.setColor(PROFILE_DRAW_THRESHOLD_COLOR); paint.setStrokeWidth(mThresholdStroke); } void setupCurrentFramePaint(Paint paint) { paint.setColor(PROFILE_DRAW_CURRENT_FRAME_COLOR); if (mGraphType == GRAPH_TYPE_LINES) paint.setStrokeWidth(mThresholdStroke); } } }