Add a linear variant of the stretch effect

Supports SurfaceViews & everything

Test: setprop debug.hwui.stretch_mode 1
Bug: 187718492
Change-Id: I9a222fa4c1a40e80a74cdaf75becb9524cbeed9b
diff --git a/libs/hwui/DamageAccumulator.cpp b/libs/hwui/DamageAccumulator.cpp
index cf3ef40b..a164233 100644
--- a/libs/hwui/DamageAccumulator.cpp
+++ b/libs/hwui/DamageAccumulator.cpp
@@ -150,9 +150,19 @@
     }
 }
 
+static inline void applyMatrix(const SkMatrix& transform, SkRect* rect) {
+    return applyMatrix(&transform, rect);
+}
+
 static inline void mapRect(const RenderProperties& props, const SkRect& in, SkRect* out) {
     if (in.isEmpty()) return;
     SkRect temp(in);
+    if (Properties::stretchEffectBehavior == StretchEffectBehavior::LinearScale) {
+        const StretchEffect& stretch = props.layerProperties().getStretchEffect();
+        if (!stretch.isEmpty()) {
+            applyMatrix(stretch.makeLinearStretch(props.getWidth(), props.getHeight()), &temp);
+        }
+    }
     applyMatrix(props.getTransformMatrix(), &temp);
     if (props.getStaticMatrix()) {
         applyMatrix(props.getStaticMatrix(), &temp);
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 7af0a22..c58c888 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -84,6 +84,8 @@
 bool Properties::useHintManager = true;
 int Properties::targetCpuTimePercentage = 70;
 
+StretchEffectBehavior Properties::stretchEffectBehavior = StretchEffectBehavior::Shader;
+
 bool Properties::load() {
     bool prevDebugLayersUpdates = debugLayersUpdates;
     bool prevDebugOverdraw = debugOverdraw;
@@ -135,6 +137,10 @@
     targetCpuTimePercentage = base::GetIntProperty(PROPERTY_TARGET_CPU_TIME_PERCENTAGE, 70);
     if (targetCpuTimePercentage <= 0 || targetCpuTimePercentage > 100) targetCpuTimePercentage = 70;
 
+    int stretchType = base::GetIntProperty(PROPERTY_STRETCH_EFFECT_TYPE, 0);
+    stretchType = std::clamp(stretchType, 0, static_cast<int>(StretchEffectBehavior::LinearScale));
+    stretchEffectBehavior = static_cast<StretchEffectBehavior>(stretchType);
+
     return (prevDebugLayersUpdates != debugLayersUpdates) || (prevDebugOverdraw != debugOverdraw);
 }
 
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 1cb87be..d06410e 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -171,6 +171,8 @@
  */
 #define PROPERTY_TARGET_CPU_TIME_PERCENTAGE "debug.hwui.target_cpu_time_percent"
 
+#define PROPERTY_STRETCH_EFFECT_TYPE "debug.hwui.stretch_mode"
+
 /**
  * Property for whether this is running in the emulator.
  */
@@ -197,6 +199,11 @@
 
 enum class RenderPipelineType { SkiaGL, SkiaVulkan, NotInitialized = 128 };
 
+enum class StretchEffectBehavior {
+    Shader,
+    LinearScale,
+};
+
 /**
  * Renderthread-only singleton which manages several static rendering properties. Most of these
  * are driven by system properties which are queried once at initialization, and again if init()
@@ -270,6 +277,8 @@
     static bool useHintManager;
     static int targetCpuTimePercentage;
 
+    static StretchEffectBehavior stretchEffectBehavior;
+
 private:
     static ProfileType sProfileType;
     static bool sDisableProfileBars;
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index fce2e1f..64abd94 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -477,6 +477,14 @@
             }
         }
     }
+
+    if (Properties::stretchEffectBehavior == StretchEffectBehavior::LinearScale) {
+        const StretchEffect& stretch = properties().layerProperties().getStretchEffect();
+        if (!stretch.isEmpty()) {
+            matrix.multiply(
+                    stretch.makeLinearStretch(properties().getWidth(), properties().getHeight()));
+        }
+    }
 }
 
 const SkPath* RenderNode::getClippedOutline(const SkRect& clipRect) const {
diff --git a/libs/hwui/effects/StretchEffect.h b/libs/hwui/effects/StretchEffect.h
index 6d517ac..c49d53a 100644
--- a/libs/hwui/effects/StretchEffect.h
+++ b/libs/hwui/effects/StretchEffect.h
@@ -20,6 +20,7 @@
 
 #include <SkImage.h>
 #include <SkImageFilter.h>
+#include <SkMatrix.h>
 #include <SkPoint.h>
 #include <SkRect.h>
 #include <SkRuntimeEffect.h>
@@ -99,6 +100,14 @@
 
     const SkVector getStretchDirection() const { return mStretchDirection; }
 
+    SkMatrix makeLinearStretch(float width, float height) const {
+        SkMatrix matrix;
+        auto [sX, sY] = getStretchDirection();
+        matrix.setScale(1 + std::abs(sX), 1 + std::abs(sY), sX > 0 ? 0 : width,
+                        sY > 0 ? 0 : height);
+        return matrix;
+    }
+
 private:
     static sk_sp<SkRuntimeEffect> getStretchEffect();
     mutable SkVector mStretchDirection{0, 0};
diff --git a/libs/hwui/jni/android_graphics_RenderNode.cpp b/libs/hwui/jni/android_graphics_RenderNode.cpp
index f92c32c..6123c05 100644
--- a/libs/hwui/jni/android_graphics_RenderNode.cpp
+++ b/libs/hwui/jni/android_graphics_RenderNode.cpp
@@ -573,7 +573,8 @@
             const RenderProperties& props = node.properties();
 
             uirenderer::Rect bounds(props.getWidth(), props.getHeight());
-            if (info.stretchEffectCount) {
+            if (Properties::stretchEffectBehavior == StretchEffectBehavior::Shader &&
+                info.stretchEffectCount) {
                 handleStretchEffect(info, bounds);
             }
 
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
index 1ae06d0..509884e 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
@@ -171,11 +171,16 @@
     displayList->mProjectedOutline = nullptr;
 }
 
+static bool stretchNeedsLayer(const LayerProperties& properties) {
+    return Properties::stretchEffectBehavior == StretchEffectBehavior::Shader &&
+           !properties.getStretchEffect().isEmpty();
+}
+
 static bool layerNeedsPaint(const sk_sp<SkImage>& snapshotImage, const LayerProperties& properties,
                             float alphaMultiplier, SkPaint* paint) {
     if (alphaMultiplier < 1.0f || properties.alpha() < 255 ||
         properties.xferMode() != SkBlendMode::kSrcOver || properties.getColorFilter() != nullptr ||
-        properties.getImageFilter() != nullptr || !properties.getStretchEffect().isEmpty()) {
+        properties.getImageFilter() != nullptr || stretchNeedsLayer(properties)) {
         paint->setAlpha(properties.alpha() * alphaMultiplier);
         paint->setBlendMode(properties.xferMode());
         paint->setColorFilter(sk_ref_sp(properties.getColorFilter()));
@@ -247,7 +252,8 @@
             }
 
             const StretchEffect& stretch = properties.layerProperties().getStretchEffect();
-            if (stretch.isEmpty()) {
+            if (stretch.isEmpty() ||
+                Properties::stretchEffectBehavior != StretchEffectBehavior::Shader) {
                 // If we don't have any stretch effects, issue the filtered
                 // canvas draw calls to make sure we still punch a hole
                 // with the same canvas transformation + clip into the target
@@ -326,6 +332,13 @@
             canvas->concat(*properties.getTransformMatrix());
         }
     }
+    if (Properties::stretchEffectBehavior == StretchEffectBehavior::LinearScale) {
+        const StretchEffect& stretch = properties.layerProperties().getStretchEffect();
+        if (!stretch.isEmpty()) {
+            canvas->concat(
+                    stretch.makeLinearStretch(properties.getWidth(), properties.getHeight()));
+        }
+    }
     const bool isLayer = properties.effectiveLayerType() != LayerType::None;
     int clipFlags = properties.getClippingFlags();
     if (properties.getAlpha() < 1) {