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