/* * 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.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.SurfaceTexture; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; import android.os.Trace; import android.util.DisplayMetrics; import android.util.Log; import android.view.Surface.OutOfResourcesException; import java.io.PrintWriter; import java.util.concurrent.locks.ReentrantLock; /** * Hardware renderer using OpenGL that's used as the remote endpoint * of ThreadedRenderer * * Currently this is mostly a copy of GLRenderer, but with a few modifications * to deal with the threading issues. Ideally native-side functionality * will replace this, but we need this to bootstrap without risking breaking * changes in GLRenderer * * @hide */ public class RemoteGLRenderer 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, PROFILE_PROPERTY_VISUALIZE_LINES }; private static final String[] OVERDRAW = { OVERDRAW_PROPERTY_SHOW, OVERDRAW_PROPERTY_COUNT }; private static final int OVERDRAW_TYPE_COUNT = 1; private static final int GL_VERSION = 2; int mWidth = -1, mHeight = -1; HardwareCanvas mCanvas; String mName; long mFrameCount; Paint mDebugPaint; boolean mDirtyRegionsEnabled; boolean mSurfaceUpdated; 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 boolean mTranslucent; 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; private GLES20Canvas mGlCanvas; private DisplayMetrics mDisplayMetrics; private ThreadedRenderer mOwningRenderer; private long mNativeCanvasContext; HardwareCanvas createCanvas() { return mGlCanvas = new GLES20Canvas(mTranslucent); } 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) { 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); } } boolean canDraw() { return 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 (mNativeCanvasContext != 0) { destroyContext(mNativeCanvasContext); mNativeCanvasContext = 0; } setEnabled(false); } 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 HardwareLayer createHardwareLayer(boolean isOpaque) { return new GLES20TextureLayer(isOpaque); } @Override public HardwareLayer createHardwareLayer(int width, int height, boolean isOpaque) { return new GLES20RenderLayer(width, height, isOpaque); } void countOverdraw(HardwareCanvas canvas) { ((GLES20Canvas) canvas).setCountOverdrawEnabled(true); } 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) { if (!usePBufferSurface()) { return false; } } action.run(); 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)); } } } RemoteGLRenderer(ThreadedRenderer owningRenderer, boolean translucent) { mOwningRenderer = owningRenderer; mTranslucent = translucent; loadSystemProperties(); } @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 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 (GLRenderer.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) { 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; } private void triggerSoftwareFallback() { destroy(true); // 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()) { mNativeCanvasContext = createContext(); boolean surfaceCreated = createEglSurface(surface); if (surfaceCreated) { if (mCanvas == null) { mCanvas = createCanvas(); } setEnabled(true); initAtlas(); return true; } else { destroy(true); setRequested(false); } } return false; } @Override void updateSurface(Surface surface) throws OutOfResourcesException { if (isRequested() && isEnabled()) { createEglSurface(surface); } } boolean createEglSurface(Surface surface) throws OutOfResourcesException { // Create an EGL surface we can render into. if (!setSurface(mNativeCanvasContext, surface)) { return false; } makeCurrent(mNativeCanvasContext); mSurfaceUpdated = true; initCaches(); return true; } @Override void invalidate(Surface surface) { setSurface(mNativeCanvasContext, null); setEnabled(false); if (surface.isValid()) { if (createEglSurface(surface) && mCanvas != null) { setEnabled(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 void setName(String name) { mName = name; } // TODO: Ping pong is fun and all, but this isn't the time or place // However we don't yet have the ability for the RenderThread to run // independently nor have a way to postDelayed, so this will work for now private Runnable mDispatchFunctorsRunnable = new Runnable() { @Override public void run() { ThreadedRenderer.postToRenderThread(mFunctorsRunnable); } }; class FunctorsRunnable implements Runnable { View.AttachInfo attachInfo; @Override public void run() { final HardwareRenderer renderer = attachInfo.mHardwareRenderer; if (renderer == null || !renderer.isEnabled() || renderer != mOwningRenderer) { return; } if (checkRenderContext() != SURFACE_STATE_ERROR) { int status = mCanvas.invokeFunctors(mRedrawClip); handleFunctorStatus(attachInfo, status); } } } /** * @param displayList The display list 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. */ void drawDisplayList(DisplayList displayList, View.AttachInfo attachInfo, HardwareDrawCallbacks callbacks, Rect dirty) { if (canDraw()) { if (!hasDirtyRegions()) { dirty = null; } final int surfaceState = checkRenderContext(); if (surfaceState != SURFACE_STATE_ERROR) { HardwareCanvas canvas = mCanvas; if (mProfileEnabled) { mProfileLock.lock(); } dirty = beginFrame(canvas, dirty, surfaceState); int saveCount = 0; int status = DisplayList.STATUS_DONE; long start = GLRenderer.getSystemTime(); try { status = prepareFrame(dirty); saveCount = canvas.save(); callbacks.onHardwarePreDraw(canvas); status |= doDrawDisplayList(attachInfo, canvas, displayList, status); } catch (Exception e) { Log.e(LOG_TAG, "An error has occurred while drawing:", e); } finally { callbacks.onHardwarePostDraw(canvas); canvas.restoreToCount(saveCount); mDrawDelta = GLRenderer.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; } } } @Override void draw(View view, View.AttachInfo attachInfo, HardwareDrawCallbacks callbacks, Rect dirty) { throw new IllegalAccessError(); } 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 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; GLRenderer.beginFrame(null); } else { int[] size = mSurfaceSize; GLRenderer.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 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 doDrawDisplayList(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(); } if (!swapBuffers(mNativeCanvasContext)) { triggerSoftwareFallback(); } mSurfaceUpdated = false; if (mProfileEnabled) { long now = System.nanoTime(); float total = (now - eglSwapBuffersStartTime) * 0.000001f; mProfileData[mProfileCurrentFrame + 2] = total; } } } 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(final 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) { // TODO: Can we just re-queue ourselves up to draw next frame instead // of bouncing back to the UI thread? // TODO: Respect mRedrawClip - for now just full inval attachInfo.mHandler.post(new Runnable() { @Override public void run() { attachInfo.mViewRootImpl.invalidate(); } }); mRedrawClip.setEmpty(); } if ((status & DisplayList.STATUS_INVOKE) != 0 || attachInfo.mHandler.hasCallbacks(mDispatchFunctorsRunnable)) { attachInfo.mHandler.removeCallbacks(mDispatchFunctorsRunnable); mFunctorsRunnable.attachInfo = attachInfo; attachInfo.mHandler.postDelayed(mDispatchFunctorsRunnable, FUNCTOR_PROCESS_DELAY); } } @Override void detachFunctor(int functor) { if (mCanvas != null) { mCanvas.detachFunctor(functor); } } @Override boolean attachFunctor(View.AttachInfo attachInfo, int functor) { if (mCanvas != null) { mCanvas.attachFunctor(functor); mFunctorsRunnable.attachInfo = attachInfo; attachInfo.mHandler.removeCallbacks(mDispatchFunctorsRunnable); attachInfo.mHandler.postDelayed(mDispatchFunctorsRunnable, 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 (!makeCurrent(mNativeCanvasContext)) { triggerSoftwareFallback(); return SURFACE_STATE_ERROR; } return mSurfaceUpdated ? SURFACE_STATE_UPDATED : 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); } } static native long createContext(); static native boolean usePBufferSurface(); static native boolean setSurface(long nativeCanvasContext, Surface surface); static native boolean swapBuffers(long nativeCanvasContext); static native boolean makeCurrent(long nativeCanvasContext); static native void destroyContext(long nativeCanvasContext); }