Merge "Add more RenderNode property support in OpReorderer path"
diff --git a/libs/hwui/OpReorderer.cpp b/libs/hwui/OpReorderer.cpp
index 163f7cc..c6b1609 100644
--- a/libs/hwui/OpReorderer.cpp
+++ b/libs/hwui/OpReorderer.cpp
@@ -359,7 +359,7 @@
 void OpReorderer::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {}
 
 void OpReorderer::deferNodePropsAndOps(RenderNode& node) {
-    if (node.applyViewProperties(mCanvasState)) {
+    if (node.applyViewProperties(mCanvasState, mAllocator)) {
         // not rejected so render
         if (node.getLayer()) {
             // HW layer
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 0601944..15ca718 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -282,9 +282,11 @@
             damageSelf(info);
             transformUpdateNeeded = true;
 #if HWUI_NEW_OPS
-    } else if (mLayer->viewportWidth != getWidth() || mLayer->viewportHeight != getHeight()) {
-        // TODO: allow it to grow larger
-        if (getWidth() > mLayer->texture.width || getHeight() > mLayer->texture.height) {
+    } else if (mLayer->viewportWidth != (uint32_t) getWidth()
+            || mLayer->viewportHeight != (uint32_t)getHeight()) {
+        // TODO: allow node's layer to grow larger
+        if ((uint32_t)getWidth() > mLayer->texture.width
+                || (uint32_t)getHeight() > mLayer->texture.height) {
 #else
     } else if (mLayer->layer.getWidth() != getWidth() || mLayer->layer.getHeight() != getHeight()) {
         if (!LayerRenderer::resizeLayer(mLayer, getWidth(), getHeight())) {
@@ -304,7 +306,7 @@
         if (info.errorHandler) {
             std::ostringstream err;
             err << "Unable to create layer for " << getName();
-            const uint32_t  maxTextureSize = Caches::getInstance().maxTextureSize;
+            const int maxTextureSize = Caches::getInstance().maxTextureSize;
             if (getWidth() > maxTextureSize || getHeight() > maxTextureSize) {
                 err << ", size " << getWidth() << "x" << getHeight()
                         << " exceeds max size " << maxTextureSize;
@@ -518,7 +520,7 @@
     }
 }
 
-bool RenderNode::applyViewProperties(CanvasState& canvasState) const {
+bool RenderNode::applyViewProperties(CanvasState& canvasState, LinearAllocator& allocator) const {
     const Outline& outline = properties().getOutline();
     if (properties().getAlpha() <= 0
             || (outline.getShouldClip() && outline.isEmpty())
@@ -542,6 +544,48 @@
             canvasState.concatMatrix(*properties().getTransformMatrix());
         }
     }
+
+    const bool isLayer = properties().effectiveLayerType() != LayerType::None;
+    int clipFlags = properties().getClippingFlags();
+    if (properties().getAlpha() < 1) {
+        if (isLayer) {
+            clipFlags &= ~CLIP_TO_BOUNDS; // bounds clipping done by layer
+        }
+        if (CC_LIKELY(isLayer || !properties().getHasOverlappingRendering())) {
+            // simply scale rendering content's alpha
+            canvasState.scaleAlpha(properties().getAlpha());
+        } else {
+            // savelayer needed to create an offscreen buffer
+            Rect layerBounds(0, 0, getWidth(), getHeight());
+            if (clipFlags) {
+                properties().getClippingRectForFlags(clipFlags, &layerBounds);
+                clipFlags = 0; // all clipping done by savelayer
+            }
+            LOG_ALWAYS_FATAL("TODO: savelayer");
+        }
+
+        if (CC_UNLIKELY(ATRACE_ENABLED() && properties().promotedToLayer())) {
+            // pretend alpha always causes savelayer to warn about
+            // performance problem affecting old versions
+            ATRACE_FORMAT("%s alpha caused saveLayer %dx%d", getName(), getWidth(), getHeight());
+        }
+    }
+    if (clipFlags) {
+        Rect clipRect;
+        properties().getClippingRectForFlags(clipFlags, &clipRect);
+        canvasState.clipRect(clipRect.left, clipRect.top, clipRect.right, clipRect.bottom,
+                SkRegion::kIntersect_Op);
+    }
+
+    // TODO: support nesting round rect clips
+    if (mProperties.getRevealClip().willClip()) {
+        Rect bounds;
+        mProperties.getRevealClip().getBounds(&bounds);
+        canvasState.setClippingRoundRect(allocator,
+                bounds, mProperties.getRevealClip().getRadius());
+    } else if (mProperties.getOutline().willClip()) {
+        canvasState.setClippingOutline(allocator, &(mProperties.getOutline()));
+    }
     return !canvasState.quickRejectConservative(
             0, 0, properties().getWidth(), properties().getHeight());
 }
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index 3500cb2..2c25751 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -171,11 +171,11 @@
         return mStagingProperties;
     }
 
-    uint32_t getWidth() {
+    int getWidth() const {
         return properties().getWidth();
     }
 
-    uint32_t getHeight() {
+    int getHeight() const {
         return properties().getHeight();
     }
 
@@ -188,7 +188,7 @@
     AnimatorManager& animators() { return mAnimatorManager; }
 
     // Returns false if the properties dictate the subtree contained in this RenderNode won't render
-    bool applyViewProperties(CanvasState& canvasState) const;
+    bool applyViewProperties(CanvasState& canvasState, LinearAllocator& allocator) const;
 
     void applyViewPropertyTransforms(mat4& matrix, bool true3dTransform = false) const;
 
diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h
index abef806..ca7789e 100644
--- a/libs/hwui/RenderProperties.h
+++ b/libs/hwui/RenderProperties.h
@@ -16,23 +16,24 @@
 #ifndef RENDERNODEPROPERTIES_H
 #define RENDERNODEPROPERTIES_H
 
-#include <algorithm>
-#include <stddef.h>
-#include <vector>
-#include <cutils/compiler.h>
-#include <androidfw/ResourceTypes.h>
-#include <utils/Log.h>
+#include "Caches.h"
+#include "DeviceInfo.h"
+#include "Rect.h"
+#include "RevealClip.h"
+#include "Outline.h"
+#include "utils/MathUtils.h"
 
 #include <SkCamera.h>
 #include <SkMatrix.h>
 #include <SkRegion.h>
 #include <SkXfermode.h>
 
-#include "Caches.h"
-#include "Rect.h"
-#include "RevealClip.h"
-#include "Outline.h"
-#include "utils/MathUtils.h"
+#include <algorithm>
+#include <stddef.h>
+#include <vector>
+#include <cutils/compiler.h>
+#include <androidfw/ResourceTypes.h>
+#include <utils/Log.h>
 
 class SkBitmap;
 class SkColorFilter;
@@ -608,10 +609,11 @@
     }
 
     bool promotedToLayer() const {
-        const int maxTextureSize = Caches::getInstance().maxTextureSize;
+        const DeviceInfo* deviceInfo = DeviceInfo::get();
+        LOG_ALWAYS_FATAL_IF(!deviceInfo, "DeviceInfo uninitialized");
         return mLayerProperties.mType == LayerType::None
-                && mPrimitiveFields.mWidth <= maxTextureSize
-                && mPrimitiveFields.mHeight <= maxTextureSize
+                && mPrimitiveFields.mWidth <= deviceInfo->maxTextureSize()
+                && mPrimitiveFields.mHeight <= deviceInfo->maxTextureSize()
                 && (mComputedFields.mNeedLayerForFunctors
                         || (!MathUtils::isZero(mPrimitiveFields.mAlpha)
                                 && mPrimitiveFields.mAlpha < 1
diff --git a/libs/hwui/unit_tests/DeviceInfoTests.cpp b/libs/hwui/unit_tests/DeviceInfoTests.cpp
index c3c68ae..17236bd 100644
--- a/libs/hwui/unit_tests/DeviceInfoTests.cpp
+++ b/libs/hwui/unit_tests/DeviceInfoTests.cpp
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-
-#include "DeviceInfo.h"
+#include <DeviceInfo.h>
 
 #include <gtest/gtest.h>
 
@@ -23,10 +22,9 @@
 using namespace android::uirenderer;
 
 TEST(DeviceInfo, basic) {
-    const DeviceInfo* di = DeviceInfo::get();
-    EXPECT_EQ(nullptr, di) << "DeviceInfo was already initialized?";
+    // can't assert state before init - another test may have initialized the singleton
     DeviceInfo::initialize();
-    di = DeviceInfo::get();
+    const DeviceInfo* di = DeviceInfo::get();
     ASSERT_NE(nullptr, di) << "DeviceInfo initialization failed";
     EXPECT_EQ(2048, di->maxTextureSize()) << "Max texture size didn't match";
 }
diff --git a/libs/hwui/unit_tests/OpReordererTests.cpp b/libs/hwui/unit_tests/OpReordererTests.cpp
index 09b10c3..50f210f 100644
--- a/libs/hwui/unit_tests/OpReordererTests.cpp
+++ b/libs/hwui/unit_tests/OpReordererTests.cpp
@@ -527,5 +527,120 @@
     *(parent->getLayerHandle()) = nullptr;
 }
 
+
+class PropertyTestRenderer : public TestRendererBase {
+public:
+    PropertyTestRenderer(std::function<void(const RectOp&, const BakedOpState&)> callback)
+            : mCallback(callback) {}
+    void onRectOp(const RectOp& op, const BakedOpState& state) override {
+        EXPECT_EQ(mIndex++, 0);
+        mCallback(op, state);
+    }
+    std::function<void(const RectOp&, const BakedOpState&)> mCallback;
+};
+
+static void testProperty(
+        std::function<int(RenderProperties&)> propSetupCallback,
+        std::function<void(const RectOp&, const BakedOpState&)> opValidateCallback) {
+    auto node = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100, [](RecordingCanvas& canvas) {
+        SkPaint paint;
+        paint.setColor(SK_ColorWHITE);
+        canvas.drawRect(0, 0, 100, 100, paint);
+    });
+    node->setPropertyFieldsDirty(propSetupCallback(node->mutateStagingProperties()));
+    TestUtils::syncNodePropertiesAndDisplayList(node);
+
+    std::vector< sp<RenderNode> > nodes;
+    nodes.push_back(node.get());
+
+    OpReorderer reorderer(sEmptyLayerUpdateQueue,
+            SkRect::MakeWH(100, 100), 200, 200, nodes);
+
+    PropertyTestRenderer renderer(opValidateCallback);
+    reorderer.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(1, renderer.getIndex()) << "Should have seen one op";
+}
+
+TEST(OpReorderer, renderPropOverlappingRenderingAlpha) {
+    testProperty([](RenderProperties& properties) {
+        properties.setAlpha(0.5f);
+        properties.setHasOverlappingRendering(false);
+        return RenderNode::ALPHA | RenderNode::GENERIC;
+    }, [](const RectOp& op, const BakedOpState& state) {
+        EXPECT_EQ(0.5f, state.alpha) << "Alpha should be applied directly to op";
+    });
+}
+
+TEST(OpReorderer, renderPropClipping) {
+    testProperty([](RenderProperties& properties) {
+        properties.setClipToBounds(true);
+        properties.setClipBounds(Rect(10, 20, 300, 400));
+        return RenderNode::GENERIC;
+    }, [](const RectOp& op, const BakedOpState& state) {
+        EXPECT_EQ(Rect(10, 20, 100, 100), state.computedState.clippedBounds)
+                << "Clip rect should be intersection of node bounds and clip bounds";
+    });
+}
+
+TEST(OpReorderer, renderPropRevealClip) {
+    testProperty([](RenderProperties& properties) {
+        properties.mutableRevealClip().set(true, 50, 50, 25);
+        return RenderNode::GENERIC;
+    }, [](const RectOp& op, const BakedOpState& state) {
+        ASSERT_NE(nullptr, state.roundRectClipState);
+        EXPECT_TRUE(state.roundRectClipState->highPriority);
+        EXPECT_EQ(25, state.roundRectClipState->radius);
+        EXPECT_EQ(Rect(50, 50, 50, 50), state.roundRectClipState->innerRect);
+    });
+}
+
+TEST(OpReorderer, renderPropOutlineClip) {
+    testProperty([](RenderProperties& properties) {
+        properties.mutableOutline().setShouldClip(true);
+        properties.mutableOutline().setRoundRect(10, 20, 30, 40, 5.0f, 0.5f);
+        return RenderNode::GENERIC;
+    }, [](const RectOp& op, const BakedOpState& state) {
+        ASSERT_NE(nullptr, state.roundRectClipState);
+        EXPECT_FALSE(state.roundRectClipState->highPriority);
+        EXPECT_EQ(5, state.roundRectClipState->radius);
+        EXPECT_EQ(Rect(15, 25, 25, 35), state.roundRectClipState->innerRect);
+    });
+}
+
+TEST(OpReorderer, renderPropTransform) {
+    testProperty([](RenderProperties& properties) {
+        properties.setLeftTopRightBottom(10, 10, 110, 110);
+
+        SkMatrix staticMatrix = SkMatrix::MakeScale(1.2f, 1.2f);
+        properties.setStaticMatrix(&staticMatrix);
+
+        // ignored, since static overrides animation
+        SkMatrix animationMatrix = SkMatrix::MakeTrans(15, 15);
+        properties.setAnimationMatrix(&animationMatrix);
+
+        properties.setTranslationX(10);
+        properties.setTranslationY(20);
+        properties.setScaleX(0.5f);
+        properties.setScaleY(0.7f);
+        return RenderNode::GENERIC
+                | RenderNode::TRANSLATION_X | RenderNode::TRANSLATION_Y
+                | RenderNode::SCALE_X | RenderNode::SCALE_Y;
+    }, [](const RectOp& op, const BakedOpState& state) {
+        Matrix4 matrix;
+        matrix.loadTranslate(10, 10, 0); // left, top
+        matrix.scale(1.2f, 1.2f, 1); // static matrix
+        // ignore animation matrix, since static overrides it
+
+        // translation xy
+        matrix.translate(10, 20);
+
+        // scale xy (from default pivot - center)
+        matrix.translate(50, 50);
+        matrix.scale(0.5f, 0.7f, 1);
+        matrix.translate(-50, -50);
+        EXPECT_MATRIX_APPROX_EQ(matrix, state.computedState.transform)
+                << "Op draw matrix must match expected combination of transformation properties";
+    });
+}
 } // namespace uirenderer
 } // namespace android
diff --git a/libs/hwui/unit_tests/TestUtils.h b/libs/hwui/unit_tests/TestUtils.h
index 770f413..0cf8040 100644
--- a/libs/hwui/unit_tests/TestUtils.h
+++ b/libs/hwui/unit_tests/TestUtils.h
@@ -16,6 +16,7 @@
 #ifndef TEST_UTILS_H
 #define TEST_UTILS_H
 
+#include <DeviceInfo.h>
 #include <Matrix.h>
 #include <Rect.h>
 #include <RenderNode.h>
@@ -90,6 +91,10 @@
     }
 
     static sp<RenderNode> createNode(int left, int top, int right, int bottom, bool onLayer = false) {
+        // if RenderNodes are being sync'd/used, device info will be needed, since
+        // DeviceInfo::maxTextureSize() affects layer property
+        DeviceInfo::initialize();
+
         sp<RenderNode> node = new RenderNode();
         node->mutateStagingProperties().setLeftTopRightBottom(left, top, right, bottom);
         node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);