Support op dumping in new pipeline

bug:26565102

Change-Id: I266e420a2f18ba9ad62942b8a0de295dfa3a2a88
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 8660b75..da7b7fb 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -119,6 +119,7 @@
         BakedOpState.cpp \
         FrameBuilder.cpp \
         LayerBuilder.cpp \
+        OpDumper.cpp \
         RecordingCanvas.cpp
 
     hwui_cflags += -DHWUI_NEW_OPS
@@ -253,6 +254,7 @@
         tests/unit/BakedOpStateTests.cpp \
         tests/unit/FrameBuilderTests.cpp \
         tests/unit/LeakCheckTests.cpp \
+        tests/unit/OpDumperTests.cpp \
         tests/unit/RecordingCanvasTests.cpp
 endif
 
diff --git a/libs/hwui/Matrix.h b/libs/hwui/Matrix.h
index 1c25f26..36007cd 100644
--- a/libs/hwui/Matrix.h
+++ b/libs/hwui/Matrix.h
@@ -14,15 +14,15 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_HWUI_MATRIX_H
-#define ANDROID_HWUI_MATRIX_H
-
-#include <SkMatrix.h>
-
-#include <cutils/compiler.h>
+#pragma once
 
 #include "Rect.h"
 
+#include <cutils/compiler.h>
+#include <iomanip>
+#include <ostream>
+#include <SkMatrix.h>
+
 namespace android {
 namespace uirenderer {
 
@@ -218,6 +218,22 @@
 
     void dump(const char* label = nullptr) const;
 
+    friend std::ostream& operator<<(std::ostream& os, const Matrix4& matrix) {
+        if (matrix.isSimple()) {
+            os << "offset " << matrix.getTranslateX() << "x" << matrix.getTranslateY();
+            if (!matrix.isPureTranslate()) {
+                os << ", scale " << matrix[kScaleX] << "x" << matrix[kScaleY];
+            }
+        } else {
+            os << "[" << matrix[0];
+            for (int i = 1; i < 16; i++) {
+                os << ", " << matrix[i];
+            }
+            os << "]";
+        }
+        return os;
+    }
+
     static const Matrix4& identity();
 
 private:
@@ -244,4 +260,3 @@
 }; // namespace uirenderer
 }; // namespace android
 
