1 /*
2  * Copyright 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "StretchShaderFactory.h"
18 #include <SkImageFilter.h>
19 #include <SkRefCnt.h>
20 #include <SkRuntimeEffect.h>
21 #include <SkString.h>
22 #include <SkSurface.h>
23 #include "log/log.h"
24 #include <memory>
25 
26 namespace android {
27 namespace renderengine {
28 namespace skia {
29 
30 static const SkString stretchShader = SkString(R"(
31     uniform shader uContentTexture;
32 
33     // multiplier to apply to scale effect
34     uniform float uMaxStretchIntensity;
35 
36     // Maximum percentage to stretch beyond bounds  of target
37     uniform float uStretchAffectedDistX;
38     uniform float uStretchAffectedDistY;
39 
40     // Distance stretched as a function of the normalized overscroll times
41     // scale intensity
42     uniform float uDistanceStretchedX;
43     uniform float uDistanceStretchedY;
44     uniform float uInverseDistanceStretchedX;
45     uniform float uInverseDistanceStretchedY;
46     uniform float uDistDiffX;
47 
48     // Difference between the peak stretch amount and overscroll amount normalized
49     uniform float uDistDiffY;
50 
51     // Horizontal offset represented as a ratio of pixels divided by the target width
52     uniform float uScrollX;
53     // Vertical offset represented as a ratio of pixels divided by the target height
54     uniform float uScrollY;
55 
56     // Normalized overscroll amount in the horizontal direction
57     uniform float uOverscrollX;
58 
59     // Normalized overscroll amount in the vertical direction
60     uniform float uOverscrollY;
61     uniform float viewportWidth; // target height in pixels
62     uniform float viewportHeight; // target width in pixels
63 
64     // uInterpolationStrength is the intensity of the interpolation.
65     // if uInterpolationStrength is 0, then the stretch is constant for all the
66     // uStretchAffectedDist. if uInterpolationStrength is 1, then stretch intensity
67     // is interpolated based on the pixel position in the uStretchAffectedDist area;
68     // The closer we are from the scroll anchor point, the more it stretches,
69     // and the other way around.
70     uniform float uInterpolationStrength;
71 
72     float easeIn(float t, float d) {
73         return t * d;
74     }
75 
76     float computeOverscrollStart(
77         float inPos,
78         float overscroll,
79         float uStretchAffectedDist,
80         float uInverseStretchAffectedDist,
81         float distanceStretched,
82         float interpolationStrength
83     ) {
84         float offsetPos = uStretchAffectedDist - inPos;
85         float posBasedVariation = mix(
86                 1. ,easeIn(offsetPos, uInverseStretchAffectedDist), interpolationStrength);
87         float stretchIntensity = overscroll * posBasedVariation;
88         return distanceStretched - (offsetPos / (1. + stretchIntensity));
89     }
90 
91     float computeOverscrollEnd(
92         float inPos,
93         float overscroll,
94         float reverseStretchDist,
95         float uStretchAffectedDist,
96         float uInverseStretchAffectedDist,
97         float distanceStretched,
98         float interpolationStrength
99     ) {
100         float offsetPos = inPos - reverseStretchDist;
101         float posBasedVariation = mix(
102                 1. ,easeIn(offsetPos, uInverseStretchAffectedDist), interpolationStrength);
103         float stretchIntensity = (-overscroll) * posBasedVariation;
104         return 1 - (distanceStretched - (offsetPos / (1. + stretchIntensity)));
105     }
106 
107     // Prefer usage of return values over out parameters as it enables
108     // SKSL to properly inline method calls and works around potential GPU
109     // driver issues on Wembly. See b/182566543 for details
110     float computeOverscroll(
111         float inPos,
112         float overscroll,
113         float uStretchAffectedDist,
114         float uInverseStretchAffectedDist,
115         float distanceStretched,
116         float distanceDiff,
117         float interpolationStrength
118     ) {
119       float outPos = inPos;
120       // overscroll is provided via uniform so there is no concern
121       // for potential incoherent branches
122       if (overscroll > 0) {
123             if (inPos <= uStretchAffectedDist) {
124                 outPos = computeOverscrollStart(
125                   inPos,
126                   overscroll,
127                   uStretchAffectedDist,
128                   uInverseStretchAffectedDist,
129                   distanceStretched,
130                   interpolationStrength
131                 );
132             } else if (inPos >= distanceStretched) {
133                 outPos = distanceDiff + inPos;
134             }
135         }
136         if (overscroll < 0) {
137             float stretchAffectedDist = 1. - uStretchAffectedDist;
138             if (inPos >= stretchAffectedDist) {
139                 outPos = computeOverscrollEnd(
140                   inPos,
141                   overscroll,
142                   stretchAffectedDist,
143                   uStretchAffectedDist,
144                   uInverseStretchAffectedDist,
145                   distanceStretched,
146                   interpolationStrength
147                 );
148             } else if (inPos < stretchAffectedDist) {
149                 outPos = -distanceDiff + inPos;
150             }
151         }
152         return outPos;
153     }
154 
155     vec4 main(vec2 coord) {
156         // Normalize SKSL pixel coordinate into a unit vector
157         float inU = coord.x / viewportWidth;
158         float inV = coord.y / viewportHeight;
159         float outU;
160         float outV;
161         float stretchIntensity;
162         // Add the normalized scroll position within scrolling list
163         inU += uScrollX;
164         inV += uScrollY;
165         outU = inU;
166         outV = inV;
167         outU = computeOverscroll(
168             inU,
169             uOverscrollX,
170             uStretchAffectedDistX,
171             uInverseDistanceStretchedX,
172             uDistanceStretchedX,
173             uDistDiffX,
174             uInterpolationStrength
175         );
176         outV = computeOverscroll(
177             inV,
178             uOverscrollY,
179             uStretchAffectedDistY,
180             uInverseDistanceStretchedY,
181             uDistanceStretchedY,
182             uDistDiffY,
183             uInterpolationStrength
184         );
185         coord.x = (outU - uScrollX) * viewportWidth;
186         coord.y = (outV - uScrollY) * viewportHeight;
187         return uContentTexture.eval(coord);
188     })");
189 
190 const float INTERPOLATION_STRENGTH_VALUE = 0.7f;
191 
createSkShader(const sk_sp<SkShader> & inputShader,const StretchEffect & stretchEffect)192 sk_sp<SkShader> StretchShaderFactory::createSkShader(const sk_sp<SkShader>& inputShader,
193                                                      const StretchEffect& stretchEffect) {
194     if (!stretchEffect.hasEffect()) {
195         return nullptr;
196     }
197 
198     float viewportWidth = stretchEffect.width;
199     float viewportHeight = stretchEffect.height;
200     float normOverScrollDistX = stretchEffect.vectorX;
201     float normOverScrollDistY = stretchEffect.vectorY;
202     float distanceStretchedX =
203         StretchEffect::CONTENT_DISTANCE_STRETCHED / (1 + abs(normOverScrollDistX));
204     float distanceStretchedY =
205         StretchEffect::CONTENT_DISTANCE_STRETCHED / (1 + abs(normOverScrollDistY));
206     float inverseDistanceStretchedX =
207         1.f / StretchEffect::CONTENT_DISTANCE_STRETCHED;
208     float inverseDistanceStretchedY =
209         1.f / StretchEffect::CONTENT_DISTANCE_STRETCHED;
210     float diffX =
211         distanceStretchedX - StretchEffect::CONTENT_DISTANCE_STRETCHED;
212     float diffY =
213         distanceStretchedY - StretchEffect::CONTENT_DISTANCE_STRETCHED;
214     auto& srcBounds = stretchEffect.mappedChildBounds;
215     float normalizedScrollX = srcBounds.left / viewportWidth;
216     float normalizedScrollY = srcBounds.top / viewportHeight;
217 
218     if (mBuilder == nullptr) {
219         const static SkRuntimeEffect::Result instance =
220             SkRuntimeEffect::MakeForShader(stretchShader);
221         mBuilder = std::make_unique<SkRuntimeShaderBuilder>(instance.effect);
222     }
223 
224     mBuilder->child("uContentTexture") = inputShader;
225     mBuilder->uniform("uInterpolationStrength").set(&INTERPOLATION_STRENGTH_VALUE, 1);
226     mBuilder->uniform("uStretchAffectedDistX").set(&StretchEffect::CONTENT_DISTANCE_STRETCHED, 1);
227     mBuilder->uniform("uStretchAffectedDistY").set(&StretchEffect::CONTENT_DISTANCE_STRETCHED, 1);
228     mBuilder->uniform("uDistanceStretchedX").set(&distanceStretchedX, 1);
229     mBuilder->uniform("uDistanceStretchedY").set(&distanceStretchedY, 1);
230     mBuilder->uniform("uInverseDistanceStretchedX").set(&inverseDistanceStretchedX, 1);
231     mBuilder->uniform("uInverseDistanceStretchedY").set(&inverseDistanceStretchedY, 1);
232     mBuilder->uniform("uDistDiffX").set(&diffX, 1);
233     mBuilder->uniform("uDistDiffY").set(&diffY, 1);
234     mBuilder->uniform("uOverscrollX").set(&normOverScrollDistX, 1);
235     mBuilder->uniform("uOverscrollY").set(&normOverScrollDistY, 1);
236     mBuilder->uniform("uScrollX").set(&normalizedScrollX, 1);
237     mBuilder->uniform("uScrollY").set(&normalizedScrollY, 1);
238     mBuilder->uniform("viewportWidth").set(&viewportWidth, 1);
239     mBuilder->uniform("viewportHeight").set(&viewportHeight, 1);
240 
241     return mBuilder->makeShader();
242 }
243 
244 } // namespace skia
245 } // namespace renderengine
246 } // namespace android