blob: 2757c3952dbb133e6d4cf94643a2c4f394b87bd7 [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,
Nader Jawad76d84ae2021-05-13 20:43:18 -070097 float interpolationStrength,
98 float viewportDimension
Nader Jawad6701a602021-02-23 18:14:22 -080099 ) {
100 float offsetPos = inPos - reverseStretchDist;
Michel Comin Escude9ed86142021-04-13 12:30:51 -0700101 float posBasedVariation = mix(
Nader Jawadbc341862021-05-06 10:48:18 -0700102 1. ,easeIn(offsetPos, uInverseStretchAffectedDist), interpolationStrength);
Nader Jawad6701a602021-02-23 18:14:22 -0800103 float stretchIntensity = (-overscroll) * posBasedVariation;
Nader Jawad76d84ae2021-05-13 20:43:18 -0700104 return viewportDimension - (distanceStretched - (offsetPos / (1. + stretchIntensity)));
Nader Jawad6701a602021-02-23 18:14:22 -0800105 }
106
Nader Jawad7148aa72021-03-31 13:11:57 -0700107 // 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(
Nader Jawad6701a602021-02-23 18:14:22 -0800111 float inPos,
112 float overscroll,
113 float uStretchAffectedDist,
Nader Jawadebb26b12021-04-01 15:54:09 -0700114 float uInverseStretchAffectedDist,
Nader Jawad6701a602021-02-23 18:14:22 -0800115 float distanceStretched,
Michel Comin Escude9ed86142021-04-13 12:30:51 -0700116 float distanceDiff,
Nader Jawad76d84ae2021-05-13 20:43:18 -0700117 float interpolationStrength,
118 float viewportDimension
Nader Jawad6701a602021-02-23 18:14:22 -0800119 ) {
Nader Jawad7148aa72021-03-31 13:11:57 -0700120 if (overscroll > 0) {
Nader Jawad91a55b62021-05-13 19:08:04 -0700121 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;
Nader Jawad6701a602021-02-23 18:14:22 -0800132 }
Nader Jawad91a55b62021-05-13 19:08:04 -0700133 } else if (overscroll < 0) {
Nader Jawad76d84ae2021-05-13 20:43:18 -0700134 float stretchAffectedDist = viewportDimension - uStretchAffectedDist;
Nader Jawad91a55b62021-05-13 19:08:04 -0700135 if (inPos >= stretchAffectedDist) {
136 return computeOverscrollEnd(
137 inPos,
138 overscroll,
139 stretchAffectedDist,
140 uStretchAffectedDist,
141 uInverseStretchAffectedDist,
142 distanceStretched,
Nader Jawad76d84ae2021-05-13 20:43:18 -0700143 interpolationStrength,
144 viewportDimension
Nader Jawad91a55b62021-05-13 19:08:04 -0700145 );
146 } else {
147 return -distanceDiff + inPos;
Nader Jawad6701a602021-02-23 18:14:22 -0800148 }
Nader Jawad91a55b62021-05-13 19:08:04 -0700149 } else {
150 return inPos;
151 }
Nader Jawad6701a602021-02-23 18:14:22 -0800152 }
153
154 vec4 main(vec2 coord) {
Nader Jawad76d84ae2021-05-13 20:43:18 -0700155 float inU = coord.x;
156 float inV = coord.y;
Nader Jawad6701a602021-02-23 18:14:22 -0800157 float outU;
158 float outV;
Nader Jawad76d84ae2021-05-13 20:43:18 -0700159
Nader Jawad6701a602021-02-23 18:14:22 -0800160 inU += uScrollX;
161 inV += uScrollY;
Nader Jawad7148aa72021-03-31 13:11:57 -0700162 outU = computeOverscroll(
Nader Jawad6701a602021-02-23 18:14:22 -0800163 inU,
164 uOverscrollX,
Nader Jawada225d212021-03-17 15:12:58 -0700165 uStretchAffectedDistX,
Nader Jawadebb26b12021-04-01 15:54:09 -0700166 uInverseDistanceStretchedX,
Nader Jawad6701a602021-02-23 18:14:22 -0800167 uDistanceStretchedX,
Michel Comin Escude9ed86142021-04-13 12:30:51 -0700168 uDistDiffX,
Nader Jawad76d84ae2021-05-13 20:43:18 -0700169 uInterpolationStrength,
170 viewportWidth
Nader Jawad6701a602021-02-23 18:14:22 -0800171 );
Nader Jawad7148aa72021-03-31 13:11:57 -0700172 outV = computeOverscroll(
Nader Jawad6701a602021-02-23 18:14:22 -0800173 inV,
174 uOverscrollY,
Nader Jawada225d212021-03-17 15:12:58 -0700175 uStretchAffectedDistY,
Nader Jawadebb26b12021-04-01 15:54:09 -0700176 uInverseDistanceStretchedY,
Nader Jawad6701a602021-02-23 18:14:22 -0800177 uDistanceStretchedY,
Michel Comin Escude9ed86142021-04-13 12:30:51 -0700178 uDistDiffY,
Nader Jawad76d84ae2021-05-13 20:43:18 -0700179 uInterpolationStrength,
180 viewportHeight
Nader Jawad6701a602021-02-23 18:14:22 -0800181 );
Nader Jawad76d84ae2021-05-13 20:43:18 -0700182 coord.x = outU;
183 coord.y = outV;
Brian Osmanbb10b9c2021-09-07 20:12:32 +0000184 return uContentTexture.eval(coord);
Nader Jawad6701a602021-02-23 18:14:22 -0800185 })");
186
187static const float ZERO = 0.f;
Michel Comin Escude9ed86142021-04-13 12:30:51 -0700188static const float INTERPOLATION_STRENGTH_VALUE = 0.7f;
Nader Jawad3206cce2021-06-23 11:48:58 -0700189static const char CONTENT_TEXTURE[] = "uContentTexture";
Nader Jawad6701a602021-02-23 18:14:22 -0800190
Nader Jawad197743f2021-04-19 19:45:13 -0700191sk_sp<SkShader> StretchEffect::getShader(float width, float height,
Nader Jawad6aff4812021-06-09 10:14:43 -0700192 const sk_sp<SkImage>& snapshotImage,
193 const SkMatrix* matrix) const {
Nader Jawad6701a602021-02-23 18:14:22 -0800194 if (isEmpty()) {
195 return nullptr;
196 }
197
Nader Jawada225d212021-03-17 15:12:58 -0700198 float normOverScrollDistX = mStretchDirection.x();
199 float normOverScrollDistY = mStretchDirection.y();
Nader Jawad76d84ae2021-05-13 20:43:18 -0700200 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;
Nader Jawad6701a602021-02-23 18:14:22 -0800206
207 if (mBuilder == nullptr) {
208 mBuilder = std::make_unique<SkRuntimeShaderBuilder>(getStretchEffect());
209 }
210
Nader Jawad3206cce2021-06-23 11:48:58 -0700211 mBuilder->child(CONTENT_TEXTURE) =
Nader Jawad6aff4812021-06-09 10:14:43 -0700212 snapshotImage->makeShader(SkTileMode::kClamp, SkTileMode::kClamp,
213 SkSamplingOptions(SkFilterMode::kLinear), matrix);
Michel Comin Escude9ed86142021-04-13 12:30:51 -0700214 mBuilder->uniform("uInterpolationStrength").set(&INTERPOLATION_STRENGTH_VALUE, 1);
Nader Jawad76d84ae2021-05-13 20:43:18 -0700215 mBuilder->uniform("uStretchAffectedDistX").set(&width, 1);
216 mBuilder->uniform("uStretchAffectedDistY").set(&height, 1);
Nader Jawad6701a602021-02-23 18:14:22 -0800217 mBuilder->uniform("uDistanceStretchedX").set(&distanceStretchedX, 1);
218 mBuilder->uniform("uDistanceStretchedY").set(&distanceStretchedY, 1);
Nader Jawadebb26b12021-04-01 15:54:09 -0700219 mBuilder->uniform("uInverseDistanceStretchedX").set(&inverseDistanceStretchedX, 1);
220 mBuilder->uniform("uInverseDistanceStretchedY").set(&inverseDistanceStretchedY, 1);
Nader Jawad6701a602021-02-23 18:14:22 -0800221 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);
Nader Jawad197743f2021-04-19 19:45:13 -0700227 mBuilder->uniform("viewportWidth").set(&width, 1);
228 mBuilder->uniform("viewportHeight").set(&height, 1);
Nader Jawad6701a602021-02-23 18:14:22 -0800229
Brian Osmanbb7c43d2022-02-03 14:48:12 -0500230 auto result = mBuilder->makeShader();
Nader Jawad3206cce2021-06-23 11:48:58 -0700231 mBuilder->child(CONTENT_TEXTURE) = nullptr;
232 return result;
Nader Jawad6701a602021-02-23 18:14:22 -0800233}
234
235sk_sp<SkRuntimeEffect> StretchEffect::getStretchEffect() {
Brian Osman64ee3242021-04-20 19:46:39 +0000236 const static SkRuntimeEffect::Result instance = SkRuntimeEffect::MakeForShader(stretchShader);
Nader Jawad6701a602021-02-23 18:14:22 -0800237 return instance.effect;
John Reck5cb290b2021-02-01 13:47:31 -0500238}
239
Nader Jawadbc341862021-05-06 10:48:18 -0700240/**
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 */
248float 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 */
264float 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 */
285float 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
308float StretchEffect::computeStretchedPositionX(float normalizedX) const {
309 return computeReverseOverscroll(mStretchDirection.x(), normalizedX);
310}
311
312float StretchEffect::computeStretchedPositionY(float normalizedY) const {
313 return computeReverseOverscroll(mStretchDirection.y(), normalizedY);
314}
315
John Reck5cb290b2021-02-01 13:47:31 -0500316} // namespace android::uirenderer