Add an on-screen overdraw counter

The counter can be enabled by setting the system property called
debug.hwui.overdraw to the string "count". If the string is set
to "show", overdraw will be highlighted on screen instead of
printing out a simple counter.

Change-Id: I9a9c970d54bffab43138bbb7682f6c04bc2c40bd
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index 21a03d8..cc7d948 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -274,6 +274,18 @@
 
     private static native int nGetStencilSize();
 
+    void setCountOverdrawEnabled(boolean enabled) {
+        nSetCountOverdrawEnabled(mRenderer, enabled);
+    }
+
+    static native void nSetCountOverdrawEnabled(int renderer, boolean enabled);
+
+    float getOverdraw() {
+        return nGetOverdraw(mRenderer);
+    }
+
+    static native float nGetOverdraw(int renderer);
+
     ///////////////////////////////////////////////////////////////////////////
     // Functor
     ///////////////////////////////////////////////////////////////////////////
diff --git a/core/java/android/view/GLES20RenderLayer.java b/core/java/android/view/GLES20RenderLayer.java
index 685dc70..68ba77c 100644
--- a/core/java/android/view/GLES20RenderLayer.java
+++ b/core/java/android/view/GLES20RenderLayer.java
@@ -100,12 +100,17 @@
 
     @Override
     HardwareCanvas start(Canvas currentCanvas) {
+        return start(currentCanvas, null);
+    }
+
+    @Override
+    HardwareCanvas start(Canvas currentCanvas, Rect dirty) {
         if (currentCanvas instanceof GLES20Canvas) {
             ((GLES20Canvas) currentCanvas).interrupt();
         }
         HardwareCanvas canvas = getCanvas();
         canvas.setViewport(mWidth, mHeight);
-        canvas.onPreDraw(null);
+        canvas.onPreDraw(dirty);
         return canvas;
     }
 
diff --git a/core/java/android/view/GLES20TextureLayer.java b/core/java/android/view/GLES20TextureLayer.java
index e863e49..a4cc630 100644
--- a/core/java/android/view/GLES20TextureLayer.java
+++ b/core/java/android/view/GLES20TextureLayer.java
@@ -63,6 +63,11 @@
     }
 
     @Override
+    HardwareCanvas start(Canvas currentCanvas, Rect dirty) {
+        return null;
+    }
+
+    @Override
     void end(Canvas currentCanvas) {
     }
 
diff --git a/core/java/android/view/HardwareLayer.java b/core/java/android/view/HardwareLayer.java
index 18b838b..23383d9 100644
--- a/core/java/android/view/HardwareLayer.java
+++ b/core/java/android/view/HardwareLayer.java
@@ -158,14 +158,22 @@
     /**
      * This must be invoked before drawing onto this layer.
      *
-     * @param currentCanvas
+     * @param currentCanvas The canvas whose rendering needs to be interrupted
      */
     abstract HardwareCanvas start(Canvas currentCanvas);
 
     /**
+     * This must be invoked before drawing onto this layer.
+     *
+     * @param dirty The dirty area to repaint
+     * @param currentCanvas The canvas whose rendering needs to be interrupted
+     */
+    abstract HardwareCanvas start(Canvas currentCanvas, Rect dirty);
+
+    /**
      * This must be invoked after drawing onto this layer.
      *
-     * @param currentCanvas
+     * @param currentCanvas The canvas whose rendering needs to be resumed
      */
     abstract void end(Canvas currentCanvas);
 
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index c7b1253..e8c1653 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -17,6 +17,7 @@
 package android.view;
 
 import android.content.ComponentCallbacks2;
+import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.graphics.SurfaceTexture;
@@ -45,7 +46,6 @@
 
 import java.io.File;
 import java.io.PrintWriter;
-import java.util.Arrays;
 import java.util.concurrent.locks.ReentrantLock;
 
 import static javax.microedition.khronos.egl.EGL10.*;
@@ -165,15 +165,32 @@
             "debug.hwui.show_layers_updates";
 
     /**
-     * Turn on to show overdraw level.
+     * Controls overdraw debugging.
      *
      * Possible values:
-     * "true", to enable overdraw debugging
      * "false", to disable overdraw debugging
+     * "show", to show overdraw areas on screen
+     * "count", to display an overdraw counter
      *
      * @hide
      */
-    public static final String DEBUG_SHOW_OVERDRAW_PROPERTY = "debug.hwui.show_overdraw";
+    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.
@@ -765,6 +782,17 @@
         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;