-#endif // ANDROID_HWUI_MATRIX_H
diff --git a/libs/hwui/OpDumper.cpp b/libs/hwui/OpDumper.cpp
new file mode 100644
index 0000000..c34cfbe
--- /dev/null
+++ b/libs/hwui/OpDumper.cpp
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#include "OpDumper.h"
+
+#include "RecordedOp.h"
+
+namespace android {
+namespace uirenderer {
+
+#define STRINGIFY(n) #n,
+static const char* sOpNameLut[] = BUILD_FULL_OP_LUT(STRINGIFY);
+
+void OpDumper::dump(const RecordedOp& op, std::ostream& output, int level) {
+    for (int i = 0; i < level; i++) {
+        output << "  ";
+    }
+
+    Rect localBounds(op.unmappedBounds);
+    op.localMatrix.mapRect(localBounds);
+    output << sOpNameLut[op.opId] << " " << localBounds;
+
+    if (op.localClip && !op.localClip->rect.contains(localBounds)) {
+        output << std::fixed << std::setprecision(0)
+             << " clip=" << op.localClip->rect
+             << " mode=" << (int)op.localClip->mode;
+    }
+}
+
+} // namespace uirenderer
+} // namespace android
diff --git a/libs/hwui/OpDumper.h b/libs/hwui/OpDumper.h
new file mode 100644
index 0000000..c99b517
--- /dev/null
+++ b/libs/hwui/OpDumper.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#pragma once
+
+#include <ostream>
+
+namespace android {
+namespace uirenderer {
+
+struct RecordedOp;
+
+class OpDumper {
+public:
+    static void dump(const RecordedOp& op, std::ostream& output, int level = 0);
+};
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h
index bb26e2e..c37458d 100644
--- a/libs/hwui/RecordedOp.h
+++ b/libs/hwui/RecordedOp.h
@@ -119,6 +119,9 @@
 #define BUILD_RENDERABLE_OP_LUT(OP_FN) \
         { MAP_OPS_BASED_ON_TYPE(NULLPTR_OP_FN, OP_FN, OP_FN, OP_FN) }
 
+#define BUILD_FULL_OP_LUT(OP_FN) \
+        { MAP_OPS_BASED_ON_TYPE(OP_FN, OP_FN, OP_FN, OP_FN) }
+
 /**
  * Op mapping functions, which skip unsupported ops.
  *
diff --git a/libs/hwui/Rect.h b/libs/hwui/Rect.h
index 30c925c..d9fce9b 100644
--- a/libs/hwui/Rect.h
+++ b/libs/hwui/Rect.h
@@ -14,16 +14,17 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_HWUI_RECT_H
-#define ANDROID_HWUI_RECT_H
+#pragma once
 
-#include <cmath>
-#include <algorithm>
-#include <SkRect.h>
+#include "Vertex.h"
 
 #include <utils/Log.h>
 
-#include "Vertex.h"
+#include <algorithm>
+#include <cmath>
+#include <iomanip>
+#include <ostream>
+#include <SkRect.h>
 
 namespace android {
 namespace uirenderer {
@@ -282,9 +283,23 @@
     void dump(const char* label = nullptr) const {
         ALOGD("%s[l=%.2f t=%.2f r=%.2f b=%.2f]", label ? label : "Rect", left, top, right, bottom);
     }
+
+    friend std::ostream& operator<<(std::ostream& os, const Rect& rect) {
+        if (rect.isEmpty()) {
+            return os << "empty";
+        }
+
+        if (rect.left == 0 && rect.top == 0) {
+            return os << "[" << rect.right << " x " << rect.bottom << "]";
+        }
+
+        return os << "[" << rect.left
+                << " " << rect.top
+                << " " << rect.right
+                << " " << rect.bottom << "]";
+    }
 }; // class Rect
 
 }; // namespace uirenderer
 }; // namespace android
 
-#endif // ANDROID_HWUI_RECT_H
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 9ac76a4..61441ce 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -19,8 +19,9 @@
 #include "DamageAccumulator.h"
 #include "Debug.h"
 #if HWUI_NEW_OPS
-#include "RecordedOp.h"
 #include "BakedOpRenderer.h"
+#include "RecordedOp.h"
+#include "OpDumper.h"
 #endif
 #include "DisplayListOp.h"
 #include "LayerRenderer.h"
@@ -95,6 +96,34 @@
  * This function is a simplified version of replay(), where we simply retrieve and log the
  * display list. This function should remain in sync with the replay() function.
  */
+#if HWUI_NEW_OPS
+void RenderNode::output(uint32_t level, const char* label) {
+    ALOGD("%s (%s %p%s%s%s%s%s)",
+            label,
+            getName(),
+            this,
+            (MathUtils::isZero(properties().getAlpha()) ? ", zero alpha" : ""),
+            (properties().hasShadow() ? ", casting shadow" : ""),
+            (isRenderable() ? "" : ", empty"),
+            (properties().getProjectBackwards() ? ", projected" : ""),
+            (mLayer != nullptr ? ", on HW Layer" : ""));
+    properties().debugOutputProperties(level + 1);
+
+    if (mDisplayList) {
+        for (auto&& op : mDisplayList->getOps()) {
+            std::stringstream strout;
+            OpDumper::dump(*op, strout, level + 1);
+            if (op->opId == RecordedOpId::RenderNodeOp) {
+                auto rnOp = reinterpret_cast<const RenderNodeOp*>(op);
+                rnOp->renderNode->output(level + 1, strout.str().c_str());
+            } else {
+                ALOGD("%s", strout.str().c_str());
+            }
+        }
+    }
+    ALOGD("%*s/RenderNode(%s %p)", level * 2, "", getName(), this);
+}
+#else
 void RenderNode::output(uint32_t level) {
     ALOGD("%*sStart display list (%p, %s%s%s%s%s%s)", (level - 1) * 2, "", this,
             getName(),
@@ -104,22 +133,16 @@
             (properties().getProjectBackwards() ? ", projected" : ""),
             (mLayer != nullptr ? ", on HW Layer" : ""));
     ALOGD("%*s%s %d", level * 2, "", "Save", SaveFlags::MatrixClip);
-
     properties().debugOutputProperties(level);
-
     if (mDisplayList) {
-#if HWUI_NEW_OPS
-        LOG_ALWAYS_FATAL("op dumping unsupported");
-#else
         // TODO: consider printing the chunk boundaries here
         for (auto&& op : mDisplayList->getOps()) {
             op->output(level, DisplayListOp::kOpLogFlag_Recurse);
         }
-#endif
     }
-
     ALOGD("%*sDone (%p, %s)", (level - 1) * 2, "", this, getName());
-}
+    }
+#endif
 
 void RenderNode::copyTo(proto::RenderNode *pnode) {
     pnode->set_id(static_cast<uint64_t>(
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index e037645..8381925 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -123,7 +123,11 @@
     void defer(DeferStateStruct& deferStruct, const int level);
     void replay(ReplayStateStruct& replayStruct, const int level);
 
+#if HWUI_NEW_OPS
+    ANDROID_API void output(uint32_t level = 0, const char* label = "Root");
+#else
     ANDROID_API void output(uint32_t level = 1);
+#endif
     ANDROID_API int getDebugSize();
     void copyTo(proto::RenderNode* node);
 
diff --git a/libs/hwui/RenderProperties.cpp b/libs/hwui/RenderProperties.cpp
index b848af4..0b0f0fa 100644
--- a/libs/hwui/RenderProperties.cpp
+++ b/libs/hwui/RenderProperties.cpp
@@ -102,22 +102,23 @@
 
 void RenderProperties::debugOutputProperties(const int level) const {
     if (mPrimitiveFields.mLeft != 0 || mPrimitiveFields.mTop != 0) {
-        ALOGD("%*sTranslate (left, top) %d, %d", level * 2, "", mPrimitiveFields.mLeft, mPrimitiveFields.mTop);
+        ALOGD("%*s(Translate (left, top) %d, %d)", level * 2, "",
+                mPrimitiveFields.mLeft, mPrimitiveFields.mTop);
     }
     if (mStaticMatrix) {
-        ALOGD("%*sConcatMatrix (static) %p: " SK_MATRIX_STRING,
+        ALOGD("%*s(ConcatMatrix (static) %p: " SK_MATRIX_STRING ")",
                 level * 2, "", mStaticMatrix, SK_MATRIX_ARGS(mStaticMatrix));
     }
     if (mAnimationMatrix) {
-        ALOGD("%*sConcatMatrix (animation) %p: " SK_MATRIX_STRING,
+        ALOGD("%*s(ConcatMatrix (animation) %p: " SK_MATRIX_STRING ")",
                 level * 2, "", mAnimationMatrix, SK_MATRIX_ARGS(mAnimationMatrix));
     }
     if (hasTransformMatrix()) {
         if (isTransformTranslateOnly()) {
-            ALOGD("%*sTranslate %.2f, %.2f, %.2f",
+            ALOGD("%*s(Translate %.2f, %.2f, %.2f)",
                     level * 2, "", getTranslationX(), getTranslationY(), getZ());
         } else {
-            ALOGD("%*sConcatMatrix %p: " SK_MATRIX_STRING,
+            ALOGD("%*s(ConcatMatrix %p: " SK_MATRIX_STRING ")",
                     level * 2, "", mComputedFields.mTransformMatrix, SK_MATRIX_ARGS(mComputedFields.mTransformMatrix));
         }
     }
@@ -132,7 +133,7 @@
 
         if (CC_LIKELY(isLayer || !getHasOverlappingRendering())) {
             // simply scale rendering content's alpha
-            ALOGD("%*sScaleAlpha %.2f", level * 2, "", mPrimitiveFields.mAlpha);
+            ALOGD("%*s(ScaleAlpha %.2f)", level * 2, "", mPrimitiveFields.mAlpha);
         } else {
             // savelayeralpha to create an offscreen buffer to apply alpha
             Rect layerBounds(0, 0, getWidth(), getHeight());
@@ -140,19 +141,18 @@
                 getClippingRectForFlags(clipFlags, &layerBounds);
                 clipFlags = 0; // all clipping done by savelayer
             }
-            ALOGD("%*sSaveLayerAlpha %d, %d, %d, %d, %d, 0x%x", level * 2, "",
+            ALOGD("%*s(SaveLayerAlpha %d, %d, %d, %d, %d, 0x%x)", level * 2, "",
                     (int)layerBounds.left, (int)layerBounds.top,
                     (int)layerBounds.right, (int)layerBounds.bottom,
                     (int)(mPrimitiveFields.mAlpha * 255),
                     SaveFlags::HasAlphaLayer | SaveFlags::ClipToLayer);
         }
-
-
     }
+
     if (clipFlags) {
         Rect clipRect;
         getClippingRectForFlags(clipFlags, &clipRect);
-        ALOGD("%*sClipRect %d, %d, %d, %d", level * 2, "",
+        ALOGD("%*s(ClipRect %d, %d, %d, %d)", level * 2, "",
                 (int)clipRect.left, (int)clipRect.top, (int)clipRect.right, (int)clipRect.bottom);
     }
 }
diff --git a/libs/hwui/tests/unit/OpDumperTests.cpp b/libs/hwui/tests/unit/OpDumperTests.cpp
new file mode 100644
index 0000000..01840d7
--- /dev/null
+++ b/libs/hwui/tests/unit/OpDumperTests.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#include <gtest/gtest.h>
+
+#include "tests/common/TestUtils.h"
+#include "OpDumper.h"
+
+using namespace android;
+using namespace android::uirenderer;
+
+TEST(OpDumper, dump) {
+    SkPaint paint;
+    RectOp op(uirenderer::Rect(100, 100), Matrix4::identity(), nullptr, &paint);
+
+    std::stringstream stream;
+    OpDumper::dump(op, stream);
+    EXPECT_STREQ("RectOp [100 x 100]", stream.str().c_str());
+
+    stream.str("");
+    OpDumper::dump(op, stream, 2);
+    EXPECT_STREQ("    RectOp [100 x 100]", stream.str().c_str());
+
+    ClipRect clipRect(uirenderer::Rect(50, 50));
+    op.localClip = &clipRect;
+
+    stream.str("");
+    OpDumper::dump(op, stream, 2);
+    EXPECT_STREQ("    RectOp [100 x 100] clip=[50 x 50] mode=0", stream.str().c_str());
+}
diff --git a/libs/hwui/utils/StringUtils.h b/libs/hwui/utils/StringUtils.h
index 05a3d59..5add957 100644
--- a/libs/hwui/utils/StringUtils.h
+++ b/libs/hwui/utils/StringUtils.h
@@ -42,7 +42,7 @@
         static const char* SUFFIXES[] = {"B", "KiB", "MiB"};
         size_t suffix = 0;
         double temp = d.bytes;
-        while (temp > 1000 && suffix < 2) {
+        while (temp > 1024 && suffix < 2) {
             temp /= 1024.0;
             suffix++;
         }