Wire SKSL based stretch shader to HWUI

--Ported SKSL based stretch shader from OpenGL prototype
--Hooked up the stretch APIs in RenderNode to the stretch
shader.
--Updated RenderNode layer logic to promote the RenderNode to
a layer if there is a stretch to be applied to it in order
to feed the layer as input to the stretch shader

Bug: 179047472
Test: builds + sample overscroll stretches + updated CTS test
Change-Id: I744ff70099fe251ce07f23d067bf13444a468c08
diff --git a/libs/hwui/effects/StretchEffect.cpp b/libs/hwui/effects/StretchEffect.cpp
index 51cbc75..d4fd105 100644
--- a/libs/hwui/effects/StretchEffect.cpp
+++ b/libs/hwui/effects/StretchEffect.cpp
@@ -15,13 +15,195 @@
  */
 
 #include "StretchEffect.h"
+#include <SkImageFilter.h>
+#include <SkRefCnt.h>
+#include <SkRuntimeEffect.h>
+#include <SkString.h>
+#include <SkSurface.h>
+#include <include/effects/SkImageFilters.h>
+
+#include <memory>
 
 namespace android::uirenderer {
 
-sk_sp<SkImageFilter> StretchEffect::getImageFilter() const {
-    // TODO: Implement & Cache
-    // Probably need to use mutable to achieve caching
-    return nullptr;
+static const SkString stretchShader = SkString(R"(
+    uniform shader uContentTexture;
+
+    // multiplier to apply to scale effect
+    uniform float uMaxStretchIntensity;
+
+    // Maximum percentage to stretch beyond bounds  of target
+    uniform float uStretchAffectedDist;
+
+    // Distance stretched as a function of the normalized overscroll times
+    // scale intensity
+    uniform float uDistanceStretchedX;
+    uniform float uDistanceStretchedY;
+    uniform float uDistDiffX;
+
+    // Difference between the peak stretch amount and overscroll amount normalized
+    uniform float uDistDiffY;
+
+    // Horizontal offset represented as a ratio of pixels divided by the target width
+    uniform float uScrollX;
+    // Vertical offset represented as a ratio of pixels divided by the target height
+    uniform float uScrollY;
+
+    // Normalized overscroll amount in the horizontal direction
+    uniform float uOverscrollX;
+
+    // Normalized overscroll amount in the vertical direction
+    uniform float uOverscrollY;
+    uniform float viewportWidth; // target height in pixels
+    uniform float viewportHeight; // target width in pixels
+
+    void computeOverscrollStart(
+        out float outPos,
+        float inPos,
+        float overscroll,
+        float uStretchAffectedDist,
+        float distanceStretched
+    ) {
+        float offsetPos = uStretchAffectedDist - inPos;
+        float posBasedVariation = smoothstep(0., uStretchAffectedDist, offsetPos);
+        float stretchIntensity = overscroll * posBasedVariation;
+        outPos = distanceStretched - (offsetPos / (1. + stretchIntensity));
+    }
+
+    void computeOverscrollEnd(
+        out float outPos,
+        float inPos,
+        float overscroll,
+        float reverseStretchDist,
+        float uStretchAffectedDist,
+        float distanceStretched
+    ) {
+        float offsetPos = inPos - reverseStretchDist;
+        float posBasedVariation = (smoothstep(0., uStretchAffectedDist, offsetPos));
+        float stretchIntensity = (-overscroll) * posBasedVariation;
+        outPos = 1 - (distanceStretched - (offsetPos / (1. + stretchIntensity)));
+    }
+
+    void computeOverscroll(
+        out float outPos,
+        float inPos,
+        float overscroll,
+        float uStretchAffectedDist,
+        float distanceStretched,
+        float distanceDiff
+    ) {
+        if (overscroll > 0) {
+            if (inPos <= uStretchAffectedDist) {
+                computeOverscrollStart(
+                  outPos,
+                  inPos,
+                  overscroll,
+                  uStretchAffectedDist,
+                  distanceStretched
+                );
+            } else if (inPos >= distanceStretched) {
+                outPos = distanceDiff + inPos;
+            }
+        }
+        if (overscroll < 0) {
+            float stretchAffectedDist = 1. - uStretchAffectedDist;
+            if (inPos >= stretchAffectedDist) {
+                computeOverscrollEnd(
+                  outPos,
+                  inPos,
+                  overscroll,
+                  stretchAffectedDist,
+                  uStretchAffectedDist,
+                  distanceStretched
+                );
+            } else if (inPos < stretchAffectedDist) {
+                outPos = -distanceDiff + inPos;
+            }
+        }
+    }
+
+    vec4 main(vec2 coord) {
+        // Normalize SKSL pixel coordinate into a unit vector
+        float inU = coord.x / viewportWidth;
+        float inV = coord.y / viewportHeight;
+        float outU;
+        float outV;
+        float stretchIntensity;
+        // Add the normalized scroll position within scrolling list
+        inU += uScrollX;
+        inV += uScrollY;
+        outU = inU;
+        outV = inV;
+        computeOverscroll(
+            outU,
+            inU,
+            uOverscrollX,
+            uStretchAffectedDist,
+            uDistanceStretchedX,
+            uDistDiffX
+        );
+        computeOverscroll(
+            outV,
+            inV,
+            uOverscrollY,
+            uStretchAffectedDist,
+            uDistanceStretchedY,
+            uDistDiffY
+        );
+        coord.x = outU * viewportWidth;
+        coord.y = outV * viewportHeight;
+        return sample(uContentTexture, coord);
+    })");
+
+static const float ZERO = 0.f;
+
+sk_sp<SkImageFilter> StretchEffect::getImageFilter(const sk_sp<SkImage>& snapshotImage) const {
+    if (isEmpty()) {
+        return nullptr;
+    }
+
+    if (mStretchFilter != nullptr) {
+        return mStretchFilter;
+    }
+
+    float distanceNotStretchedX = maxStretchAmount / stretchArea.width();
+    float distanceNotStretchedY = maxStretchAmount / stretchArea.height();
+    float normOverScrollDistX = mStretchDirection.x();
+    float normOverScrollDistY = mStretchDirection.y();
+    float distanceStretchedX = maxStretchAmount / (1 + abs(normOverScrollDistX));
+    float distanceStretchedY = maxStretchAmount / (1 + abs(normOverScrollDistY));
+    float diffX = distanceStretchedX - distanceNotStretchedX;
+    float diffY = distanceStretchedY - distanceNotStretchedY;
+    float viewportWidth = stretchArea.width();
+    float viewportHeight = stretchArea.height();
+
+    if (mBuilder == nullptr) {
+        mBuilder = std::make_unique<SkRuntimeShaderBuilder>(getStretchEffect());
+    }
+
+    mBuilder->child("uContentTexture") = snapshotImage->makeShader(
+            SkTileMode::kClamp, SkTileMode::kClamp, SkSamplingOptions(SkFilterMode::kLinear));
+    mBuilder->uniform("uStretchAffectedDist").set(&maxStretchAmount, 1);
+    mBuilder->uniform("uDistanceStretchedX").set(&distanceStretchedX, 1);
+    mBuilder->uniform("uDistanceStretchedY").set(&distanceStretchedY, 1);
+    mBuilder->uniform("uDistDiffX").set(&diffX, 1);
+    mBuilder->uniform("uDistDiffY").set(&diffY, 1);
+    mBuilder->uniform("uOverscrollX").set(&normOverScrollDistX, 1);
+    mBuilder->uniform("uOverscrollY").set(&normOverScrollDistY, 1);
+    mBuilder->uniform("uScrollX").set(&ZERO, 1);
+    mBuilder->uniform("uScrollY").set(&ZERO, 1);
+    mBuilder->uniform("viewportWidth").set(&viewportWidth, 1);
+    mBuilder->uniform("viewportHeight").set(&viewportHeight, 1);
+
+    mStretchFilter = SkImageFilters::Shader(mBuilder->makeShader(nullptr, false),
+                                            SkRect{0, 0, viewportWidth, viewportHeight});
+
+    return mStretchFilter;
+}
+
+sk_sp<SkRuntimeEffect> StretchEffect::getStretchEffect() {
+    const static SkRuntimeEffect::Result instance = SkRuntimeEffect::Make(stretchShader);
+    return instance.effect;
 }
 
 } // namespace android::uirenderer
\ No newline at end of file
diff --git a/libs/hwui/effects/StretchEffect.h b/libs/hwui/effects/StretchEffect.h
index 7dfd639..d2da06b 100644
--- a/libs/hwui/effects/StretchEffect.h
+++ b/libs/hwui/effects/StretchEffect.h
@@ -18,9 +18,11 @@
 
 #include "utils/MathUtils.h"
 
+#include <SkImage.h>
+#include <SkImageFilter.h>
 #include <SkPoint.h>
 #include <SkRect.h>
-#include <SkImageFilter.h>
+#include <SkRuntimeEffect.h>
 
 namespace android::uirenderer {
 
@@ -31,15 +33,27 @@
         SmoothStep,
     };
 
+    StretchEffect(const SkRect& area, const SkVector& direction, float maxStretchAmount)
+            : stretchArea(area), maxStretchAmount(maxStretchAmount), mStretchDirection(direction) {}
+
+    StretchEffect() {}
+
     bool isEmpty() const {
-        return MathUtils::isZero(stretchDirection.x())
-                && MathUtils::isZero(stretchDirection.y());
+        return MathUtils::isZero(mStretchDirection.x()) && MathUtils::isZero(mStretchDirection.y());
     }
 
     void setEmpty() {
         *this = StretchEffect{};
     }
 
+    StretchEffect& operator=(const StretchEffect& other) {
+        this->stretchArea = other.stretchArea;
+        this->mStretchDirection = other.mStretchDirection;
+        this->mStretchFilter = nullptr;
+        this->maxStretchAmount = other.maxStretchAmount;
+        return *this;
+    }
+
     void mergeWith(const StretchEffect& other) {
         if (other.isEmpty()) {
             return;
@@ -48,7 +62,7 @@
             *this = other;
             return;
         }
-        stretchDirection += other.stretchDirection;
+        setStretchDirection(mStretchDirection + other.mStretchDirection);
         if (isEmpty()) {
             return setEmpty();
         }
@@ -56,11 +70,23 @@
         maxStretchAmount = std::max(maxStretchAmount, other.maxStretchAmount);
     }
 
-    sk_sp<SkImageFilter> getImageFilter() const;
+    sk_sp<SkImageFilter> getImageFilter(const sk_sp<SkImage>& snapshotImage) const;
 
     SkRect stretchArea {0, 0, 0, 0};
-    SkVector stretchDirection {0, 0};
     float maxStretchAmount = 0;
+
+    void setStretchDirection(const SkVector& direction) {
+        mStretchFilter = nullptr;
+        mStretchDirection = direction;
+    }
+
+    const SkVector getStretchDirection() const { return mStretchDirection; }
+
+private:
+    static sk_sp<SkRuntimeEffect> getStretchEffect();
+    mutable SkVector mStretchDirection{0, 0};
+    mutable std::unique_ptr<SkRuntimeShaderBuilder> mBuilder;
+    mutable sk_sp<SkImageFilter> mStretchFilter;
 };
 
 } // namespace android::uirenderer