@@ -810,7 +838,9 @@
         Paint mProfilePaint;
 
         boolean mDebugDirtyRegions;
-        boolean mShowOverdraw;
+        int mDebugOverdraw = -1;
+        HardwareLayer mDebugOverdrawLayer;
+        Paint mDebugOverdrawPaint;
 
         final int mGlVersion;
         final boolean mTranslucent;
@@ -829,18 +859,13 @@
             loadSystemProperties(null);
         }
 
-        private static final String[] VISUALIZERS = {
-                PROFILE_PROPERTY_VISUALIZE_BARS,
-                PROFILE_PROPERTY_VISUALIZE_LINES
-        };
-
         @Override
         boolean loadSystemProperties(Surface surface) {
             boolean value;
             boolean changed = false;
 
             String profiling = SystemProperties.get(PROFILE_PROPERTY);
-            int graphType = Arrays.binarySearch(VISUALIZERS, profiling);
+            int graphType = search(VISUALIZERS, profiling);
             value = graphType >= 0;
 
             if (graphType != mProfileVisualizerType) {
@@ -897,11 +922,19 @@
                 }
             }
 
-            value = SystemProperties.getBoolean(
-                    HardwareRenderer.DEBUG_SHOW_OVERDRAW_PROPERTY, false);
-            if (value != mShowOverdraw) {
+            String overdraw = SystemProperties.get(HardwareRenderer.DEBUG_OVERDRAW_PROPERTY);
+            int debugOverdraw = search(OVERDRAW, overdraw);
+            if (debugOverdraw != mDebugOverdraw) {
                 changed = true;
-                mShowOverdraw = value;
+                mDebugOverdraw = debugOverdraw;
+
+                if (mDebugOverdraw != OVERDRAW_TYPE_COUNT) {
+                    if (mDebugOverdrawLayer != null) {
+                        mDebugOverdrawLayer.destroy();
+                        mDebugOverdrawLayer = null;
+                        mDebugOverdrawPaint = null;
+                    }
+                }
             }
 
             if (nLoadProperties()) {
@@ -911,6 +944,13 @@
             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) {
@@ -1392,6 +1432,8 @@
                         canvas.restoreToCount(saveCount);
                         view.mRecreateDisplayList = false;
 
+                        debugOverdraw(attachInfo, dirty, canvas, displayList);
+
                         mFrameCount++;
 
                         debugDirtyRegions(dirty, canvas);
@@ -1411,6 +1453,61 @@
             }
         }
 
+        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) {
+                // TODO: Use an alpha layer allocated from a GraphicBuffer
+                // The alpha format will help with rendering performance and
+                // the GraphicBuffer will let us skip the read pixels step
+                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) {
             view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)
                     == View.PFLAG_INVALIDATED;
@@ -2019,6 +2116,16 @@
         }
 
         @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();
         }
diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp
index c8fa290..cd7a032 100644
--- a/core/jni/android_view_GLES20Canvas.cpp
+++ b/core/jni/android_view_GLES20Canvas.cpp
@@ -185,6 +185,16 @@
     }
 }
 
+static void android_view_GLES20Canvas_setCountOverdrawEnabled(JNIEnv* env, jobject clazz,
+        OpenGLRenderer* renderer, jboolean enabled) {
+    renderer->setCountOverdrawEnabled(enabled);
+}
+
+static jfloat android_view_GLES20Canvas_getOverdraw(JNIEnv* env, jobject clazz,
+        OpenGLRenderer* renderer) {
+    return renderer->getOverdraw();
+}
+
 // ----------------------------------------------------------------------------
 // Functor
 // ----------------------------------------------------------------------------
@@ -944,6 +954,9 @@
     { "nSetName",           "(ILjava/lang/String;)V",
             (void*) android_view_GLES20Canvas_setName },
 
+    { "nSetCountOverdrawEnabled", "(IZ)V",     (void*) android_view_GLES20Canvas_setCountOverdrawEnabled },
+    { "nGetOverdraw",             "(I)F",      (void*) android_view_GLES20Canvas_getOverdraw },
+
     { "nGetStencilSize",    "()I",             (void*) android_view_GLES20Canvas_getStencilSize },
 
     { "nCallDrawGLFunction", "(II)I",          (void*) android_view_GLES20Canvas_callDrawGLFunction },
