blob: df480afcbe802a95c1910436430adbd79571fc11 [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 if (overscroll > 0) {
Nader Jawad91a55b62021-05-13 19:08:04 -0700119 if (inPos <= uStretchAffectedDist) {
120 return computeOverscrollStart(
121 inPos,
122 overscroll,
123 uStretchAffectedDist,
124 uInverseStretchAffectedDist,
125 distanceStretched,
126 interpolationStrength
127 );
128 } else {
129 return distanceDiff + inPos;
Nader Jawad6701a602021-02-23 18:14:22 -0800130 }
Nader Jawad91a55b62021-05-13 19:08:04 -0700131 } else if (overscroll < 0) {
132 float stretchAffectedDist = 1. - uStretchAffectedDist;
133 if (inPos >= stretchAffectedDist) {
134 return computeOverscrollEnd(
135 inPos,
136 overscroll,
137 stretchAffectedDist,
138 uStretchAffectedDist,
139 uInverseStretchAffectedDist,
140 distanceStretched,
141 interpolationStrength
142 );
143 } else {
144 return -distanceDiff + inPos;
Nader Jawad6701a602021-02-23 18:14:22 -0800145 }
Nader Jawad91a55b62021-05-13 19:08:04 -0700146 } else {
147 return inPos;
148 }
Nader Jawad6701a602021-02-23 18:14:22 -0800149 }
150
151 vec4 main(vec2 coord) {
152 // Normalize SKSL pixel coordinate into a unit vector
153 float inU = coord.x / viewportWidth;
154 float inV = coord.y / viewportHeight;
155 float outU;
156 float outV;
Nader Jawad6701a602021-02-23 18:14:22 -0800157 // Add the normalized scroll position within scrolling list
158 inU += uScrollX;
159 inV += uScrollY;
Nader Jawad7148aa72021-03-31 13:11:57 -0700160 outU = computeOverscroll(
Nader Jawad6701a602021-02-23 18:14:22 -0800161 inU,
162 uOverscrollX,
Nader Jawada225d212021-03-17 15:12:58 -0700163 uStretchAffectedDistX,
Nader Jawadebb26b12021-04-01 15:54:09 -0700164 uInverseDistanceStretchedX,
Nader Jawad6701a602021-02-23 18:14:22 -0800165 uDistanceStretchedX,
Michel Comin Escude9ed86142021-04-13 12:30:51 -0700166 uDistDiffX,
167 uInterpolationStrength
Nader Jawad6701a602021-02-23 18:14:22 -0800168 );
Nader Jawad7148aa72021-03-31 13:11:57 -0700169 outV = computeOverscroll(
Nader Jawad6701a602021-02-23 18:14:22 -0800170 inV,
171 uOverscrollY,
Nader Jawada225d212021-03-17 15:12:58 -0700172 uStretchAffectedDistY,
Nader Jawadebb26b12021-04-01 15:54:09 -0700173 uInverseDistanceStretchedY,
Nader Jawad6701a602021-02-23 18:14:22 -0800174 uDistanceStretchedY,
Michel Comin Escude9ed86142021-04-13 12:30:51 -0700175 uDistDiffY,
176 uInterpolationStrength
Nader Jawad6701a602021-02-23 18:14:22 -0800177 );
178 coord.x = outU * viewportWidth;
179 coord.y = outV * viewportHeight;
180 return sample(uContentTexture, coord);
181 })");
182
183static const float ZERO = 0.f;
Nader Jawadebb26b12021-04-01 15:54:09 -0700184static const float CONTENT_DISTANCE_STRETCHED = 1.f;
Michel Comin Escude9ed86142021-04-13 12:30:51 -0700185static const float INTERPOLATION_STRENGTH_VALUE = 0.7f;
Nader Jawad6701a602021-02-23 18:14:22 -0800186
Nader Jawad197743f2021-04-19 19:45:13 -0700187sk_sp<SkShader> StretchEffect::getShader(float width, float height,
188 const sk_sp<SkImage>& snapshotImage) const {
Nader Jawad6701a602021-02-23 18:14:22 -0800189 if (isEmpty()) {
190 return nullptr;
191 }
192
Nader Jawada225d212021-03-17 15:12:58 -0700193 float normOverScrollDistX = mStretchDirection.x();
194 float normOverScrollDistY = mStretchDirection.y();
Nader Jawadebb26b12021-04-01 15:54:09 -0700195 float distanceStretchedX = CONTENT_DISTANCE_STRETCHED / (1 + abs(normOverScrollDistX));
196 float distanceStretchedY = CONTENT_DISTANCE_STRETCHED / (1 + abs(normOverScrollDistY));
Nader Jawaddbc85fc2021-04-02 22:47:27 -0700197 float inverseDistanceStretchedX = 1.f / CONTENT_DISTANCE_STRETCHED;
198 float inverseDistanceStretchedY = 1.f / CONTENT_DISTANCE_STRETCHED;
Nader Jawadebb26b12021-04-01 15:54:09 -0700199 float diffX = distanceStretchedX - CONTENT_DISTANCE_STRETCHED;
200 float diffY = distanceStretchedY - CONTENT_DISTANCE_STRETCHED;
Nader Jawad6701a602021-02-23 18:14:22 -0800201
202 if (mBuilder == nullptr) {
203 mBuilder = std::make_unique<SkRuntimeShaderBuilder>(getStretchEffect());
204 }
205
206 mBuilder->child("uContentTexture") = snapshotImage->makeShader(
207 SkTileMode::kClamp, SkTileMode::kClamp, SkSamplingOptions(SkFilterMode::kLinear));
Michel Comin Escude9ed86142021-04-13 12:30:51 -0700208 mBuilder->uniform("uInterpolationStrength").set(&INTERPOLATION_STRENGTH_VALUE, 1);
Nader Jawadebb26b12021-04-01 15:54:09 -0700209 mBuilder->uniform("uStretchAffectedDistX").set(&CONTENT_DISTANCE_STRETCHED, 1);
210 mBuilder->uniform("uStretchAffectedDistY").set(&CONTENT_DISTANCE_STRETCHED, 1);
Nader Jawad6701a602021-02-23 18:14:22 -0800211 mBuilder->uniform("uDistanceStretchedX").set(&distanceStretchedX, 1);
212 mBuilder->uniform("uDistanceStretchedY").set(&distanceStretchedY, 1);
Nader Jawadebb26b12021-04-01 15:54:09 -0700213 mBuilder->uniform("uInverseDistanceStretchedX").set(&inverseDistanceStretchedX, 1);
214 mBuilder->uniform("uInverseDistanceStretchedY").set(&inverseDistanceStretchedY, 1);
Nader Jawad6701a602021-02-23 18:14:22 -0800215 mBuilder->uniform("uDistDiffX").set(&diffX, 1);
216 mBuilder->uniform("uDistDiffY").set(&diffY, 1);
217 mBuilder->uniform("uOverscrollX").set(&normOverScrollDistX, 1);
218 mBuilder->uniform("uOverscrollY").set(&normOverScrollDistY, 1);
219 mBuilder->uniform("uScrollX").set(&ZERO, 1);
220 mBuilder->uniform("uScrollY").set(&ZERO, 1);
Nader Jawad197743f2021-04-19 19:45:13 -0700221 mBuilder->uniform("viewportWidth").set(&width, 1);
222 mBuilder->uniform("viewportHeight").set(&height, 1);
Nader Jawad6701a602021-02-23 18:14:22 -0800223
Nader Jawad197743f2021-04-19 19:45:13 -0700224 return mBuilder->makeShader(nullptr, false);
Nader Jawad6701a602021-02-23 18:14:22 -0800225}
226
227sk_sp<SkRuntimeEffect> StretchEffect::getStretchEffect() {
Brian Osman64ee3242021-04-20 19:46:39 +0000228 const static SkRuntimeEffect::Result instance = SkRuntimeEffect::MakeForShader(stretchShader);
Nader Jawad6701a602021-02-23 18:14:22 -0800229 return instance.effect;
John Reck5cb290b2021-02-01 13:47:31 -0500230}
231
Nader Jawadbc341862021-05-06 10:48:18 -0700232/**
233 * Helper method that maps the input texture position to the stretch position
234 * based on the given overscroll value that represents an overscroll from
235 * either the top or left
236 * @param overscroll current overscroll value
237 * @param input normalized input position (can be x or y) on the input texture
238 * @return stretched position of the input normalized from 0 to 1
239 */
240float reverseMapStart(float overscroll, float input) {
241 float numerator = (-input * overscroll * overscroll) -
242 (2 * input * overscroll) - input;
243 float denominator = 1.f + (.3f * overscroll) +
244 (.7f * input * overscroll * overscroll) + (.7f * input * overscroll);
245 return -(numerator / denominator);
246}
247
248/**
249 * Helper method that maps the input texture position to the stretch position
250 * based on the given overscroll value that represents an overscroll from
251 * either the bottom or right
252 * @param overscroll current overscroll value
253 * @param input normalized input position (can be x or y) on the input texture
254 * @return stretched position of the input normalized from 0 to 1
255 */
256float reverseMapEnd(float overscroll, float input) {
257 float numerator = (.3f * overscroll * overscroll) -
258 (.3f * input * overscroll * overscroll) +
259 (1.3f * input * overscroll) - overscroll - input;
260 float denominator = (.7f * input * overscroll * overscroll) -
261 (.7f * input * overscroll) - (.7f * overscroll * overscroll) +
262 overscroll - 1.f;
263 return numerator / denominator;
264}
265
266/**
267 * Calculates the normalized stretch position given the normalized input
268 * position. This handles calculating the overscroll from either the
269 * top or left vs bottom or right depending on the sign of the given overscroll
270 * value
271 *
272 * @param overscroll unit vector of overscroll from -1 to 1 indicating overscroll
273 * from the bottom or right vs top or left respectively
274 * @param normalizedInput the
275 * @return
276 */
277float computeReverseOverscroll(float overscroll, float normalizedInput) {
278 float distanceStretched = 1.f / (1.f + abs(overscroll));
279 float distanceDiff = distanceStretched - 1.f;
280 if (overscroll > 0) {
281 float output = reverseMapStart(overscroll, normalizedInput);
282 if (output <= 1.0f) {
283 return output;
284 } else if (output >= distanceStretched){
285 return output - distanceDiff;
286 }
287 }
288
289 if (overscroll < 0) {
290 float output = reverseMapEnd(overscroll, normalizedInput);
291 if (output >= 0.f) {
292 return output;
293 } else if (output < 0.f){
294 return output + distanceDiff;
295 }
296 }
297 return normalizedInput;
298}
299
300float StretchEffect::computeStretchedPositionX(float normalizedX) const {
301 return computeReverseOverscroll(mStretchDirection.x(), normalizedX);
302}
303
304float StretchEffect::computeStretchedPositionY(float normalizedY) const {
305 return computeReverseOverscroll(mStretchDirection.y(), normalizedY);
306}
307
John Reck5cb290b2021-02-01 13:47:31 -0500308} // namespace android::uirenderer