Update SurfaceFlinger to handle stretching
of surfaces that are part of a scrolling container

Bug: 184297961
Test: In progress
Change-Id: I959df097ae1fc833fb755f1fb2d759d79f260963
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
index acdb78a..3e3649e 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
@@ -56,6 +56,7 @@
 #include "log/log_main.h"
 #include "skia/debug/SkiaCapture.h"
 #include "skia/debug/SkiaMemoryReporter.h"
+#include "skia/filters/StretchShaderFactory.h"
 #include "system/graphics-base-v1.0.h"
 
 namespace {
@@ -541,14 +542,19 @@
     }
 }
 
-sk_sp<SkShader> SkiaGLRenderEngine::createRuntimeEffectShader(sk_sp<SkShader> shader,
-                                                              const LayerSettings* layer,
-                                                              const DisplaySettings& display,
-                                                              bool undoPremultipliedAlpha,
-                                                              bool requiresLinearEffect) {
-    if (layer->stretchEffect.hasEffect()) {
-        // TODO: Implement
+sk_sp<SkShader> SkiaGLRenderEngine::createRuntimeEffectShader(
+        sk_sp<SkShader> shader,
+        const LayerSettings* layer, const DisplaySettings& display, bool undoPremultipliedAlpha,
+        bool requiresLinearEffect) {
+    const auto stretchEffect = layer->stretchEffect;
+    if (stretchEffect.hasEffect()) {
+        const auto targetBuffer = layer->source.buffer.buffer;
+        const auto graphicsBuffer = targetBuffer ? targetBuffer->getBuffer() : nullptr;
+        if (graphicsBuffer && shader) {
+            shader = mStretchShaderFactory.createSkShader(shader, stretchEffect);
+        }
     }
+
     if (requiresLinearEffect) {
         const ui::Dataspace inputDataspace =
                 mUseColorManagement ? layer->sourceDataspace : ui::Dataspace::UNKNOWN;
@@ -634,6 +640,33 @@
     int mSaveCount;
 };
 
+void drawStretch(const SkRect& bounds, const StretchEffect& stretchEffect,
+                 SkCanvas* canvas, const SkPaint& paint) {
+    float top = bounds.top();
+    float left = bounds.left();
+    float bottom = bounds.bottom();
+    float right = bounds.right();
+    // Adjust the drawing bounds based on the stretch itself.
+    float stretchOffsetX =
+        round(bounds.width() * stretchEffect.getStretchWidthMultiplier());
+    float stretchOffsetY =
+        round(bounds.height() * stretchEffect.getStretchHeightMultiplier());
+    if (stretchEffect.vectorY < 0.f) {
+        top -= stretchOffsetY;
+    } else if (stretchEffect.vectorY > 0.f){
+        bottom += stretchOffsetY;
+    }
+
+    if (stretchEffect.vectorX < 0.f) {
+        left -= stretchOffsetX;
+    } else if (stretchEffect.vectorX > 0.f) {
+        right += stretchOffsetX;
+    }
+
+    auto stretchBounds = SkRect::MakeLTRB(left, top, right, bottom);
+    canvas->drawRect(stretchBounds, paint);
+}
+
 status_t SkiaGLRenderEngine::drawLayers(const DisplaySettings& display,
                                         const std::vector<const LayerSettings*>& layers,
                                         const std::shared_ptr<ExternalTexture>& buffer,
@@ -671,9 +704,7 @@
     } else {
         surfaceTextureRef =
                 std::make_shared<AutoBackendTexture::LocalRef>(grContext.get(),
-                                                               buffer->getBuffer()
-                                                                       ->toAHardwareBuffer(),
-                                                               true);
+                      buffer->getBuffer()->toAHardwareBuffer(), true);
     }
 
     const ui::Dataspace dstDataspace =
@@ -988,7 +1019,17 @@
             paint.setAntiAlias(true);
             canvas->drawRRect(getRoundedRect(layer), paint);
         } else {
-            canvas->drawRect(bounds, paint);
+            auto& stretchEffect = layer->stretchEffect;
+            // TODO (njawad) temporarily disable manipulation of geometry
+            //  the layer bounds will be updated in HWUI instead of RenderEngine
+            //  in a subsequent CL
+            // Keep the method call in a dead code path to make -Werror happy
+            // with unused methods
+            if (stretchEffect.hasEffect() && /* DISABLES CODE */ (false)) {
+                drawStretch(bounds, stretchEffect, canvas, paint);
+            } else {
+                canvas->drawRect(bounds, paint);
+            }
         }
         if (kFlushAfterEveryLayer) {
             ATRACE_NAME("flush surface");
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.h b/libs/renderengine/skia/SkiaGLRenderEngine.h
index e71c560..98f5ee2 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.h
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.h
@@ -39,6 +39,7 @@
 #include "debug/SkiaCapture.h"
 #include "filters/BlurFilter.h"
 #include "filters/LinearEffect.h"
+#include "filters/StretchShaderFactory.h"
 
 namespace android {
 namespace renderengine {
@@ -101,7 +102,8 @@
                     const ShadowSettings& shadowSettings);
     // If requiresLinearEffect is true or the layer has a stretchEffect a new shader is returned.
     // Otherwise it returns the input shader.
-    sk_sp<SkShader> createRuntimeEffectShader(sk_sp<SkShader> shader, const LayerSettings* layer,
+    sk_sp<SkShader> createRuntimeEffectShader(sk_sp<SkShader> shader,
+                                              const LayerSettings* layer,
                                               const DisplaySettings& display,
                                               bool undoPremultipliedAlpha,
                                               bool requiresLinearEffect);
@@ -116,14 +118,20 @@
     const PixelFormat mDefaultPixelFormat;
     const bool mUseColorManagement;
 
+    // Identifier used or various mappings of layers to various
+    // textures or shaders
+    using LayerId = uint64_t;
+
     // Number of external holders of ExternalTexture references, per GraphicBuffer ID.
-    std::unordered_map<uint64_t, int32_t> mGraphicBufferExternalRefs GUARDED_BY(mRenderingMutex);
+    std::unordered_map<LayerId, int32_t> mGraphicBufferExternalRefs GUARDED_BY(mRenderingMutex);
     // Cache of GL textures that we'll store per GraphicBuffer ID, sliced by GPU context.
-    std::unordered_map<uint64_t, std::shared_ptr<AutoBackendTexture::LocalRef>> mTextureCache
+    std::unordered_map<LayerId, std::shared_ptr<AutoBackendTexture::LocalRef>> mTextureCache
             GUARDED_BY(mRenderingMutex);
-    std::unordered_map<uint64_t, std::shared_ptr<AutoBackendTexture::LocalRef>>
+    std::unordered_map<LayerId, std::shared_ptr<AutoBackendTexture::LocalRef>>
             mProtectedTextureCache GUARDED_BY(mRenderingMutex);
     std::unordered_map<LinearEffect, sk_sp<SkRuntimeEffect>, LinearEffectHasher> mRuntimeEffects;
+
+    StretchShaderFactory mStretchShaderFactory;
     // Mutex guarding rendering operations, so that:
     // 1. GL operations aren't interleaved, and
     // 2. Internal state related to rendering that is potentially modified by
diff --git a/libs/renderengine/skia/filters/StretchShaderFactory.cpp b/libs/renderengine/skia/filters/StretchShaderFactory.cpp
new file mode 100644
index 0000000..9b62789
--- /dev/null
+++ b/libs/renderengine/skia/filters/StretchShaderFactory.cpp
@@ -0,0 +1,247 @@
+/*
+ * Copyright 2021 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 "StretchShaderFactory.h"
+#include <SkImageFilter.h>
+#include <SkRefCnt.h>
+#include <SkRuntimeEffect.h>
+#include <SkString.h>
+#include <SkSurface.h>
+#include "log/log.h"
+#include <memory>
+
+namespace android {
+namespace renderengine {
+namespace skia {
+
+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 uStretchAffectedDistX;
+    uniform float uStretchAffectedDistY;
+
+    // Distance stretched as a function of the normalized overscroll times
+    // scale intensity
+    uniform float uDistanceStretchedX;
+    uniform float uDistanceStretchedY;
+    uniform float uInverseDistanceStretchedX;
+    uniform float uInverseDistanceStretchedY;
+    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
+
+    // uInterpolationStrength is the intensity of the interpolation.
+    // if uInterpolationStrength is 0, then the stretch is constant for all the
+    // uStretchAffectedDist. if uInterpolationStrength is 1, then stretch intensity
+    // is interpolated based on the pixel position in the uStretchAffectedDist area;
+    // The closer we are from the scroll anchor point, the more it stretches,
+    // and the other way around.
+    uniform float uInterpolationStrength;
+
+    float easeInCubic(float t, float d) {
+        float tmp = t * d;
+        return tmp * tmp * tmp;
+    }
+
+    float computeOverscrollStart(
+        float inPos,
+        float overscroll,
+        float uStretchAffectedDist,
+        float uInverseStretchAffectedDist,
+        float distanceStretched,
+        float interpolationStrength
+    ) {
+        float offsetPos = uStretchAffectedDist - inPos;
+        float posBasedVariation = mix(
+                1. ,easeInCubic(offsetPos, uInverseStretchAffectedDist), interpolationStrength);
+        float stretchIntensity = overscroll * posBasedVariation;
+        return distanceStretched - (offsetPos / (1. + stretchIntensity));
+    }
+
+    float computeOverscrollEnd(
+        float inPos,
+        float overscroll,
+        float reverseStretchDist,
+        float uStretchAffectedDist,
+        float uInverseStretchAffectedDist,
+        float distanceStretched,
+        float interpolationStrength
+    ) {
+        float offsetPos = inPos - reverseStretchDist;
+        float posBasedVariation = mix(
+                1. ,easeInCubic(offsetPos, uInverseStretchAffectedDist), interpolationStrength);
+        float stretchIntensity = (-overscroll) * posBasedVariation;
+        return 1 - (distanceStretched - (offsetPos / (1. + stretchIntensity)));
+    }
+
+    // Prefer usage of return values over out parameters as it enables
+    // SKSL to properly inline method calls and works around potential GPU
+    // driver issues on Wembly. See b/182566543 for details
+    float computeOverscroll(
+        float inPos,
+        float overscroll,
+        float uStretchAffectedDist,
+        float uInverseStretchAffectedDist,
+        float distanceStretched,
+        float distanceDiff,
+        float interpolationStrength
+    ) {
+      float outPos = inPos;
+      // overscroll is provided via uniform so there is no concern
+      // for potential incoherent branches
+      if (overscroll > 0) {
+            if (inPos <= uStretchAffectedDist) {
+                outPos = computeOverscrollStart(
+                  inPos,
+                  overscroll,
+                  uStretchAffectedDist,
+                  uInverseStretchAffectedDist,
+                  distanceStretched,
+                  interpolationStrength
+                );
+            } else if (inPos >= distanceStretched) {
+                outPos = distanceDiff + inPos;
+            }
+        }
+        if (overscroll < 0) {
+            float stretchAffectedDist = 1. - uStretchAffectedDist;
+            if (inPos >= stretchAffectedDist) {
+                outPos = computeOverscrollEnd(
+                  inPos,
+                  overscroll,
+                  stretchAffectedDist,
+                  uStretchAffectedDist,
+                  uInverseStretchAffectedDist,
+                  distanceStretched,
+                  interpolationStrength
+                );
+            } else if (inPos < stretchAffectedDist) {
+                outPos = -distanceDiff + inPos;
+            }
+        }
+        return outPos;
+    }
+
+    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;
+        outU = computeOverscroll(
+            inU,
+            uOverscrollX,
+            uStretchAffectedDistX,
+            uInverseDistanceStretchedX,
+            uDistanceStretchedX,
+            uDistDiffX,
+            uInterpolationStrength
+        );
+        outV = computeOverscroll(
+            inV,
+            uOverscrollY,
+            uStretchAffectedDistY,
+            uInverseDistanceStretchedY,
+            uDistanceStretchedY,
+            uDistDiffY,
+            uInterpolationStrength
+        );
+        coord.x = (outU - uScrollX) * viewportWidth;
+        coord.y = (outV - uScrollY) * viewportHeight;
+        return sample(uContentTexture, coord);
+    })");
+
+const float INTERPOLATION_STRENGTH_VALUE = 0.7f;
+
+sk_sp<SkShader> StretchShaderFactory::createSkShader(const sk_sp<SkShader>& inputShader,
+                                                     const StretchEffect& stretchEffect) {
+    if (!stretchEffect.hasEffect()) {
+        return nullptr;
+    }
+
+    float viewportWidth = stretchEffect.width;
+    float viewportHeight = stretchEffect.height;
+    float normOverScrollDistX = stretchEffect.vectorX;
+    float normOverScrollDistY = stretchEffect.vectorY;
+    float distanceStretchedX =
+        StretchEffect::CONTENT_DISTANCE_STRETCHED / (1 + abs(normOverScrollDistX));
+    float distanceStretchedY =
+        StretchEffect::CONTENT_DISTANCE_STRETCHED / (1 + abs(normOverScrollDistY));
+    float inverseDistanceStretchedX =
+        1.f / StretchEffect::CONTENT_DISTANCE_STRETCHED;
+    float inverseDistanceStretchedY =
+        1.f / StretchEffect::CONTENT_DISTANCE_STRETCHED;
+    float diffX =
+        distanceStretchedX - StretchEffect::CONTENT_DISTANCE_STRETCHED;
+    float diffY =
+        distanceStretchedY - StretchEffect::CONTENT_DISTANCE_STRETCHED;
+    auto& srcBounds = stretchEffect.mappedChildBounds;
+    float normalizedScrollX = srcBounds.left / viewportWidth;
+    float normalizedScrollY = srcBounds.top / viewportHeight;
+
+    if (mBuilder == nullptr) {
+        const static SkRuntimeEffect::Result instance =
+            SkRuntimeEffect::MakeForShader(stretchShader);
+        mBuilder = std::make_unique<SkRuntimeShaderBuilder>(instance.effect);
+    }
+
+    mBuilder->child("uContentTexture") = inputShader;
+    mBuilder->uniform("uInterpolationStrength").set(&INTERPOLATION_STRENGTH_VALUE, 1);
+    mBuilder->uniform("uStretchAffectedDistX").set(&StretchEffect::CONTENT_DISTANCE_STRETCHED, 1);
+    mBuilder->uniform("uStretchAffectedDistY").set(&StretchEffect::CONTENT_DISTANCE_STRETCHED, 1);
+    mBuilder->uniform("uDistanceStretchedX").set(&distanceStretchedX, 1);
+    mBuilder->uniform("uDistanceStretchedY").set(&distanceStretchedY, 1);
+    mBuilder->uniform("uInverseDistanceStretchedX").set(&inverseDistanceStretchedX, 1);
+    mBuilder->uniform("uInverseDistanceStretchedY").set(&inverseDistanceStretchedY, 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(&normalizedScrollX, 1);
+    mBuilder->uniform("uScrollY").set(&normalizedScrollY, 1);
+    mBuilder->uniform("viewportWidth").set(&viewportWidth, 1);
+    mBuilder->uniform("viewportHeight").set(&viewportHeight, 1);
+
+    return mBuilder->makeShader(nullptr, false);
+}
+
+} // namespace skia
+} // namespace renderengine
+} // namespace android
\ No newline at end of file
diff --git a/libs/renderengine/skia/filters/StretchShaderFactory.h b/libs/renderengine/skia/filters/StretchShaderFactory.h
new file mode 100644
index 0000000..9c3ab7c
--- /dev/null
+++ b/libs/renderengine/skia/filters/StretchShaderFactory.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2021 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 <SkImage.h>
+#include <SkRuntimeEffect.h>
+#include <SkShader.h>
+#include <ui/StretchEffect.h>
+
+namespace android {
+namespace renderengine {
+namespace skia {
+class StretchShaderFactory {
+public:
+    sk_sp<SkShader> createSkShader(const sk_sp<SkShader>& inputShader,
+                                   const StretchEffect& stretchEffect);
+
+private:
+    std::unique_ptr<SkRuntimeShaderBuilder> mBuilder;
+};
+} // namespace skia
+} // namespace renderengine
+} // namespace android
\ No newline at end of file