blob: 0599bfaf02f540431687c03df47eb8f6b23f2ece [file] [log] [blame]
John Reck5cb290b2021-02-01 13:47:31 -05001/*
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"
Nader Jawad6701a602021-02-23 18:14:22 -080018#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>
John Reck5cb290b2021-02-01 13:47:31 -050026
27namespace android::uirenderer {
28
Nader Jawad6701a602021-02-23 18:14:22 -080029static 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
Nader Jawada225d212021-03-17 15:12:58 -070036 uniform float uStretchAffectedDistX;
37 uniform float uStretchAffectedDistY;
Nader Jawad6701a602021-02-23 18:14:22 -080038
39 // Distance stretched as a function of the normalized overscroll times
40 // scale intensity
41 uniform float uDistanceStretchedX;
42 uniform float uDistanceStretchedY;
Nader Jawadebb26b12021-04-01 15:54:09 -070043 uniform float uInverseDistanceStretchedX;
44 uniform float uInverseDistanceStretchedY;
Nader Jawad6701a602021-02-23 18:14:22 -080045 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
Michel Comin Escude9ed86142021-04-13 12:30:51 -070063 // 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
Nader Jawadbc341862021-05-06 10:48:18 -070071 float easeIn(float t, float d) {
72 return t * d;
Nader Jawadebb26b12021-04-01 15:54:09 -070073 }
74
Nader Jawad7148aa72021-03-31 13:11:57 -070075 float computeOverscrollStart(
Nader Jawad6701a602021-02-23 18:14:22 -080076 float inPos,
77 float overscroll,
78 float uStretchAffectedDist,
Nader Jawadebb26b12021-04-01 15:54:09 -070079 float uInverseStretchAffectedDist,
Michel Comin Escude9ed86142021-04-13 12:30:51 -070080 float distanceStretched,
81 float interpolationStrength
Nader Jawad6701a602021-02-23 18:14:22 -080082 ) {
83 float offsetPos = uStretchAffectedDist - inPos;
Michel Comin Escude9ed86142021-04-13 12:30:51 -070084 float posBasedVariation = mix(
Nader Jawadbc341862021-05-06 10:48:18 -070085 1. ,easeIn(offsetPos, uInverseStretchAffectedDist), interpolationStrength);
Nader Jawad6701a602021-02-23 18:14:22 -080086 float stretchIntensity = overscroll * posBasedVariation;
Nader Jawad7148aa72021-03-31 13:11:57 -070087 return distanceStretched - (offsetPos / (1. + stretchIntensity));
Nader Jawad6701a602021-02-23 18:14:22 -080088 }
89
Nader Jawad7148aa72021-03-31 13:11:57 -070090 float computeOverscrollEnd(
Nader Jawad6701a602021-02-23 18:14:22 -080091 float inPos,
92 float overscroll,
93 float reverseStretchDist,
94 float uStretchAffectedDist,
Nader Jawadebb26b12021-04-01 15:54:09 -070095 float uInverseStretchAffectedDist,
Michel Comin Escude9ed86142021-04-13 12:30:51 -070096 float distanceStretched,
97 float interpolationStrength
Nader Jawad6701a602021-02-23 18:14:22 -080098 ) {
99 float offsetPos = inPos - reverseStretchDist;
Michel Comin Escude9ed86142021-04-13 12:30:51 -0700100 float posBasedVariation = mix(
Nader Jawadbc341862021-05-06 10:48:18 -0700101 1. ,easeIn(offsetPos, uInverseStretchAffectedDist), interpolationStrength);
Nader Jawad6701a602021-02-23 18:14:22 -0800102 float stretchIntensity = (-overscroll) * posBasedVariation;
Nader Jawad7148aa72021-03-31 13:11:57 -0700103 return 1 - (distanceStretched - (offsetPos / (1. + stretchIntensity)));
Nader Jawad6701a602021-02-23 18:14:22 -0800104 }
105
Nader Jawad7148aa72021-03-31 13:11:57 -0700106 // Prefer usage of return values over out parameters as it enables
107 // SKSL to properly inline method calls and works around potential GPU
108 // driver issues on Wembly. See b/182566543 for details
109 float computeOverscroll(
Nader Jawad6701a602021-02-23 18:14:22 -0800110 float inPos,
111 float overscroll,
112 float uStretchAffectedDist,
Nader Jawadebb26b12021-04-01 15:54:09 -0700113 float uInverseStretchAffectedDist,
Nader Jawad6701a602021-02-23 18:14:22 -0800114 float distanceStretched,
Michel Comin Escude9ed86142021-04-13 12:30:51 -0700115 float distanceDiff,
116 float interpolationStrength
Nader Jawad6701a602021-02-23 18:14:22 -0800117 ) {
Nader Jawad7148aa72021-03-31 13:11:57 -0700118 float outPos = inPos;
119 if (overscroll > 0) {
Nader Jawad6701a602021-02-23 18:14:22 -0800120 if (inPos <= uStretchAffectedDist) {
Nader Jawad7148aa72021-03-31 13:11:57 -0700121 outPos = computeOverscrollStart(
Nader Jawad6701a602021-02-23 18:14:22 -0800122 inPos,
123 overscroll,
124 uStretchAffectedDist,
Nader Jawadebb26b12021-04-01 15:54:09 -0700125 uInverseStretchAffectedDist,
Michel Comin Escude9ed86142021-04-13 12:30:51 -0700126 distanceStretched,
127 interpolationStrength
Nader Jawad6701a602021-02-23 18:14:22 -0800128 );
129 } else if (inPos >= distanceStretched) {
130 outPos = distanceDiff + inPos;
131 }
132 }
133 if (overscroll < 0) {
134 float stretchAffectedDist = 1. - uStretchAffectedDist;
135 if (inPos >= stretchAffectedDist) {
Nader Jawad7148aa72021-03-31 13:11:57 -0700136 outPos = computeOverscrollEnd(
Nader Jawad6701a602021-02-23 18:14:22 -0800137 inPos,
138 overscroll,
139 stretchAffectedDist,
140 uStretchAffectedDist,
Nader Jawadebb26b12021-04-01 15:54:09 -0700141 uInverseStretchAffectedDist,
Michel Comin Escude9ed86142021-04-13 12:30:51 -0700142 distanceStretched,
143 interpolationStrength
Nader Jawad6701a602021-02-23 18:14:22 -0800144 );
145 } else if (inPos < stretchAffectedDist) {
146 outPos = -distanceDiff + inPos;
147 }
148 }
Nader Jawad7148aa72021-03-31 13:11:57 -0700149 return outPos;
Nader Jawad6701a602021-02-23 18:14:22 -0800150 }
151
152 vec4 main(vec2 coord) {
153 // Normalize SKSL pixel coordinate into a unit vector
154 float inU = coord.x / viewportWidth;
155 float inV = coord.y / viewportHeight;
156 float outU;
157 float outV;
158 float stretchIntensity;
159 // Add the normalized scroll position within scrolling list
160 inU += uScrollX;
161 inV += uScrollY;
162 outU = inU;
163 outV = inV;
Nader Jawad7148aa72021-03-31 13:11:57 -0700164 outU = computeOverscroll(
Nader Jawad6701a602021-02-23 18:14:22 -0800165 inU,
166 uOverscrollX,
Nader Jawada225d212021-03-17 15:12:58 -0700167 uStretchAffectedDistX,
Nader Jawadebb26b12021-04-01 15:54:09 -0700168 uInverseDistanceStretchedX,
Nader Jawad6701a602021-02-23 18:14:22 -0800169 uDistanceStretchedX,
Michel Comin Escude9ed86142021-04-13 12:30:51 -0700170 uDistDiffX,
171 uInterpolationStrength
Nader Jawad6701a602021-02-23 18:14:22 -0800172 );
Nader Jawad7148aa72021-03-31 13:11:57 -0700173 outV = computeOverscroll(
Nader Jawad6701a602021-02-23 18:14:22 -0800174 inV,
175 uOverscrollY,
Nader Jawada225d212021-03-17 15:12:58 -0700176 uStretchAffectedDistY,
Nader Jawadebb26b12021-04-01 15:54:09 -0700177 uInverseDistanceStretchedY,
Nader Jawad6701a602021-02-23 18:14:22 -0800178 uDistanceStretchedY,
Michel Comin Escude9ed86142021-04-13 12:30:51 -0700179 uDistDiffY,
180 uInterpolationStrength
Nader Jawad6701a602021-02-23 18:14:22 -0800181 );
182 coord.x = outU * viewportWidth;
183 coord.y = outV * viewportHeight;
184 return sample(uContentTexture, coord);
185 })");
186
187static const float ZERO = 0.f;
Nader Jawadebb26b12021-04-01 15:54:09 -0700188static const float CONTENT_DISTANCE_STRETCHED = 1.f;
Michel Comin Escude9ed86142021-04-13 12:30:51 -0700189static const float INTERPOLATION_STRENGTH_VALUE = 0.7f;
Nader Jawad6701a602021-02-23 18:14:22 -0800190
Nader Jawad197743f2021-04-19 19:45:13 -0700191sk_sp<SkShader> StretchEffect::getShader(float width, float height,
192 const sk_sp<SkImage>& snapshotImage) const {
Nader Jawad6701a602021-02-23 18:14:22 -0800193 if (isEmpty()) {
194 return nullptr;
195 }
196
Nader Jawada225d212021-03-17 15:12:58 -0700197 float normOverScrollDistX = mStretchDirection.x();
198 float normOverScrollDistY = mStretchDirection.y();
Nader Jawadebb26b12021-04-01 15:54:09 -0700199 float distanceStretchedX = CONTENT_DISTANCE_STRETCHED / (1 + abs(normOverScrollDistX));
200 float distanceStretchedY = CONTENT_DISTANCE_STRETCHED / (1 + abs(normOverScrollDistY));
Nader Jawaddbc85fc2021-04-02 22:47:27 -0700201 float inverseDistanceStretchedX = 1.f / CONTENT_DISTANCE_STRETCHED;
202 float inverseDistanceStretchedY = 1.f / CONTENT_DISTANCE_STRETCHED;
Nader Jawadebb26b12021-04-01 15:54:09 -0700203 float diffX = distanceStretchedX - CONTENT_DISTANCE_STRETCHED;
204 float diffY = distanceStretchedY - CONTENT_DISTANCE_STRETCHED;
Nader Jawad6701a602021-02-23 18:14:22 -0800205
206 if (mBuilder == nullptr) {
207 mBuilder = std::make_unique<SkRuntimeShaderBuilder>(getStretchEffect());
208 }
209
210 mBuilder->child("uContentTexture") = snapshotImage->makeShader(
211 SkTileMode::kClamp, SkTileMode::kClamp, SkSamplingOptions(SkFilterMode::kLinear));
Michel Comin Escude9ed86142021-04-13 12:30:51 -0700212 mBuilder->uniform("uInterpolationStrength").set(&INTERPOLATION_STRENGTH_VALUE, 1);
Nader Jawadebb26b12021-04-01 15:54:09 -0700213 mBuilder->uniform("uStretchAffectedDistX").set(&CONTENT_DISTANCE_STRETCHED, 1);
214 mBuilder->uniform("uStretchAffectedDistY").set(&CONTENT_DISTANCE_STRETCHED, 1);
Nader Jawad6701a602021-02-23 18:14:22 -0800215 mBuilder->uniform("uDistanceStretchedX").set(&distanceStretchedX, 1);
216 mBuilder->uniform("uDistanceStretchedY").set(&distanceStretchedY, 1);
Nader Jawadebb26b12021-04-01 15:54:09 -0700217 mBuilder->uniform("uInverseDistanceStretchedX").set(&inverseDistanceStretchedX, 1);
218 mBuilder->uniform("uInverseDistanceStretchedY").set(&inverseDistanceStretchedY, 1);
Nader Jawad6701a602021-02-23 18:14:22 -0800219 mBuilder->uniform("uDistDiffX").set(&diffX, 1);
220 mBuilder->uniform("uDistDiffY").set(&diffY, 1);
221 mBuilder->uniform("uOverscrollX").set(&normOverScrollDistX, 1);
222 mBuilder->uniform("uOverscrollY").set(&normOverScrollDistY, 1);
223 mBuilder->uniform("uScrollX").set(&ZERO, 1);
224 mBuilder->uniform("uScrollY").set(&ZERO, 1);
Nader Jawad197743f2021-04-19 19:45:13 -0700225 mBuilder->uniform("viewportWidth").set(&width, 1);
226 mBuilder->uniform("viewportHeight").set(&height, 1);
Nader Jawad6701a602021-02-23 18:14:22 -0800227
Nader Jawad197743f2021-04-19 19:45:13 -0700228 return mBuilder->makeShader(nullptr, false);
Nader Jawad6701a602021-02-23 18:14:22 -0800229}
230
231sk_sp<SkRuntimeEffect> StretchEffect::getStretchEffect() {
Brian Osman64ee3242021-04-20 19:46:39 +0000232 const static SkRuntimeEffect::Result instance = SkRuntimeEffect::MakeForShader(stretchShader);
Nader Jawad6701a602021-02-23 18:14:22 -0800233 return instance.effect;
John Reck5cb290b2021-02-01 13:47:31 -0500234}
235
Nader Jawadbc341862021-05-06 10:48:18 -0700236/**
237 * Helper method that maps the input texture position to the stretch position
238 * based on the given overscroll value that represents an overscroll from
239 * either the top or left
240 * @param overscroll current overscroll value
241 * @param input normalized input position (can be x or y) on the input texture
242 * @return stretched position of the input normalized from 0 to 1
243 */
244float reverseMapStart(float overscroll, float input) {
245 float numerator = (-input * overscroll * overscroll) -
246 (2 * input * overscroll) - input;
247 float denominator = 1.f + (.3f * overscroll) +
248 (.7f * input * overscroll * overscroll) + (.7f * input * overscroll);
249 return -(numerator / denominator);
250}
251
252/**
253 * Helper method that maps the input texture position to the stretch position
254 * based on the given overscroll value that represents an overscroll from
255 * either the bottom or right
256 * @param overscroll current overscroll value
257 * @param input normalized input position (can be x or y) on the input texture
258 * @return stretched position of the input normalized from 0 to 1
259 */
260float reverseMapEnd(float overscroll, float input) {
261 float numerator = (.3f * overscroll * overscroll) -
262 (.3f * input * overscroll * overscroll) +
263 (1.3f * input * overscroll) - overscroll - input;
264 float denominator = (.7f * input * overscroll * overscroll) -
265 (.7f * input * overscroll) - (.7f * overscroll * overscroll) +
266 overscroll - 1.f;
267 return numerator / denominator;
268}
269
270/**
271 * Calculates the normalized stretch position given the normalized input
272 * position. This handles calculating the overscroll from either the
273 * top or left vs bottom or right depending on the sign of the given overscroll
274 * value
275 *
276 * @param overscroll unit vector of overscroll from -1 to 1 indicating overscroll
277 * from the bottom or right vs top or left respectively
278 * @param normalizedInput the
279 * @return
280 */
281float computeReverseOverscroll(float overscroll, float normalizedInput) {
282 float distanceStretched = 1.f / (1.f + abs(overscroll));
283 float distanceDiff = distanceStretched - 1.f;
284 if (overscroll > 0) {
285 float output = reverseMapStart(overscroll, normalizedInput);
286 if (output <= 1.0f) {
287 return output;
288 } else if (output >= distanceStretched){
289 return output - distanceDiff;
290 }
291 }
292
293 if (overscroll < 0) {
294 float output = reverseMapEnd(overscroll, normalizedInput);
295 if (output >= 0.f) {
296 return output;
297 } else if (output < 0.f){
298 return output + distanceDiff;
299 }
300 }
301 return normalizedInput;
302}
303
304float StretchEffect::computeStretchedPositionX(float normalizedX) const {
305 return computeReverseOverscroll(mStretchDirection.x(), normalizedX);
306}
307
308float StretchEffect::computeStretchedPositionY(float normalizedY) const {
309 return computeReverseOverscroll(mStretchDirection.y(), normalizedY);
310}
311
John Reck5cb290b2021-02-01 13:47:31 -0500312} // namespace android::uirenderer