Bug 717349 - Add optional render and checkerboarding profiling. r=kats

This adds checkerboard profiling to LayerRenderer, that can be enabled either
by enabling debug logging of the "GeckoLayerRendererProf" tag, or via
reflection using PanningPerfAPI.
This commit is contained in:
Chris Lord 2012-02-02 09:02:32 +00:00
parent 674b523321
commit 926982757d
5 changed files with 145 additions and 12 deletions

View File

@ -41,6 +41,7 @@ package org.mozilla.gecko.gfx;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.RectF;
import android.graphics.Region;
import android.util.Log;
import java.util.concurrent.locks.ReentrantLock;
import javax.microedition.khronos.opengles.GL10;
@ -105,6 +106,15 @@ public abstract class Layer {
return new RectF(x, y, x + width, y + height);
}
/**
* Returns the region of the layer that is considered valid. The default
* implementation of this will return the bounds of the layer, but this
* may be overridden.
*/
public Region getValidRegion(RenderContext context) {
return new Region(RectUtils.round(getBounds(context, new FloatSize(getSize()))));
}
/**
* Call this before modifying the layer. Note that, for TileLayers, "modifying the layer"
* includes altering the underlying CairoImage in any way. Thus you must call this function

View File

@ -55,6 +55,8 @@ import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.graphics.RegionIterator;
import android.opengl.GLSurfaceView;
import android.os.SystemClock;
import android.util.DisplayMetrics;
@ -70,6 +72,7 @@ import java.util.ArrayList;
*/
public class LayerRenderer implements GLSurfaceView.Renderer {
private static final String LOGTAG = "GeckoLayerRenderer";
private static final String PROFTAG = "GeckoLayerRendererProf";
/*
* The amount of time a frame is allowed to take to render before we declare it a dropped
@ -99,6 +102,12 @@ public class LayerRenderer implements GLSurfaceView.Renderer {
private int mCurrentFrame, mFrameTimingsSum, mDroppedFrames;
private boolean mShowFrameRate;
// Render profiling output
private int mFramesRendered;
private float mCompleteFramesRendered;
private boolean mProfileRender;
private long mProfileOutputTime;
/* Used by robocop for testing purposes */
private IntBuffer mPixelBuffer;
@ -147,7 +156,7 @@ public class LayerRenderer implements GLSurfaceView.Renderer {
}
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
checkFrameRateMonitorEnabled();
checkMonitoringEnabled();
gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST);
gl.glDisable(GL10.GL_DITHER);
@ -264,11 +273,50 @@ public class LayerRenderer implements GLSurfaceView.Renderer {
/* Draw the horizontal scrollbar. */
if (pageRect.width() > screenSize.width)
mHorizScrollLayer.draw(pageContext);
/* Measure how much of the screen is checkerboarding */
if ((rootLayer != null) &&
(mProfileRender || PanningPerfAPI.isRecordingCheckerboard())) {
// Find out how much of the viewport area is valid
Rect viewport = RectUtils.round(pageContext.viewport);
Region validRegion = rootLayer.getValidRegion(pageContext);
validRegion.op(viewport, Region.Op.INTERSECT);
float checkerboard = 0.0f;
if (!(validRegion.isRect() && validRegion.getBounds().equals(viewport))) {
int screenArea = viewport.width() * viewport.height();
validRegion.op(viewport, Region.Op.REVERSE_DIFFERENCE);
// XXX The assumption here is that a Region never has overlapping
// rects. This is true, as evidenced by reading the SkRegion
// source, but is not mentioned in the Android documentation,
// and so is liable to change.
// If it does change, this code will need to be reevaluated.
Rect r = new Rect();
int checkerboardArea = 0;
for (RegionIterator i = new RegionIterator(validRegion); i.next(r);) {
checkerboardArea += r.width() * r.height();
}
checkerboard = checkerboardArea / (float)screenArea;
}
PanningPerfAPI.recordCheckerboard(checkerboard);
mCompleteFramesRendered += 1.0f - checkerboard;
mFramesRendered ++;
if (frameStartTime - mProfileOutputTime > 1000) {
mProfileOutputTime = frameStartTime;
printCheckerboardStats();
}
}
}
/* Draw the FPS. */
if (mShowFrameRate) {
updateDroppedFrames(frameStartTime);
try {
gl.glEnable(GL10.GL_BLEND);
gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
@ -295,6 +343,12 @@ public class LayerRenderer implements GLSurfaceView.Renderer {
}
}
private void printCheckerboardStats() {
Log.d(PROFTAG, "Frames rendered over last 1000ms: " + mCompleteFramesRendered + "/" + mFramesRendered);
mFramesRendered = 0;
mCompleteFramesRendered = 0;
}
/** Used by robocop for testing purposes. Not for production use! */
IntBuffer getPixels() {
IntBuffer pixelBuffer = IntBuffer.allocate(mView.getWidth() * mView.getHeight());
@ -382,7 +436,6 @@ public class LayerRenderer implements GLSurfaceView.Renderer {
mCurrentFrame = (mCurrentFrame + 1) % mFrameTimings.length;
int averageTime = mFrameTimingsSum / mFrameTimings.length;
mFrameRateLayer.beginTransaction();
try {
mFrameRateLayer.setText(averageTime + " ms/" + mDroppedFrames);
@ -403,7 +456,7 @@ public class LayerRenderer implements GLSurfaceView.Renderer {
}
}
private void checkFrameRateMonitorEnabled() {
private void checkMonitoringEnabled() {
/* Do this I/O off the main thread to minimize its impact on startup time. */
new Thread(new Runnable() {
@Override
@ -411,6 +464,7 @@ public class LayerRenderer implements GLSurfaceView.Renderer {
Context context = mView.getContext();
SharedPreferences preferences = context.getSharedPreferences("GeckoApp", 0);
mShowFrameRate = preferences.getBoolean("showFrameRate", false);
mProfileRender = Log.isLoggable(PROFTAG, Log.DEBUG);
}
}).start();
}

View File

@ -438,6 +438,18 @@ public class MultiTileLayer extends Layer {
mRenderOffset.set(offset.x, offset.y);
}
@Override
public Region getValidRegion(RenderContext context) {
Region validRegion = new Region();
for (SubTile tile : mTiles) {
if (tile.key == null || tile.getValidTextureArea().isEmpty())
continue;
validRegion.op(tile.getValidRegion(context), Region.Op.UNION);
}
return validRegion;
}
/**
* Invalidates all sub-tiles. This should be called if the source backing
* this layer has changed. This method is only valid inside a transaction.

View File

@ -20,6 +20,7 @@
*
* Contributor(s):
* Kartikaya Gupta <kgupta@mozilla.com>
* Chris Lord <chrislord.net@gmail.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
@ -50,37 +51,75 @@ public class PanningPerfAPI {
// to measure.
private static final int EXPECTED_FRAME_COUNT = 2048;
private static boolean mRecording = false;
private static boolean mRecordingFrames = false;
private static List<Long> mFrameTimes;
private static long mStartTime;
private static long mFrameStartTime;
private static boolean mRecordingCheckerboard = false;
private static List<Float> mCheckerboardAmounts;
private static long mCheckerboardStartTime;
public static void startFrameTimeRecording() {
if (mRecording) {
if (mRecordingFrames) {
Log.e(LOGTAG, "Error: startFrameTimeRecording() called while already recording!");
return;
}
mRecording = true;
mRecordingFrames = true;
if (mFrameTimes == null) {
mFrameTimes = new ArrayList<Long>(EXPECTED_FRAME_COUNT);
} else {
mFrameTimes.clear();
}
mStartTime = SystemClock.uptimeMillis();
mFrameStartTime = SystemClock.uptimeMillis();
}
public static List<Long> stopFrameTimeRecording() {
if (!mRecording) {
if (!mRecordingFrames) {
Log.e(LOGTAG, "Error: stopFrameTimeRecording() called when not recording!");
return null;
}
mRecording = false;
mRecordingFrames = false;
return mFrameTimes;
}
public static void recordFrameTime() {
// this will be called often, so try to make it as quick as possible
if (mRecording) {
mFrameTimes.add(SystemClock.uptimeMillis() - mStartTime);
if (mRecordingFrames) {
mFrameTimes.add(SystemClock.uptimeMillis() - mFrameStartTime);
}
}
public static boolean isRecordingCheckerboard() {
return mRecordingCheckerboard;
}
public static void startCheckerboardRecording() {
if (mRecordingCheckerboard) {
Log.e(LOGTAG, "Error: startCheckerboardRecording() called while already recording!");
return;
}
mRecordingCheckerboard = true;
if (mCheckerboardAmounts == null) {
mCheckerboardAmounts = new ArrayList<Float>(EXPECTED_FRAME_COUNT);
} else {
mCheckerboardAmounts.clear();
}
mCheckerboardStartTime = SystemClock.uptimeMillis();
}
public static List<Float> stopCheckerboardRecording() {
if (!mRecordingCheckerboard) {
Log.e(LOGTAG, "Error: stopCheckerboardRecording() called when not recording!");
return null;
}
mRecordingCheckerboard = false;
return mCheckerboardAmounts;
}
public static void recordCheckerboard(float amount) {
// this will be called often, so try to make it as quick as possible
if (mRecordingCheckerboard) {
mCheckerboardAmounts.add(amount);
}
}
}

View File

@ -37,8 +37,10 @@
package org.mozilla.gecko.gfx;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.opengl.GLES20;
import android.util.Log;
import javax.microedition.khronos.opengles.GL10;
@ -149,6 +151,22 @@ public abstract class TileLayer extends Layer {
return mValidTextureRect;
}
@Override
public Region getValidRegion(RenderContext context) {
if (mValidTextureRect.isEmpty())
return new Region();
Point origin = getOrigin();
float scaleFactor = context.zoomFactor / getResolution();
float x = (origin.x + mValidTextureRect.left) * scaleFactor;
float y = (origin.y + mValidTextureRect.top) * scaleFactor;
float width = mValidTextureRect.width() * scaleFactor;
float height = mValidTextureRect.height() * scaleFactor;
return new Region(Math.round(x), Math.round(y),
Math.round(x + width), Math.round(y + height));
}
@Override
protected boolean performUpdates(GL10 gl, RenderContext context) {
super.performUpdates(gl, context);