blob: 2919775b05ba6dfcbfe6b9cc7f8483de2886107c [file] [log] [blame]
/*
* 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);
}