/* * 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 android.content.ComponentCallbacks2; import android.graphics.Color; 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 javax.microedition.khronos.egl.EGL10; import javax.microedition.khronos.egl.EGL11; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.egl.EGLContext; import javax.microedition.khronos.egl.EGLDisplay; import javax.microedition.khronos.egl.EGLSurface; import javax.microedition.khronos.opengles.GL; import java.io.File; import java.io.PrintWriter; import java.util.concurrent.locks.ReentrantLock; import static javax.microedition.khronos.egl.EGL10.*; /** * Interface for rendering a view hierarchy using hardware acceleration. * * @hide */ public abstract class HardwareRenderer { static final String LOG_TAG = "HardwareRenderer"; /** * Name of the file that holds the shaders cache. */ private static final String CACHE_PATH_SHADERS = "com.android.opengl.shaders_cache"; /** * Turn on to only refresh the parts of the screen that need updating. * When turned on the property defined by {@link #RENDER_DIRTY_REGIONS_PROPERTY} * must also have the value "true". */ static final boolean RENDER_DIRTY_REGIONS = true; /** * System property used to enable or disable dirty regions invalidation. * This property is only queried if {@link #RENDER_DIRTY_REGIONS} is true. * The default value of this property is assumed to be true. * * Possible values: * "true", to enable partial invalidates * "false", to disable partial invalidates */ static final String RENDER_DIRTY_REGIONS_PROPERTY = "debug.hwui.render_dirty_regions"; /** * System property used to enable or disable hardware rendering profiling. * The default value of this property is assumed to be false. * * When profiling is enabled, the adb shell dumpsys gfxinfo command will * output extra information about the time taken to execute by the last * frames. * * Possible values: * "true", to enable profiling * "visual_bars", to enable profiling and visualize the results on screen * "visual_lines", to enable profiling and visualize the results on screen * "false", to disable profiling * * @see #PROFILE_PROPERTY_VISUALIZE_BARS * @see #PROFILE_PROPERTY_VISUALIZE_LINES * * @hide */ public static final String PROFILE_PROPERTY = "debug.hwui.profile"; /** * Value for {@link #PROFILE_PROPERTY}. When the property is set to this * value, profiling data will be visualized on screen as a bar chart. * * @hide */ public static final String PROFILE_PROPERTY_VISUALIZE_BARS = "visual_bars"; /** * Value for {@link #PROFILE_PROPERTY}. When the property is set to this * value, profiling data will be visualized on screen as a line chart. * * @hide */ public static final String PROFILE_PROPERTY_VISUALIZE_LINES = "visual_lines"; /** * System property used to specify the number of frames to be used * when doing hardware rendering profiling. * The default value of this property is #PROFILE_MAX_FRAMES. * * When profiling is enabled, the adb shell dumpsys gfxinfo command will * output extra information about the time taken to execute by the last * frames. * * Possible values: * "60", to set the limit of frames to 60 */ static final String PROFILE_MAXFRAMES_PROPERTY = "debug.hwui.profile.maxframes"; /** * System property used to debug EGL configuration choice. * * Possible values: * "choice", print the chosen configuration only * "all", print all possible configurations */ static final String PRINT_CONFIG_PROPERTY = "debug.hwui.print_config"; /** * Turn on to draw dirty regions every other frame. * * Possible values: * "true", to enable dirty regions debugging * "false", to disable dirty regions debugging * * @hide */ public static final String DEBUG_DIRTY_REGIONS_PROPERTY = "debug.hwui.show_dirty_regions"; /** * Turn on to flash hardware layers when they update. * * Possible values: * "true", to enable hardware layers updates debugging * "false", to disable hardware layers updates debugging * * @hide */ public static final String DEBUG_SHOW_LAYERS_UPDATES_PROPERTY = "debug.hwui.show_layers_updates"; /** * Controls overdraw debugging. * * Possible values: * "false", to disable overdraw debugging * "show", to show overdraw areas on screen * "count", to display an overdraw counter * * @hide */ public static final String DEBUG_OVERDRAW_PROPERTY = "debug.hwui.overdraw"; /** * Value for {@link #DEBUG_OVERDRAW_PROPERTY}. When the property is set to this * value, overdraw will be shown on screen by coloring pixels. * * @hide */ public static final String OVERDRAW_PROPERTY_SHOW = "show"; /** * Value for {@link #DEBUG_OVERDRAW_PROPERTY}. When the property is set to this * value, an overdraw counter will be shown on screen. * * @hide */ public static final String OVERDRAW_PROPERTY_COUNT = "count"; /** * Turn on to debug non-rectangular clip operations. * * Possible values: * "hide", to disable this debug mode * "highlight", highlight drawing commands tested against a non-rectangular clip * "stencil", renders the clip region on screen when set * * @hide */ public static final String DEBUG_SHOW_NON_RECTANGULAR_CLIP_PROPERTY = "debug.hwui.show_non_rect_clip"; /** * A process can set this flag to false to prevent the use of hardware * rendering. * * @hide */ public static boolean sRendererDisabled = false; /** * Further hardware renderer disabling for the system process. * * @hide */ public static boolean sSystemRendererDisabled = false; /** * 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 boolean mEnabled; private boolean mRequested = true; /** * Invoke this method to disable hardware rendering in the current process. * * @hide */ public static void disable(boolean system) { sRendererDisabled = true; if (system) { sSystemRendererDisabled = true; } } /** * Indicates whether hardware acceleration is available under any form for * the view hierarchy. * * @return True if the view hierarchy can potentially be hardware accelerated, * false otherwise */ public static boolean isAvailable() { return GLES20Canvas.isAvailable(); } /** * Destroys the hardware rendering context. * * @param full If true, destroys all associated resources. */ abstract void destroy(boolean full); /** * Initializes the hardware renderer for the specified surface. * * @param surface The surface to hardware accelerate * * @return True if the initialization was successful, false otherwise. */ abstract boolean initialize(Surface surface) throws OutOfResourcesException; /** * Updates the hardware renderer for the specified surface. * * @param surface The surface to hardware accelerate */ abstract void updateSurface(Surface surface) throws OutOfResourcesException; /** * Destroys the layers used by the specified view hierarchy. * * @param view The root of the view hierarchy */ abstract void destroyLayers(View view); /** * Destroys all hardware rendering resources associated with the specified * view hierarchy. * * @param view The root of the view hierarchy */ abstract void destroyHardwareResources(View view); /** * This method should be invoked whenever the current hardware renderer * context should be reset. * * @param surface The surface to hardware accelerate */ abstract void invalidate(Surface surface); /** * This method should be invoked to ensure the hardware renderer is in * valid state (for instance, to ensure the correct EGL context is bound * to the current thread.) * * @return true if the renderer is now valid, false otherwise */ abstract boolean validate(); /** * This method ensures the hardware renderer is in a valid state * before executing the specified action. * * This method will attempt to set a valid state even if the window * the renderer is attached to was destroyed. * * @return true if the action was run */ abstract boolean safelyRun(Runnable action); /** * Setup the hardware renderer for drawing. This is called whenever the * size of the target surface changes or when the surface is first created. * * @param width Width of the drawing surface. * @param height Height of the drawing surface. */ abstract void setup(int width, int height); /** * Gets the current width of the surface. This is the width that the surface * was last set to in a call to {@link #setup(int, int)}. * * @return the current width of the surface */ abstract int getWidth(); /** * Gets the current height of the surface. This is the height that the surface * was last set to in a call to {@link #setup(int, int)}. * * @return the current width of the surface */ abstract int getHeight(); /** * Gets the current canvas associated with this HardwareRenderer. * * @return the current HardwareCanvas */ abstract HardwareCanvas getCanvas(); /** * Outputs extra debugging information in the specified file descriptor. * @param pw */ abstract void dumpGfxInfo(PrintWriter pw); /** * Outputs the total number of frames rendered (used for fps calculations) * * @return the number of frames rendered */ abstract long getFrameCount(); /** * Loads system properties used by the renderer. This method is invoked * whenever system properties are modified. Implementations can use this * to trigger live updates of the renderer based on properties. * * @param surface The surface to update with the new properties. * Can be null. * * @return True if a property has changed. */ abstract boolean loadSystemProperties(Surface surface); private static native boolean nLoadProperties(); /** * Sets the directory to use as a persistent storage for hardware rendering * resources. * * @param cacheDir A directory the current process can write to * * @hide */ public static void setupDiskCache(File cacheDir) { nSetupShadersDiskCache(new File(cacheDir, CACHE_PATH_SHADERS).getAbsolutePath()); } private static native void nSetupShadersDiskCache(String cacheFile); /** * Notifies EGL that the frame is about to be rendered. * @param size */ static void beginFrame(int[] size) { nBeginFrame(size); } private static native void nBeginFrame(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 long getSystemTime() { return nGetSystemTime(); } private static native long nGetSystemTime(); /** * 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 boolean preserveBackBuffer() { return nPreserveBackBuffer(); } private static native boolean nPreserveBackBuffer(); /** * 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 boolean isBackBufferPreserved() { return nIsBackBufferPreserved(); } private static native boolean nIsBackBufferPreserved(); /** * Indicates that the specified hardware layer needs to be updated * as soon as possible. * * @param layer The hardware layer that needs an update * * @see #flushLayerUpdates() * @see #cancelLayerUpdate(HardwareLayer) */ abstract void pushLayerUpdate(HardwareLayer layer); /** * Cancels a queued layer update. If the specified layer was not * queued for update, this method has no effect. * * @param layer The layer whose update to cancel * * @see #pushLayerUpdate(HardwareLayer) */ abstract void cancelLayerUpdate(HardwareLayer layer); /** * Forces all enqueued layer updates to be executed immediately. * * @see #pushLayerUpdate(HardwareLayer) */ abstract void flushLayerUpdates(); /** * Interface used to receive callbacks whenever a view is drawn by * a hardware renderer instance. */ interface HardwareDrawCallbacks { /** * Invoked before a view is drawn by a hardware renderer. * This method can be used to apply transformations to the * canvas but no drawing command should be issued. * * @param canvas The Canvas used to render the view. */ void onHardwarePreDraw(HardwareCanvas canvas); /** * Invoked after a view is drawn by a hardware renderer. * It is safe to invoke drawing commands from this method. * * @param canvas The Canvas used to render the view. */ void onHardwarePostDraw(HardwareCanvas canvas); } /** * Draws the specified view. * * @param view The view to draw. * @param attachInfo AttachInfo tied to the specified view. * @param callbacks Callbacks invoked when drawing happens. * @param dirty The dirty rectangle to update, can be null. */ abstract void draw(View view, View.AttachInfo attachInfo, HardwareDrawCallbacks callbacks, Rect dirty); /** * Creates a new display list that can be used to record batches of * drawing operations. * * @param name The name of the display list, used for debugging purpose. May be null. * * @return A new display list. * * @hide */ public abstract DisplayList createDisplayList(String name); /** * Creates a new hardware layer. A hardware layer built by calling this * method will be treated as a texture layer, instead of as a render target. * * @param isOpaque Whether the layer should be opaque or not * * @return A hardware layer */ abstract HardwareLayer createHardwareLayer(boolean isOpaque); /** * Creates a new hardware layer. * * @param width The minimum width of the layer * @param height The minimum height of the layer * @param isOpaque Whether the layer should be opaque or not * * @return A hardware layer */ abstract HardwareLayer createHardwareLayer(int width, int height, boolean isOpaque); /** * Creates a new {@link SurfaceTexture} that can be used to render into the * specified hardware layer. * * @param layer The layer to render into using a {@link android.graphics.SurfaceTexture} * * @return A {@link SurfaceTexture} */ abstract SurfaceTexture createSurfaceTexture(HardwareLayer layer); /** * Sets the {@link android.graphics.SurfaceTexture} that will be used to * render into the specified hardware layer. * * @param layer The layer to render into using a {@link android.graphics.SurfaceTexture} * @param surfaceTexture The {@link android.graphics.SurfaceTexture} to use for the layer */ abstract void setSurfaceTexture(HardwareLayer layer, SurfaceTexture surfaceTexture); /** * Detaches the specified functor from the current functor execution queue. * * @param functor The native functor to remove from the execution queue. * * @see HardwareCanvas#callDrawGLFunction(int) * @see #attachFunctor(android.view.View.AttachInfo, int) */ abstract void detachFunctor(long functor); /** * Schedules the specified functor in the functors execution queue. * * @param attachInfo AttachInfo tied to this renderer. * @param functor The native functor to insert in the execution queue. * * @see HardwareCanvas#callDrawGLFunction(int) * @see #detachFunctor(int) * * @return true if the functor was attached successfully */ abstract boolean attachFunctor(View.AttachInfo attachInfo, long functor); /** * Initializes the hardware renderer for the specified surface and setup the * renderer for drawing, if needed. This is invoked when the ViewAncestor has * potentially lost the hardware renderer. The hardware renderer should be * reinitialized and setup when the render {@link #isRequested()} and * {@link #isEnabled()}. * * @param width The width of the drawing surface. * @param height The height of the drawing surface. * @param surface The surface to hardware accelerate * * @return true if the surface was initialized, false otherwise. Returning * false might mean that the surface was already initialized. */ boolean initializeIfNeeded(int width, int height, Surface surface) throws OutOfResourcesException { if (isRequested()) { // We lost the gl context, so recreate it. if (!isEnabled()) { if (initialize(surface)) { setup(width, height); return true; } } } return false; } /** * Optional, sets the name of the renderer. Useful for debugging purposes. * * @param name The name of this renderer, can be null */ abstract void setName(String name); /** * Creates a hardware renderer using OpenGL. * * @param glVersion The version of OpenGL to use (1 for OpenGL 1, 11 for OpenGL 1.1, etc.) * @param translucent True if the surface is translucent, false otherwise * * @return A hardware renderer backed by OpenGL. */ static HardwareRenderer createGlRenderer(int glVersion, boolean translucent) { switch (glVersion) { case 2: return Gl20Renderer.create(translucent); } throw new IllegalArgumentException("Unknown GL version: " + glVersion); } /** * Invoke this method when the system is running out of memory. This * method will attempt to recover as much memory as possible, based on * the specified hint. * * @param level Hint about the amount of memory that should be trimmed, * see {@link android.content.ComponentCallbacks} */ static void trimMemory(int level) { startTrimMemory(level); endTrimMemory(); } /** * Starts the process of trimming memory. Usually this call will setup * hardware rendering context and reclaim memory.Extra cleanup might * be required by calling {@link #endTrimMemory()}. * * @param level Hint about the amount of memory that should be trimmed, * see {@link android.content.ComponentCallbacks} */ static void startTrimMemory(int level) { Gl20Renderer.startTrimMemory(level); } /** * Finishes the process of trimming memory. This method will usually * cleanup special resources used by the memory trimming process. */ static void endTrimMemory() { Gl20Renderer.endTrimMemory(); } /** * Indicates whether hardware acceleration is currently enabled. * * @return True if hardware acceleration is in use, false otherwise. */ boolean isEnabled() { return mEnabled; } /** * Indicates whether hardware acceleration is currently enabled. * * @param enabled True if the hardware renderer is in use, false otherwise. */ void setEnabled(boolean enabled) { mEnabled = enabled; } /** * Indicates whether hardware acceleration is currently request but not * necessarily enabled yet. * * @return True if requested, false otherwise. */ boolean isRequested() { return mRequested; } /** * Indicates whether hardware acceleration is currently requested but not * necessarily enabled yet. * * @return True to request hardware acceleration, false otherwise. */ void setRequested(boolean requested) { mRequested = requested; } /** * Describes a series of frames that should be drawn on screen as a graph. * Each frame is composed of 1 or more elements. */ abstract 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; /** * Returns the type of graph to render. * * @return {@link #GRAPH_TYPE_BARS} or {@link #GRAPH_TYPE_LINES} */ abstract int getGraphType(); /** * This method is invoked before the graph is drawn. This method * can be used to compute sizes, etc. * * @param metrics The display metrics */ abstract void prepare(DisplayMetrics metrics); /** * @return The size in pixels of a vertical unit. */ abstract int getVerticalUnitSize(); /** * @return The size in pixels of a horizontal unit. */ abstract int getHorizontalUnitSize(); /** * @return The size in pixels of the margin between horizontal units. */ abstract int getHorizontaUnitMargin(); /** * An optional threshold value. * * @return A value >= 0 to draw the threshold, a negative value * to ignore it. */ abstract float getThreshold(); /** * The data to draw in the graph. The number of elements in the * array must be at least {@link #getFrameCount()} * {@link #getElementCount()}. * If a value is negative the following values will be ignored. */ abstract float[] getData(); /** * Returns the number of frames to render in the graph. */ abstract int getFrameCount(); /** * Returns the number of elements in each frame. This directly affects * the number of series drawn in the graph. */ abstract int getElementCount(); /** * Returns the current frame, if any. If the returned value is negative * the current frame is ignored. */ abstract int getCurrentFrame(); /** * Prepares the paint to draw the specified element (or series.) */ abstract void setupGraphPaint(Paint paint, int elementIndex); /** * Prepares the paint to draw the threshold. */ abstract void setupThresholdPaint(Paint paint); /** * Prepares the paint to draw the current frame indicator. */ abstract void setupCurrentFramePaint(Paint paint); } @SuppressWarnings({"deprecation"}) static abstract 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; 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, PROFILE_PROPERTY_VISUALIZE_LINES }; private static final String[] OVERDRAW = { OVERDRAW_PROPERTY_SHOW, OVERDRAW_PROPERTY_COUNT }; private static final int OVERDRAW_TYPE_COUNT = 1; 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 = RENDER_DIRTY_REGIONS && "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; HardwareLayer mDebugOverdrawLayer; Paint mDebugOverdrawPaint; final int mGlVersion; final boolean mTranslucent; private boolean mDestroyed; private final Rect mRedrawClip = new Rect(); private final int[] mSurfaceSize = new int[2]; private final FunctorsRunnable mFunctorsRunnable = new FunctorsRunnable(); private long mDrawDelta = Long.MAX_VALUE; GlRenderer(int glVersion, boolean translucent) { mGlVersion = glVersion; mTranslucent = translucent; loadSystemProperties(null); } @Override boolean loadSystemProperties(Surface surface) { 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 DrawPerformanceDataProvider(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 (mDebugOverdraw != OVERDRAW_TYPE_COUNT) { if (mDebugOverdrawLayer != null) { mDebugOverdrawLayer.destroy(); mDebugOverdrawLayer = null; mDebugOverdrawPaint = null; } } } if (nLoadProperties()) { 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) { 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(); } } } @Override long getFrameCount() { return mFrameCount; } /** * 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(); mCanvas.setName(mName); } setEnabled(true); if (contextCreated) { initAtlas(); } } return mCanvas != null; } } return false; } @Override void updateSurface(Surface surface) throws OutOfResourcesException { if (isRequested() && isEnabled()) { createEglSurface(surface); } } abstract HardwareCanvas createCanvas(); abstract int[] getConfig(boolean dirtyRegions); 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; } abstract ManagedEGLContext createManagedContext(EGLContext eglContext); 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(); } } abstract void initCaches(); abstract void initAtlas(); EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) { int[] attribs = { EGL14.EGL_CONTEXT_CLIENT_VERSION, mGlVersion, EGL_NONE }; EGLContext context = egl.eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT, mGlVersion != 0 ? attribs : null); 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; } @Override void destroy(boolean full) { if (full && mCanvas != null) { mCanvas = null; } if (!isEnabled() || mDestroyed) { setEnabled(false); return; } destroySurface(); setEnabled(false); mDestroyed = true; mGl = null; } 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; } @Override boolean validate() { return checkRenderContext() != SURFACE_STATE_ERROR; } @Override void setup(int width, int height) { if (validate()) { mCanvas.setViewport(width, height); mWidth = width; mHeight = height; } } @Override int getWidth() { return mWidth; } @Override int getHeight() { return mHeight; } @Override HardwareCanvas getCanvas() { return mCanvas; } @Override void setName(String name) { mName = name; } boolean canDraw() { return mGl != null && mCanvas != null; } int onPreDraw(Rect dirty) { return DisplayList.STATUS_DONE; } void onPostDraw() { } class FunctorsRunnable implements Runnable { View.AttachInfo attachInfo; @Override public void run() { final HardwareRenderer renderer = attachInfo.mHardwareRenderer; if (renderer == null || !renderer.isEnabled() || renderer != GlRenderer.this) { return; } if (checkRenderContext() != SURFACE_STATE_ERROR) { int status = mCanvas.invokeFunctors(mRedrawClip); handleFunctorStatus(attachInfo, status); } } } @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; attachInfo.mHardwareCanvas = canvas; if (mProfileEnabled) { mProfileLock.lock(); } dirty = beginFrame(canvas, dirty, surfaceState); DisplayList displayList = buildDisplayList(view, canvas); // 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 = DisplayList.STATUS_DONE; long start = getSystemTime(); try { status = prepareFrame(dirty); saveCount = canvas.save(); callbacks.onHardwarePreDraw(canvas); if (displayList != null) { status |= drawDisplayList(attachInfo, 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++; debugOverdraw(attachInfo, dirty, canvas, displayList); debugDirtyRegions(dirty, canvas); drawProfileData(attachInfo); } } onPostDraw(); swapBuffers(status); if (mProfileEnabled) { mProfileLock.unlock(); } attachInfo.mIgnoreDirtyState = false; } } } abstract void countOverdraw(HardwareCanvas canvas); abstract float getOverdraw(HardwareCanvas canvas); private void debugOverdraw(View.AttachInfo attachInfo, Rect dirty, HardwareCanvas canvas, DisplayList displayList) { if (mDebugOverdraw == OVERDRAW_TYPE_COUNT) { if (mDebugOverdrawLayer == null) { mDebugOverdrawLayer = createHardwareLayer(mWidth, mHeight, true); } else if (mDebugOverdrawLayer.getWidth() != mWidth || mDebugOverdrawLayer.getHeight() != mHeight) { mDebugOverdrawLayer.resize(mWidth, mHeight); } if (!mDebugOverdrawLayer.isValid()) { mDebugOverdraw = -1; return; } HardwareCanvas layerCanvas = mDebugOverdrawLayer.start(canvas, dirty); countOverdraw(layerCanvas); final int restoreCount = layerCanvas.save(); layerCanvas.drawDisplayList(displayList, null, DisplayList.FLAG_CLIP_CHILDREN); layerCanvas.restoreToCount(restoreCount); mDebugOverdrawLayer.end(canvas); float overdraw = getOverdraw(layerCanvas); DisplayMetrics metrics = attachInfo.mRootView.getResources().getDisplayMetrics(); drawOverdrawCounter(canvas, overdraw, metrics.density); } } private void drawOverdrawCounter(HardwareCanvas canvas, float overdraw, float density) { final String text = String.format("%.2fx", overdraw); final Paint paint = setupPaint(density); // HSBtoColor will clamp the values in the 0..1 range paint.setColor(Color.HSBtoColor(0.28f - 0.28f * overdraw / 3.5f, 0.8f, 1.0f)); canvas.drawText(text, density * 4.0f, mHeight - paint.getFontMetrics().bottom, paint); } private Paint setupPaint(float density) { if (mDebugOverdrawPaint == null) { mDebugOverdrawPaint = new Paint(); mDebugOverdrawPaint.setAntiAlias(true); mDebugOverdrawPaint.setShadowLayer(density * 3.0f, 0.0f, 0.0f, 0xff000000); mDebugOverdrawPaint.setTextSize(density * 20.0f); } return mDebugOverdrawPaint; } private DisplayList buildDisplayList(View view, HardwareCanvas canvas) { if (mDrawDelta <= 0) { return view.mDisplayList; } 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"); DisplayList displayList = view.getDisplayList(); Trace.traceEnd(Trace.TRACE_TAG_VIEW); endBuildDisplayListProfiling(buildDisplayListStartTime); return displayList; } abstract void drawProfileData(View.AttachInfo attachInfo); 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(View.AttachInfo attachInfo, HardwareCanvas canvas, DisplayList displayList, int status) { long drawDisplayListStartTime = 0; if (mProfileEnabled) { drawDisplayListStartTime = System.nanoTime(); } Trace.traceBegin(Trace.TRACE_TAG_VIEW, "drawDisplayList"); try { status |= canvas.drawDisplayList(displayList, mRedrawClip, DisplayList.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; } handleFunctorStatus(attachInfo, status); return status; } private void swapBuffers(int status) { if ((status & DisplayList.STATUS_DREW) == DisplayList.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); } } } private void handleFunctorStatus(View.AttachInfo attachInfo, int status) { // If the draw flag is set, functors will be invoked while executing // the tree of display lists if ((status & DisplayList.STATUS_DRAW) != 0) { if (mRedrawClip.isEmpty()) { attachInfo.mViewRootImpl.invalidate(); } else { attachInfo.mViewRootImpl.invalidateChildInParent(null, mRedrawClip); mRedrawClip.setEmpty(); } } if ((status & DisplayList.STATUS_INVOKE) != 0 || attachInfo.mHandler.hasCallbacks(mFunctorsRunnable)) { attachInfo.mHandler.removeCallbacks(mFunctorsRunnable); mFunctorsRunnable.attachInfo = attachInfo; attachInfo.mHandler.postDelayed(mFunctorsRunnable, FUNCTOR_PROCESS_DELAY); } } @Override void detachFunctor(long functor) { if (mCanvas != null) { mCanvas.detachFunctor(functor); } } @Override boolean attachFunctor(View.AttachInfo attachInfo, long functor) { if (mCanvas != null) { mCanvas.attachFunctor(functor); mFunctorsRunnable.attachInfo = attachInfo; attachInfo.mHandler.removeCallbacks(mFunctorsRunnable); attachInfo.mHandler.postDelayed(mFunctorsRunnable, 0); return true; } return false; } /** * 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); } class DrawPerformanceDataProvider extends GraphDataProvider { private final int mGraphType; private int mVerticalUnit; private int mHorizontalUnit; private int mHorizontalMargin; private int mThresholdStroke; DrawPerformanceDataProvider(int graphType) { mGraphType = graphType; } @Override 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); } @Override int getGraphType() { return mGraphType; } @Override int getVerticalUnitSize() { return mVerticalUnit; } @Override int getHorizontalUnitSize() { return mHorizontalUnit; } @Override int getHorizontaUnitMargin() { return mHorizontalMargin; } @Override float[] getData() { return mProfileData; } @Override float getThreshold() { return 16; } @Override int getFrameCount() { return mProfileData.length / PROFILE_FRAME_DATA_COUNT; } @Override int getElementCount() { return PROFILE_FRAME_DATA_COUNT; } @Override int getCurrentFrame() { return mProfileCurrentFrame / PROFILE_FRAME_DATA_COUNT; } @Override void setupGraphPaint(Paint paint, int elementIndex) { paint.setColor(PROFILE_DRAW_COLORS[elementIndex]); if (mGraphType == GRAPH_TYPE_LINES) paint.setStrokeWidth(mThresholdStroke); } @Override void setupThresholdPaint(Paint paint) { paint.setColor(PROFILE_DRAW_THRESHOLD_COLOR); paint.setStrokeWidth(mThresholdStroke); } @Override void setupCurrentFramePaint(Paint paint) { paint.setColor(PROFILE_DRAW_CURRENT_FRAME_COLOR); if (mGraphType == GRAPH_TYPE_LINES) paint.setStrokeWidth(mThresholdStroke); } } } /** * Hardware renderer using OpenGL ES 2.0. */ static class Gl20Renderer extends GlRenderer { private GLES20Canvas mGlCanvas; private DisplayMetrics mDisplayMetrics; private static EGLSurface sPbuffer; private static final Object[] sPbufferLock = new Object[0]; static class Gl20RendererEglContext extends ManagedEGLContext { final Handler mHandler = new Handler(); public Gl20RendererEglContext(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; } } } } Gl20Renderer(boolean translucent) { super(2, translucent); } @Override HardwareCanvas createCanvas() { return mGlCanvas = new GLES20Canvas(mTranslucent); } @Override ManagedEGLContext createManagedContext(EGLContext eglContext) { return new Gl20Renderer.Gl20RendererEglContext(mEglContext); } @Override 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 }; } @Override void initCaches() { if (GLES20Canvas.initCaches()) { // Caches were (re)initialized, rebind atlas initAtlas(); } } @Override 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) { int[] 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); } } @Override boolean canDraw() { return super.canDraw() && mGlCanvas != null; } @Override int onPreDraw(Rect dirty) { return mGlCanvas.onPreDraw(dirty); } @Override void onPostDraw() { mGlCanvas.onPostDraw(); } @Override 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 { super.destroy(full); } finally { if (full && mGlCanvas != null) { mGlCanvas = null; } } } @Override void pushLayerUpdate(HardwareLayer layer) { mGlCanvas.pushLayerUpdate(layer); } @Override void cancelLayerUpdate(HardwareLayer layer) { mGlCanvas.cancelLayerUpdate(layer); } @Override void flushLayerUpdates() { mGlCanvas.flushLayerUpdates(); } @Override public DisplayList createDisplayList(String name) { return new GLES20DisplayList(name); } @Override HardwareLayer createHardwareLayer(boolean isOpaque) { return new GLES20TextureLayer(isOpaque); } @Override public HardwareLayer createHardwareLayer(int width, int height, boolean isOpaque) { return new GLES20RenderLayer(width, height, isOpaque); } @Override void countOverdraw(HardwareCanvas canvas) { ((GLES20Canvas) canvas).setCountOverdrawEnabled(true); } @Override float getOverdraw(HardwareCanvas canvas) { return ((GLES20Canvas) canvas).getOverdraw(); } @Override public SurfaceTexture createSurfaceTexture(HardwareLayer layer) { return ((GLES20TextureLayer) layer).getSurfaceTexture(); } @Override void setSurfaceTexture(HardwareLayer layer, SurfaceTexture surfaceTexture) { ((GLES20TextureLayer) layer).setSurfaceTexture(surfaceTexture); } @Override boolean safelyRun(Runnable action) { boolean needsContext = !isEnabled() || checkRenderContext() == SURFACE_STATE_ERROR; if (needsContext) { Gl20RendererEglContext managedContext = (Gl20RendererEglContext) 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 destroyLayers(final View view) { if (view != null) { safelyRun(new Runnable() { @Override public void run() { if (mCanvas != null) { mCanvas.clearLayerUpdates(); } destroyHardwareLayer(view); GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_LAYERS); } }); } } private static void destroyHardwareLayer(View view) { view.destroyLayer(true); if (view instanceof ViewGroup) { ViewGroup group = (ViewGroup) view; int count = group.getChildCount(); for (int i = 0; i < count; i++) { destroyHardwareLayer(group.getChildAt(i)); } } } @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 HardwareRenderer create(boolean translucent) { if (GLES20Canvas.isAvailable()) { return new Gl20Renderer(translucent); } return null; } static void startTrimMemory(int level) { if (sEgl == null || sEglConfig == null) return; Gl20RendererEglContext managedContext = (Gl20RendererEglContext) 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); } } }