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/RenderProperties.h b/libs/hwui/RenderProperties.h
index 609706e..5540e2d 100644
--- a/libs/hwui/RenderProperties.h
+++ b/libs/hwui/RenderProperties.h
@@ -552,8 +552,8 @@
 
     bool promotedToLayer() const {
         return mLayerProperties.mType == LayerType::None && fitsOnLayer() &&
-               (mComputedFields.mNeedLayerForFunctors ||
-                mLayerProperties.mImageFilter != nullptr ||
+               (mComputedFields.mNeedLayerForFunctors || mLayerProperties.mImageFilter != nullptr ||
+                !mLayerProperties.getStretchEffect().isEmpty() ||
                 (!MathUtils::isZero(mPrimitiveFields.mAlpha) && mPrimitiveFields.mAlpha < 1 &&
                  mPrimitiveFields.mHasOverlappingRendering));
     }
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
diff --git a/libs/hwui/jni/android_graphics_RenderNode.cpp b/libs/hwui/jni/android_graphics_RenderNode.cpp
index 5f60437..fc7d0d1 100644
--- a/libs/hwui/jni/android_graphics_RenderNode.cpp
+++ b/libs/hwui/jni/android_graphics_RenderNode.cpp
@@ -180,14 +180,13 @@
 }
 
 static jboolean android_view_RenderNode_stretch(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr,
-        jfloat left, jfloat top, jfloat right, jfloat bottom, jfloat vX, jfloat vY, jfloat max) {
+                                                jfloat left, jfloat top, jfloat right,
+                                                jfloat bottom, jfloat vX, jfloat vY, jfloat max) {
+    StretchEffect effect =
+            StretchEffect(SkRect::MakeLTRB(left, top, right, bottom), {.fX = vX, .fY = vY}, max);
     RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
     renderNode->mutateStagingProperties().mutateLayerProperties().mutableStretchEffect().mergeWith(
-            StretchEffect{
-        .stretchArea = SkRect::MakeLTRB(left, top, right, bottom),
-        .stretchDirection = {.fX = vX, .fY = vY},
-        .maxStretchAmount = max
-    });
+            effect);
     renderNode->setPropertyFieldsDirty(RenderNode::GENERIC);
     return true;
 }
@@ -659,10 +658,11 @@
                 return;
             }
 #ifdef __ANDROID__  // Layoutlib does not support CanvasContext
+            SkVector stretchDirection = effect->getStretchDirection();
             env->CallVoidMethod(localref, gPositionListener_ApplyStretchMethod,
                                 info.canvasContext.getFrameNumber(), area.left, area.top,
-                                area.right, area.bottom, effect->stretchDirection.fX,
-                                effect->stretchDirection.fY, effect->maxStretchAmount);
+                                area.right, area.bottom, stretchDirection.fX, stretchDirection.fY,
+                                effect->maxStretchAmount);
 #endif
             env->DeleteLocalRef(localref);
         }