diff --git a/libs/hwui/Caches.cpp b/libs/hwui/Caches.cpp
index c60848c..f08b5ca 100644
--- a/libs/hwui/Caches.cpp
+++ b/libs/hwui/Caches.cpp
@@ -152,7 +152,7 @@
 
     if (property_get(PROPERTY_DEBUG_OVERDRAW, property, NULL) > 0) {
         INIT_LOGD("  Overdraw debug enabled: %s", property);
-        debugOverdraw = !strcmp(property, "true");
+        debugOverdraw = !strcmp(property, "show");
     } else {
         debugOverdraw = false;
     }
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 025e9f8..1447eb7 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -21,7 +21,6 @@
 #include <sys/types.h>
 
 #include <SkCanvas.h>
-#include <SkPathMeasure.h>
 #include <SkTypeface.h>
 
 #include <utils/Log.h>
@@ -120,6 +119,7 @@
 
     mFirstSnapshot = new Snapshot;
     mFrameStarted = false;
+    mCountOverdraw = false;
 
     mScissorOptimizationDisabled = false;
 }
@@ -222,6 +222,7 @@
 
 status_t OpenGLRenderer::prepareDirty(float left, float top,
         float right, float bottom, bool opaque) {
+
     setupFrameState(left, top, right, bottom, opaque);
 
     // Layer renderers will start the frame immediately
@@ -253,7 +254,7 @@
 }
 
 status_t OpenGLRenderer::clear(float left, float top, float right, float bottom, bool opaque) {
-    if (!opaque) {
+    if (!opaque || mCountOverdraw) {
         mCaches.enableScissor();
         mCaches.setScissor(left, mSnapshot->height - bottom, right - left, bottom - top);
         glClear(GL_COLOR_BUFFER_BIT);
@@ -335,6 +336,10 @@
 #endif
     }
 
+    if (mCountOverdraw) {
+        countOverdraw();
+    }
+
     mFrameStarted = false;
 }
 
@@ -524,6 +529,21 @@
     }
 }
 
