• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 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 "StretchEffect.h"
18 #include <SkImageFilter.h>
19 #include <SkRefCnt.h>
20 #include <SkRuntimeEffect.h>
21 #include <SkString.h>
22 #include <SkSurface.h>
23 #include <include/effects/SkImageFilters.h>
24 
25 #include <memory>
26 
27 namespace android::uirenderer {
28 
29 static const SkString stretchShader = SkString(R"(
30     uniform shader uContentTexture;
31 
32     // multiplier to apply to scale effect
33     uniform float uMaxStretchIntensity;
34 
35     // Maximum percentage to stretch beyond bounds  of target
36     uniform float uStretchAffectedDistX;
37     uniform float uStretchAffectedDistY;
38 
39     // Distance stretched as a function of the normalized overscroll times
40     // scale intensity
41     uniform float uDistanceStretchedX;
42     uniform float uDistanceStretchedY;
43     uniform float uInverseDistanceStretchedX;
44     uniform float uInverseDistanceStretchedY;
45     uniform float uDistDiffX;
46 
47     // Difference between the peak stretch amount and overscroll amount normalized
48     uniform float uDistDiffY;
49 
50     // Horizontal offset represented as a ratio of pixels divided by the target width
51     uniform float uScrollX;
52     // Vertical offset represented as a ratio of pixels divided by the target height
53     uniform float uScrollY;
54 
55     // Normalized overscroll amount in the horizontal direction
56     uniform float uOverscrollX;
57 
58     // Normalized overscroll amount in the vertical direction
59     uniform float uOverscrollY;
60     uniform float viewportWidth; // target height in pixels
61     uniform float viewportHeight; // target width in pixels
62 
63     // uInterpolationStrength is the intensity of the interpolation.
64     // if uInterpolationStrength is 0, then the stretch is constant for all the
65     // uStretchAffectedDist. if uInterpolationStrength is 1, then stretch intensity
66     // is interpolated based on the pixel position in the uStretchAffectedDist area;
67     // The closer we are from the scroll anchor point, the more it stretches,
68     // and the other way around.
69     uniform float uInterpolationStrength;
70 
71     float easeIn(float t, float d) {
72         return t * d;
73     }
74 
75     float computeOverscrollStart(
76         float inPos,
77         float overscroll,
78         float uStretchAffectedDist,
79         float uInverseStretchAffectedDist,
80         float distanceStretched,
81         float interpolationStrength
82     ) {
83         float offsetPos = uStretchAffectedDist - inPos;
84         float posBasedVariation = mix(
85                 1. ,easeIn(offsetPos, uInverseStretchAffectedDist), interpolationStrength);
86         float stretchIntensity = overscroll * posBasedVariation;
87         return distanceStretched - (offsetPos / (1. + stretchIntensity));
88     }
89 
90     float computeOverscrollEnd(
91         float inPos,
92         float overscroll,
93         float reverseStretchDist,
94         float uStretchAffectedDist,
95         float uInverseStretchAffectedDist,
96         float distanceStretched,
97         float interpolationStrength,
98         float viewportDimension
99     ) {
100         float offsetPos = inPos - reverseStretchDist;
101         float posBasedVariation = mix(
102                 1. ,easeIn(offsetPos, uInverseStretchAffectedDist), interpolationStrength);
103         float stretchIntensity = (-overscroll) * posBasedVariation;
104         return viewportDimension - (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         float viewportDimension
119     ) {
120       if (overscroll > 0) {
121         if (inPos <= uStretchAffectedDist) {
122             return computeOverscrollStart(
123               inPos,
124               overscroll,
125               uStretchAffectedDist,
126               uInverseStretchAffectedDist,
127               distanceStretched,
128               interpolationStrength
129             );
130         } else {
131             return distanceDiff + inPos;
132         }
133       } else if (overscroll < 0) {
134         float stretchAffectedDist = viewportDimension - uStretchAffectedDist;
135         if (inPos >= stretchAffectedDist) {
136             return computeOverscrollEnd(
137               inPos,
138               overscroll,
139               stretchAffectedDist,
140               uStretchAffectedDist,
141               uInverseStretchAffectedDist,
142               distanceStretched,
143               interpolationStrength,
144               viewportDimension
145             );
146         } else {
147             return -distanceDiff + inPos;
148         }
149       } else {
150         return inPos;
151       }
152     }
153 
154     vec4 main(vec2 coord) {
155         float inU = coord.x;
156         float inV = coord.y;
157         float outU;
158         float outV;
159 
160         inU += uScrollX;
161         inV += uScrollY;
162         outU = computeOverscroll(
163             inU,
164             uOverscrollX,
165             uStretchAffectedDistX,
166             uInverseDistanceStretchedX,
167             uDistanceStretchedX,
168             uDistDiffX,
169             uInterpolationStrength,
170             viewportWidth
171         );
172         outV = computeOverscroll(
173             inV,
174             uOverscrollY,
175             uStretchAffectedDistY,
176             uInverseDistanceStretchedY,
177             uDistanceStretchedY,
178             uDistDiffY,
179             uInterpolationStrength,
180             viewportHeight
181         );
182         coord.x = outU;
183         coord.y = outV;
184         return uContentTexture.eval(coord);
185     })");
186 
187 static const float ZERO = 0.f;
188 static const float INTERPOLATION_STRENGTH_VALUE = 0.7f;
189 static const char CONTENT_TEXTURE[] = "uContentTexture";
190 
getShader(float width,float height,const sk_sp<SkImage> & snapshotImage,const SkMatrix * matrix) const191 sk_sp<SkShader> StretchEffect::getShader(float width, float height,
192                                          const sk_sp<SkImage>& snapshotImage,
193                                          const SkMatrix* matrix) const {
194     if (isEmpty()) {
195         return nullptr;
196     }
197 
198     float normOverScrollDistX = mStretchDirection.x();
199     float normOverScrollDistY = mStretchDirection.y();
200     float distanceStretchedX = width / (1 + abs(normOverScrollDistX));
201     float distanceStretchedY = height / (1 + abs(normOverScrollDistY));
202     float inverseDistanceStretchedX = 1.f / width;
203     float inverseDistanceStretchedY = 1.f / height;
204     float diffX = distanceStretchedX - width;
205     float diffY = distanceStretchedY - height;
206 
207     if (mBuilder == nullptr) {
208         mBuilder = std::make_unique<SkRuntimeShaderBuilder>(getStretchEffect());
209     }
210 
211     mBuilder->child(CONTENT_TEXTURE) =
212             snapshotImage->makeShader(SkTileMode::kClamp, SkTileMode::kClamp,
213                                       SkSamplingOptions(SkFilterMode::kLinear), matrix);
214     mBuilder->uniform("uInterpolationStrength").set(&INTERPOLATION_STRENGTH_VALUE, 1);
215     mBuilder->uniform("uStretchAffectedDistX").set(&width, 1);
216     mBuilder->uniform("uStretchAffectedDistY").set(&height, 1);
217     mBuilder->uniform("uDistanceStretchedX").set(&distanceStretchedX, 1);
218     mBuilder->uniform("uDistanceStretchedY").set(&distanceStretchedY, 1);
219     mBuilder->uniform("uInverseDistanceStretchedX").set(&inverseDistanceStretchedX, 1);
220     mBuilder->uniform("uInverseDistanceStretchedY").set(&inverseDistanceStretchedY, 1);
221     mBuilder->uniform("uDistDiffX").set(&diffX, 1);
222     mBuilder->uniform("uDistDiffY").set(&diffY, 1);
223     mBuilder->uniform("uOverscrollX").set(&normOverScrollDistX, 1);
224     mBuilder->uniform("uOverscrollY").set(&normOverScrollDistY, 1);
225     mBuilder->uniform("uScrollX").set(&ZERO, 1);
226     mBuilder->uniform("uScrollY").set(&ZERO, 1);
227     mBuilder->uniform("viewportWidth").set(&width, 1);
228     mBuilder->uniform("viewportHeight").set(&height, 1);
229 
230     auto result = mBuilder->makeShader();
231     mBuilder->child(CONTENT_TEXTURE) = nullptr;
232     return result;
233 }
234 
getStretchEffect()235 sk_sp<SkRuntimeEffect> StretchEffect::getStretchEffect() {
236     const static SkRuntimeEffect::Result instance = SkRuntimeEffect::MakeForShader(stretchShader);
237     return instance.effect;
238 }
239 
240 /**
241  * Helper method that maps the input texture position to the stretch position
242  * based on the given overscroll value that represents an overscroll from
243  * either the top or left
244  * @param overscroll current overscroll value
245  * @param input normalized input position (can be x or y) on the input texture
246  * @return stretched position of the input normalized from 0 to 1
247  */
reverseMapStart(float overscroll,float input)248 float reverseMapStart(float overscroll, float input) {
249     float numerator = (-input * overscroll * overscroll) -
250         (2 * input * overscroll) - input;
251     float denominator = 1.f + (.3f * overscroll) +
252         (.7f * input * overscroll * overscroll) + (.7f * input * overscroll);
253     return -(numerator / denominator);
254 }
255 
256 /**
257  * Helper method that maps the input texture position to the stretch position
258  * based on the given overscroll value that represents an overscroll from
259  * either the bottom or right
260  * @param overscroll current overscroll value
261  * @param input normalized input position (can be x or y) on the input texture
262  * @return stretched position of the input normalized from 0 to 1
263  */
reverseMapEnd(float overscroll,float input)264 float reverseMapEnd(float overscroll, float input) {
265     float numerator = (.3f * overscroll * overscroll) -
266         (.3f * input * overscroll * overscroll) +
267         (1.3f * input * overscroll) - overscroll - input;
268     float denominator = (.7f * input * overscroll * overscroll) -
269         (.7f * input * overscroll) - (.7f * overscroll * overscroll) +
270         overscroll - 1.f;
271     return numerator / denominator;
272 }
273 
274 /**
275   * Calculates the normalized stretch position given the normalized input
276   * position. This handles calculating the overscroll from either the
277   * top or left vs bottom or right depending on the sign of the given overscroll
278   * value
279   *
280   * @param overscroll unit vector of overscroll from -1 to 1 indicating overscroll
281   * from the bottom or right vs top or left respectively
282   * @param normalizedInput the
283   * @return
284   */
computeReverseOverscroll(float overscroll,float normalizedInput)285 float computeReverseOverscroll(float overscroll, float normalizedInput) {
286     float distanceStretched = 1.f / (1.f + abs(overscroll));
287     float distanceDiff = distanceStretched - 1.f;
288     if (overscroll > 0) {
289         float output = reverseMapStart(overscroll, normalizedInput);
290         if (output <= 1.0f) {
291             return output;
292         } else if (output >= distanceStretched){
293             return output - distanceDiff;
294         }
295     }
296 
297     if (overscroll < 0) {
298         float output = reverseMapEnd(overscroll, normalizedInput);
299         if (output >= 0.f) {
300             return output;
301         } else if (output < 0.f){
302             return output + distanceDiff;
303         }
304     }
305     return normalizedInput;
306 }
307 
computeStretchedPositionX(float normalizedX) const308 float StretchEffect::computeStretchedPositionX(float normalizedX) const {
309   return computeReverseOverscroll(mStretchDirection.x(), normalizedX);
310 }
311 
computeStretchedPositionY(float normalizedY) const312 float StretchEffect::computeStretchedPositionY(float normalizedY) const {
313   return computeReverseOverscroll(mStretchDirection.y(), normalizedY);
314 }
315 
316 } // namespace android::uirenderer