@@ -702,106 +702,110 @@
 const char* const kClassPathName = "android/graphics/RenderNode";
 
 static const JNINativeMethod gMethods[] = {
-// ----------------------------------------------------------------------------
-// Regular JNI
-// ----------------------------------------------------------------------------
-    { "nCreate",               "(Ljava/lang/String;)J", (void*) android_view_RenderNode_create },
-    { "nGetNativeFinalizer",   "()J",    (void*) android_view_RenderNode_getNativeFinalizer },
-    { "nOutput",               "(J)V",    (void*) android_view_RenderNode_output },
-    { "nGetUsageSize",         "(J)I",    (void*) android_view_RenderNode_getUsageSize },
-    { "nGetAllocatedSize",         "(J)I",    (void*) android_view_RenderNode_getAllocatedSize },
-    { "nAddAnimator",              "(JJ)V", (void*) android_view_RenderNode_addAnimator },
-    { "nEndAllAnimators",          "(J)V", (void*) android_view_RenderNode_endAllAnimators },
-    { "nRequestPositionUpdates",   "(JLandroid/graphics/RenderNode$PositionUpdateListener;)V", (void*) android_view_RenderNode_requestPositionUpdates },
+        // ----------------------------------------------------------------------------
+        // Regular JNI
+        // ----------------------------------------------------------------------------
+        {"nCreate", "(Ljava/lang/String;)J", (void*)android_view_RenderNode_create},
+        {"nGetNativeFinalizer", "()J", (void*)android_view_RenderNode_getNativeFinalizer},
+        {"nOutput", "(J)V", (void*)android_view_RenderNode_output},
+        {"nGetUsageSize", "(J)I", (void*)android_view_RenderNode_getUsageSize},
+        {"nGetAllocatedSize", "(J)I", (void*)android_view_RenderNode_getAllocatedSize},
+        {"nAddAnimator", "(JJ)V", (void*)android_view_RenderNode_addAnimator},
+        {"nEndAllAnimators", "(J)V", (void*)android_view_RenderNode_endAllAnimators},
+        {"nRequestPositionUpdates", "(JLandroid/graphics/RenderNode$PositionUpdateListener;)V",
+         (void*)android_view_RenderNode_requestPositionUpdates},
 
-// ----------------------------------------------------------------------------
-// Critical JNI via @CriticalNative annotation in RenderNode.java
-// ----------------------------------------------------------------------------
-    { "nDiscardDisplayList",   "(J)V",   (void*) android_view_RenderNode_discardDisplayList },
-    { "nIsValid",              "(J)Z",   (void*) android_view_RenderNode_isValid },
-    { "nSetLayerType",         "(JI)Z",  (void*) android_view_RenderNode_setLayerType },
-    { "nGetLayerType",         "(J)I",   (void*) android_view_RenderNode_getLayerType },
-    { "nSetLayerPaint",        "(JJ)Z",  (void*) android_view_RenderNode_setLayerPaint },
-    { "nSetStaticMatrix",      "(JJ)Z",  (void*) android_view_RenderNode_setStaticMatrix },
-    { "nSetAnimationMatrix",   "(JJ)Z",  (void*) android_view_RenderNode_setAnimationMatrix },
-    { "nGetAnimationMatrix",   "(JJ)Z",  (void*) android_view_RenderNode_getAnimationMatrix },
-    { "nSetClipToBounds",      "(JZ)Z",  (void*) android_view_RenderNode_setClipToBounds },
-    { "nGetClipToBounds",      "(J)Z",   (void*) android_view_RenderNode_getClipToBounds },
-    { "nSetClipBounds",        "(JIIII)Z", (void*) android_view_RenderNode_setClipBounds },
-    { "nSetClipBoundsEmpty",   "(J)Z",   (void*) android_view_RenderNode_setClipBoundsEmpty },
-    { "nSetProjectBackwards",  "(JZ)Z",  (void*) android_view_RenderNode_setProjectBackwards },
-    { "nSetProjectionReceiver","(JZ)Z",  (void*) android_view_RenderNode_setProjectionReceiver },
+        // ----------------------------------------------------------------------------
+        // Critical JNI via @CriticalNative annotation in RenderNode.java
+        // ----------------------------------------------------------------------------
+        {"nDiscardDisplayList", "(J)V", (void*)android_view_RenderNode_discardDisplayList},
+        {"nIsValid", "(J)Z", (void*)android_view_RenderNode_isValid},
+        {"nSetLayerType", "(JI)Z", (void*)android_view_RenderNode_setLayerType},
+        {"nGetLayerType", "(J)I", (void*)android_view_RenderNode_getLayerType},
+        {"nSetLayerPaint", "(JJ)Z", (void*)android_view_RenderNode_setLayerPaint},
+        {"nSetStaticMatrix", "(JJ)Z", (void*)android_view_RenderNode_setStaticMatrix},
+        {"nSetAnimationMatrix", "(JJ)Z", (void*)android_view_RenderNode_setAnimationMatrix},
+        {"nGetAnimationMatrix", "(JJ)Z", (void*)android_view_RenderNode_getAnimationMatrix},
+        {"nSetClipToBounds", "(JZ)Z", (void*)android_view_RenderNode_setClipToBounds},
+        {"nGetClipToBounds", "(J)Z", (void*)android_view_RenderNode_getClipToBounds},
+        {"nSetClipBounds", "(JIIII)Z", (void*)android_view_RenderNode_setClipBounds},
+        {"nSetClipBoundsEmpty", "(J)Z", (void*)android_view_RenderNode_setClipBoundsEmpty},
+        {"nSetProjectBackwards", "(JZ)Z", (void*)android_view_RenderNode_setProjectBackwards},
+        {"nSetProjectionReceiver", "(JZ)Z", (void*)android_view_RenderNode_setProjectionReceiver},
 
-    { "nSetOutlineRoundRect",  "(JIIIIFF)Z", (void*) android_view_RenderNode_setOutlineRoundRect },
-    { "nSetOutlinePath",       "(JJF)Z", (void*) android_view_RenderNode_setOutlinePath },
-    { "nSetOutlineEmpty",      "(J)Z",   (void*) android_view_RenderNode_setOutlineEmpty },
-    { "nSetOutlineNone",       "(J)Z",   (void*) android_view_RenderNode_setOutlineNone },
-    { "nClearStretch",         "(J)Z",   (void*) android_view_RenderNode_clearStretch },
-    { "nStretch",              "(JFFFFFFF)Z",   (void*) android_view_RenderNode_stretch },
-    { "nHasShadow",            "(J)Z",   (void*) android_view_RenderNode_hasShadow },
-    { "nSetSpotShadowColor",   "(JI)Z",  (void*) android_view_RenderNode_setSpotShadowColor },
-    { "nGetSpotShadowColor",   "(J)I",   (void*) android_view_RenderNode_getSpotShadowColor },
-    { "nSetAmbientShadowColor","(JI)Z",  (void*) android_view_RenderNode_setAmbientShadowColor },
-    { "nGetAmbientShadowColor","(J)I",   (void*) android_view_RenderNode_getAmbientShadowColor },
-    { "nSetClipToOutline",     "(JZ)Z",  (void*) android_view_RenderNode_setClipToOutline },
-    { "nSetRevealClip",        "(JZFFF)Z", (void*) android_view_RenderNode_setRevealClip },
+        {"nSetOutlineRoundRect", "(JIIIIFF)Z", (void*)android_view_RenderNode_setOutlineRoundRect},
+        {"nSetOutlinePath", "(JJF)Z", (void*)android_view_RenderNode_setOutlinePath},
+        {"nSetOutlineEmpty", "(J)Z", (void*)android_view_RenderNode_setOutlineEmpty},
+        {"nSetOutlineNone", "(J)Z", (void*)android_view_RenderNode_setOutlineNone},
+        {"nClearStretch", "(J)Z", (void*)android_view_RenderNode_clearStretch},
+        {"nStretch", "(JFFFFFFF)Z", (void*)android_view_RenderNode_stretch},
+        {"nHasShadow", "(J)Z", (void*)android_view_RenderNode_hasShadow},
+        {"nSetSpotShadowColor", "(JI)Z", (void*)android_view_RenderNode_setSpotShadowColor},
+        {"nGetSpotShadowColor", "(J)I", (void*)android_view_RenderNode_getSpotShadowColor},
+        {"nSetAmbientShadowColor", "(JI)Z", (void*)android_view_RenderNode_setAmbientShadowColor},
+        {"nGetAmbientShadowColor", "(J)I", (void*)android_view_RenderNode_getAmbientShadowColor},
+        {"nSetClipToOutline", "(JZ)Z", (void*)android_view_RenderNode_setClipToOutline},
+        {"nSetRevealClip", "(JZFFF)Z", (void*)android_view_RenderNode_setRevealClip},
 
-    { "nSetAlpha",             "(JF)Z",  (void*) android_view_RenderNode_setAlpha },
-    { "nSetRenderEffect",      "(JJ)Z",  (void*) android_view_RenderNode_setRenderEffect },
-    { "nSetHasOverlappingRendering", "(JZ)Z",
-            (void*) android_view_RenderNode_setHasOverlappingRendering },
-    { "nSetUsageHint",    "(JI)V", (void*) android_view_RenderNode_setUsageHint },
-    { "nSetElevation",         "(JF)Z",  (void*) android_view_RenderNode_setElevation },
-    { "nSetTranslationX",      "(JF)Z",  (void*) android_view_RenderNode_setTranslationX },
-    { "nSetTranslationY",      "(JF)Z",  (void*) android_view_RenderNode_setTranslationY },
-    { "nSetTranslationZ",      "(JF)Z",  (void*) android_view_RenderNode_setTranslationZ },
-    { "nSetRotation",          "(JF)Z",  (void*) android_view_RenderNode_setRotation },
-    { "nSetRotationX",         "(JF)Z",  (void*) android_view_RenderNode_setRotationX },
-    { "nSetRotationY",         "(JF)Z",  (void*) android_view_RenderNode_setRotationY },
-    { "nSetScaleX",            "(JF)Z",  (void*) android_view_RenderNode_setScaleX },
-    { "nSetScaleY",            "(JF)Z",  (void*) android_view_RenderNode_setScaleY },
-    { "nSetPivotX",            "(JF)Z",  (void*) android_view_RenderNode_setPivotX },
-    { "nSetPivotY",            "(JF)Z",  (void*) android_view_RenderNode_setPivotY },
-    { "nResetPivot",           "(J)Z",   (void*) android_view_RenderNode_resetPivot },
-    { "nSetCameraDistance",    "(JF)Z",  (void*) android_view_RenderNode_setCameraDistance },
-    { "nSetLeft",              "(JI)Z",  (void*) android_view_RenderNode_setLeft },
-    { "nSetTop",               "(JI)Z",  (void*) android_view_RenderNode_setTop },
-    { "nSetRight",             "(JI)Z",  (void*) android_view_RenderNode_setRight },
-    { "nSetBottom",            "(JI)Z",  (void*) android_view_RenderNode_setBottom },
-    { "nGetLeft",              "(J)I",  (void*) android_view_RenderNode_getLeft },
-    { "nGetTop",               "(J)I",  (void*) android_view_RenderNode_getTop },
-    { "nGetRight",             "(J)I",  (void*) android_view_RenderNode_getRight },
-    { "nGetBottom",            "(J)I",  (void*) android_view_RenderNode_getBottom },
-    { "nSetLeftTopRightBottom","(JIIII)Z", (void*) android_view_RenderNode_setLeftTopRightBottom },
-    { "nOffsetLeftAndRight",   "(JI)Z",  (void*) android_view_RenderNode_offsetLeftAndRight },
-    { "nOffsetTopAndBottom",   "(JI)Z",  (void*) android_view_RenderNode_offsetTopAndBottom },
+        {"nSetAlpha", "(JF)Z", (void*)android_view_RenderNode_setAlpha},
+        {"nSetRenderEffect", "(JJ)Z", (void*)android_view_RenderNode_setRenderEffect},
+        {"nSetHasOverlappingRendering", "(JZ)Z",
+         (void*)android_view_RenderNode_setHasOverlappingRendering},
+        {"nSetUsageHint", "(JI)V", (void*)android_view_RenderNode_setUsageHint},
+        {"nSetElevation", "(JF)Z", (void*)android_view_RenderNode_setElevation},
+        {"nSetTranslationX", "(JF)Z", (void*)android_view_RenderNode_setTranslationX},
+        {"nSetTranslationY", "(JF)Z", (void*)android_view_RenderNode_setTranslationY},
+        {"nSetTranslationZ", "(JF)Z", (void*)android_view_RenderNode_setTranslationZ},
+        {"nSetRotation", "(JF)Z", (void*)android_view_RenderNode_setRotation},
+        {"nSetRotationX", "(JF)Z", (void*)android_view_RenderNode_setRotationX},
+        {"nSetRotationY", "(JF)Z", (void*)android_view_RenderNode_setRotationY},
+        {"nSetScaleX", "(JF)Z", (void*)android_view_RenderNode_setScaleX},
+        {"nSetScaleY", "(JF)Z", (void*)android_view_RenderNode_setScaleY},
+        {"nSetPivotX", "(JF)Z", (void*)android_view_RenderNode_setPivotX},
+        {"nSetPivotY", "(JF)Z", (void*)android_view_RenderNode_setPivotY},
+        {"nResetPivot", "(J)Z", (void*)android_view_RenderNode_resetPivot},
+        {"nSetCameraDistance", "(JF)Z", (void*)android_view_RenderNode_setCameraDistance},
+        {"nSetLeft", "(JI)Z", (void*)android_view_RenderNode_setLeft},
+        {"nSetTop", "(JI)Z", (void*)android_view_RenderNode_setTop},
+        {"nSetRight", "(JI)Z", (void*)android_view_RenderNode_setRight},
+        {"nSetBottom", "(JI)Z", (void*)android_view_RenderNode_setBottom},
+        {"nGetLeft", "(J)I", (void*)android_view_RenderNode_getLeft},
+        {"nGetTop", "(J)I", (void*)android_view_RenderNode_getTop},
+        {"nGetRight", "(J)I", (void*)android_view_RenderNode_getRight},
+        {"nGetBottom", "(J)I", (void*)android_view_RenderNode_getBottom},
+        {"nSetLeftTopRightBottom", "(JIIII)Z",
+         (void*)android_view_RenderNode_setLeftTopRightBottom},
+        {"nOffsetLeftAndRight", "(JI)Z", (void*)android_view_RenderNode_offsetLeftAndRight},
+        {"nOffsetTopAndBottom", "(JI)Z", (void*)android_view_RenderNode_offsetTopAndBottom},
 
-    { "nHasOverlappingRendering", "(J)Z",  (void*) android_view_RenderNode_hasOverlappingRendering },
-    { "nGetClipToOutline",        "(J)Z",  (void*) android_view_RenderNode_getClipToOutline },
-    { "nGetAlpha",                "(J)F",  (void*) android_view_RenderNode_getAlpha },
-    { "nGetCameraDistance",       "(J)F",  (void*) android_view_RenderNode_getCameraDistance },
-    { "nGetScaleX",               "(J)F",  (void*) android_view_RenderNode_getScaleX },
-    { "nGetScaleY",               "(J)F",  (void*) android_view_RenderNode_getScaleY },
-    { "nGetElevation",            "(J)F",  (void*) android_view_RenderNode_getElevation },
-    { "nGetTranslationX",         "(J)F",  (void*) android_view_RenderNode_getTranslationX },
-    { "nGetTranslationY",         "(J)F",  (void*) android_view_RenderNode_getTranslationY },
-    { "nGetTranslationZ",         "(J)F",  (void*) android_view_RenderNode_getTranslationZ },
-    { "nGetRotation",             "(J)F",  (void*) android_view_RenderNode_getRotation },
-    { "nGetRotationX",            "(J)F",  (void*) android_view_RenderNode_getRotationX },
-    { "nGetRotationY",            "(J)F",  (void*) android_view_RenderNode_getRotationY },
-    { "nIsPivotExplicitlySet",    "(J)Z",  (void*) android_view_RenderNode_isPivotExplicitlySet },
-    { "nHasIdentityMatrix",       "(J)Z",  (void*) android_view_RenderNode_hasIdentityMatrix },
+        {"nHasOverlappingRendering", "(J)Z",
+         (void*)android_view_RenderNode_hasOverlappingRendering},
+        {"nGetClipToOutline", "(J)Z", (void*)android_view_RenderNode_getClipToOutline},
+        {"nGetAlpha", "(J)F", (void*)android_view_RenderNode_getAlpha},
+        {"nGetCameraDistance", "(J)F", (void*)android_view_RenderNode_getCameraDistance},
+        {"nGetScaleX", "(J)F", (void*)android_view_RenderNode_getScaleX},
+        {"nGetScaleY", "(J)F", (void*)android_view_RenderNode_getScaleY},
+        {"nGetElevation", "(J)F", (void*)android_view_RenderNode_getElevation},
+        {"nGetTranslationX", "(J)F", (void*)android_view_RenderNode_getTranslationX},
+        {"nGetTranslationY", "(J)F", (void*)android_view_RenderNode_getTranslationY},
+        {"nGetTranslationZ", "(J)F", (void*)android_view_RenderNode_getTranslationZ},
+        {"nGetRotation", "(J)F", (void*)android_view_RenderNode_getRotation},
+        {"nGetRotationX", "(J)F", (void*)android_view_RenderNode_getRotationX},
+        {"nGetRotationY", "(J)F", (void*)android_view_RenderNode_getRotationY},
+        {"nIsPivotExplicitlySet", "(J)Z", (void*)android_view_RenderNode_isPivotExplicitlySet},
+        {"nHasIdentityMatrix", "(J)Z", (void*)android_view_RenderNode_hasIdentityMatrix},
 
-    { "nGetTransformMatrix",       "(JJ)V", (void*) android_view_RenderNode_getTransformMatrix },
-    { "nGetInverseTransformMatrix","(JJ)V", (void*) android_view_RenderNode_getInverseTransformMatrix },
+        {"nGetTransformMatrix", "(JJ)V", (void*)android_view_RenderNode_getTransformMatrix},
+        {"nGetInverseTransformMatrix", "(JJ)V",
+         (void*)android_view_RenderNode_getInverseTransformMatrix},
 
-    { "nGetPivotX",                "(J)F",  (void*) android_view_RenderNode_getPivotX },
-    { "nGetPivotY",                "(J)F",  (void*) android_view_RenderNode_getPivotY },
-    { "nGetWidth",                 "(J)I",  (void*) android_view_RenderNode_getWidth },
-    { "nGetHeight",                "(J)I",  (void*) android_view_RenderNode_getHeight },
-    { "nSetAllowForceDark",        "(JZ)Z", (void*) android_view_RenderNode_setAllowForceDark },
-    { "nGetAllowForceDark",        "(J)Z",  (void*) android_view_RenderNode_getAllowForceDark },
-    { "nGetUniqueId",              "(J)J",  (void*) android_view_RenderNode_getUniqueId },
+        {"nGetPivotX", "(J)F", (void*)android_view_RenderNode_getPivotX},
+        {"nGetPivotY", "(J)F", (void*)android_view_RenderNode_getPivotY},
+        {"nGetWidth", "(J)I", (void*)android_view_RenderNode_getWidth},
+        {"nGetHeight", "(J)I", (void*)android_view_RenderNode_getHeight},
+        {"nSetAllowForceDark", "(JZ)Z", (void*)android_view_RenderNode_setAllowForceDark},
+        {"nGetAllowForceDark", "(J)Z", (void*)android_view_RenderNode_getAllowForceDark},
+        {"nGetUniqueId", "(J)J", (void*)android_view_RenderNode_getUniqueId},
 };
 
 int register_android_view_RenderNode(JNIEnv* env) {
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
index c010212..cb0ff8d 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
@@ -169,8 +169,8 @@
     displayList->mProjectedOutline = nullptr;
 }
 