+void OpenGLRenderer::countOverdraw() {
+    size_t count = mWidth * mHeight;
+    uint32_t* buffer = new uint32_t[count];
+    glReadPixels(0, 0, mWidth, mHeight, GL_RGBA, GL_UNSIGNED_BYTE, &buffer[0]);
+
+    size_t total = 0;
+    for (size_t i = 0; i < count; i++) {
+        total += buffer[i] & 0xff;
+    }
+
+    mOverdraw = total / float(count);
+
+    delete[] buffer;
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 // Layers
 ///////////////////////////////////////////////////////////////////////////////
@@ -1656,6 +1676,8 @@
     mDescription.hasDebugHighlight = !mCaches.debugOverdraw &&
             mCaches.debugStencilClip == Caches::kStencilShowHighlight &&
             mCaches.stencil.isTestEnabled();
+
+    mDescription.emulateStencil = mCountOverdraw;
 }
 
 void OpenGLRenderer::setupDrawWithTexture(bool isAlpha8) {
@@ -3546,6 +3568,19 @@
 
 void OpenGLRenderer::chooseBlending(bool blend, SkXfermode::Mode mode,
         ProgramDescription& description, bool swapSrcDst) {
+    if (mCountOverdraw) {
+        if (!mCaches.blend) glEnable(GL_BLEND);
+        if (mCaches.lastSrcMode != GL_ONE || mCaches.lastDstMode != GL_ONE) {
+            glBlendFunc(GL_ONE, GL_ONE);
+        }
+
+        mCaches.blend = true;
+        mCaches.lastSrcMode = GL_ONE;
+        mCaches.lastDstMode = GL_ONE;
+
+        return;
+    }
+
     blend = blend || mode != SkXfermode::kSrcOver_Mode;
 
     if (blend) {
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index 640c7db..df275d7 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -192,6 +192,14 @@
      */
     virtual void resume();
 
+    ANDROID_API void setCountOverdrawEnabled(bool enabled) {
+        mCountOverdraw = enabled;
+    }
+
+    ANDROID_API float getOverdraw() {
+        return mCountOverdraw ? mOverdraw : 0.0f;
+    }
+
     ANDROID_API status_t invokeFunctors(Rect& dirty);
     ANDROID_API void detachFunctor(Functor* functor);
     ANDROID_API void attachFunctor(Functor* functor);
@@ -981,6 +989,7 @@
 
     void debugOverdraw(bool enable, bool clear);
     void renderOverdraw();
+    void countOverdraw();
 
     /**
      * Should be invoked every time the glScissor is modified.
@@ -1072,6 +1081,12 @@
     // No-ops start/endTiling when set
     bool mSuppressTiling;
 
+
+    // If true, this renderer will setup drawing to emulate
+    // an increment stencil buffer in the color buffer
+    bool mCountOverdraw;
+    float mOverdraw;
+
     // Optional name of the renderer
     String8 mName;
 
diff --git a/libs/hwui/Program.h b/libs/hwui/Program.h
index a252209..dd1aaa2 100644
--- a/libs/hwui/Program.h
+++ b/libs/hwui/Program.h
@@ -85,6 +85,7 @@
 #define PROGRAM_HAS_COLORS 42
 
 #define PROGRAM_HAS_DEBUG_HIGHLIGHT 43
+#define PROGRAM_EMULATE_STENCIL 44
 
 ///////////////////////////////////////////////////////////////////////////////
 // Types
@@ -163,6 +164,7 @@
     float gamma;
 
     bool hasDebugHighlight;
+    bool emulateStencil;
 
     /**
      * Resets this description. All fields are reset back to the default
@@ -275,6 +277,7 @@
         if (isSimpleGradient) key |= programid(0x1) << PROGRAM_IS_SIMPLE_GRADIENT;
         if (hasColors) key |= programid(0x1) << PROGRAM_HAS_COLORS;
         if (hasDebugHighlight) key |= programid(0x1) << PROGRAM_HAS_DEBUG_HIGHLIGHT;
+        if (emulateStencil) key |= programid(0x1) << PROGRAM_EMULATE_STENCIL;
         return key;
     }
 
diff --git a/libs/hwui/ProgramCache.cpp b/libs/hwui/ProgramCache.cpp
index 8eb85e5..367294c 100644
--- a/libs/hwui/ProgramCache.cpp
+++ b/libs/hwui/ProgramCache.cpp
@@ -342,6 +342,12 @@
 };
 const char* gFS_Main_DebugHighlight =
         "    gl_FragColor.rgb = vec3(0.0, gl_FragColor.a, 0.0);\n";
+const char* gFS_Main_EmulateStencil =
+        "    gl_FragColor.rgba = vec4(1.0 / 255.0, 1.0 / 255.0, 1.0 / 255.0, 1.0);\n"
+        "    return;\n"
+        "    /*\n";
+const char* gFS_Footer_EmulateStencil =
+        "    */\n";
 const char* gFS_Footer =
         "}\n\n";
 
@@ -603,7 +609,8 @@
     // Optimization for common cases
     if (!description.isAA && !blendFramebuffer && !description.hasColors &&
             description.colorOp == ProgramDescription::kColorNone &&
-            !description.isPoint && !description.hasDebugHighlight) {
+            !description.isPoint && !description.hasDebugHighlight &&
+            !description.emulateStencil) {
         bool fast = false;
 
         const bool noShader = !description.hasGradient && !description.hasBitmap;
@@ -683,6 +690,9 @@
 
     // Begin the shader
     shader.append(gFS_Main); {
+        if (description.emulateStencil) {
+            shader.append(gFS_Main_EmulateStencil);
+        }
         // Stores the result in fragColor directly
         if (description.hasTexture || description.hasExternalTexture) {
             if (description.hasAlpha8Texture) {
@@ -757,6 +767,9 @@
             shader.append(gFS_Main_DebugHighlight);
         }
     }
+    if (description.emulateStencil) {
+        shader.append(gFS_Footer_EmulateStencil);
+    }
     // End the shader
     shader.append(gFS_Footer);
 
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 87ac845..7c68b5b 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -71,9 +71,9 @@
 
 /**
  * Used to enable/disable overdraw debugging. The accepted values are
- * "true" and "false". The default value is "false".
+ * "show", "count" and "false". The default value is "false".
  */
-#define PROPERTY_DEBUG_OVERDRAW "debug.hwui.show_overdraw"
+#define PROPERTY_DEBUG_OVERDRAW "debug.hwui.overdraw"
 
 /**
  * Used to enable/disable non-rectangular clipping debugging.
diff --git a/libs/hwui/thread/TaskManager.h b/libs/hwui/thread/TaskManager.h
index 477314b..f2a216f 100644
--- a/libs/hwui/thread/TaskManager.h
+++ b/libs/hwui/thread/TaskManager.h
@@ -43,7 +43,7 @@
     /**
      * Returns true if this task  manager can run tasks,
      * false otherwise. This method will typically return
-     * true on a single CPU core device.
+     * false on a single CPU core device.
      */
     bool canRunTasks() const;