-static bool layerNeedsPaint(const LayerProperties& properties, float alphaMultiplier,
-                            SkPaint* paint) {
+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()) {
@@ -179,7 +179,8 @@
         paint->setColorFilter(sk_ref_sp(properties.getColorFilter()));
 
         sk_sp<SkImageFilter> imageFilter = sk_ref_sp(properties.getImageFilter());
-        sk_sp<SkImageFilter> stretchFilter = properties.getStretchEffect().getImageFilter();
+        sk_sp<SkImageFilter> stretchFilter =
+                properties.getStretchEffect().getImageFilter(snapshotImage);
         sk_sp<SkImageFilter> filter;
         if (imageFilter && stretchFilter) {
             filter = SkImageFilters::Compose(
@@ -240,7 +241,8 @@
         if (renderNode->getLayerSurface() && mComposeLayer) {
             SkASSERT(properties.effectiveLayerType() == LayerType::RenderLayer);
             SkPaint paint;
-            layerNeedsPaint(layerProperties, alphaMultiplier, &paint);
+            sk_sp<SkImage> snapshotImage = renderNode->getLayerSurface()->makeImageSnapshot();
+            layerNeedsPaint(snapshotImage, layerProperties, alphaMultiplier, &paint);
             SkSamplingOptions sampling(SkFilterMode::kLinear);
 
             // surfaces for layers are created on LAYER_SIZE boundaries (which are >= layer size) so
@@ -254,8 +256,8 @@
                 canvas->drawAnnotation(bounds, String8::format(
                     "SurfaceID|%" PRId64, renderNode->uniqueId()).c_str(), nullptr);
             }
-            canvas->drawImageRect(renderNode->getLayerSurface()->makeImageSnapshot(), bounds,
-                                  bounds, sampling, &paint, SkCanvas::kStrict_SrcRectConstraint);
+            canvas->drawImageRect(snapshotImage, bounds, bounds, sampling, &paint,
+                                  SkCanvas::kStrict_SrcRectConstraint);
 
             if (!renderNode->getSkiaLayer()->hasRenderedSinceRepaint) {
                 renderNode->getSkiaLayer()->hasRenderedSinceRepaint = true;