blob: 43f805d906a5e14bc4bd508e731d7045ef44908e [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;
Nader Jawad6701a602021-02-23 18:14:22 -0800184 return sample(uContentTexture, coord);
185 })");
186
187static const float ZERO = 0.f;
Michel Comin Escude9ed86142021-04-13 12:30:51 -0700188static const float INTERPOLATION_STRENGTH_VALUE = 0.7f;
Nader Jawad6701a602021-02-23 18:14:22 -0800189
Nader Jawad197743f2021-04-19 19:45:13 -0700190sk_sp<SkShader> StretchEffect::getShader(float width, float height,
Nader Jawad6aff4812021-06-09 10:14:43 -0700191 const sk_sp<SkImage>& snapshotImage,
192 const SkMatrix* matrix) 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 Jawad76d84ae2021-05-13 20:43:18 -0700199 float distanceStretchedX = width / (1 + abs(normOverScrollDistX));
200 float distanceStretchedY = height / (1 + abs(normOverScrollDistY));
201 float inverseDistanceStretchedX = 1.f / width;
202 float inverseDistanceStretchedY = 1.f / height;
203 float diffX = distanceStretchedX - width;
204 float diffY = distanceStretchedY - height;
Nader Jawad6701a602021-02-23 18:14:22 -0800205
206 if (mBuilder == nullptr) {
207 mBuilder = std::make_unique<SkRuntimeShaderBuilder>(getStretchEffect());
208 }
209
Nader Jawad6aff4812021-06-09 10:14:43 -0700210 mBuilder->child("uContentTexture") =
211 snapshotImage->makeShader(SkTileMode::kClamp, SkTileMode::kClamp,
212 SkSamplingOptions(SkFilterMode::kLinear), matrix);
Michel Comin Escude9ed86142021-04-13 12:30:51 -0700213 mBuilder->uniform("uInterpolationStrength").set(&INTERPOLATION_STRENGTH_VALUE, 1);
Nader Jawad76d84ae2021-05-13 20:43:18 -0700214 mBuilder->uniform("uStretchAffectedDistX").set(&width, 1);
215 mBuilder->uniform("uStretchAffectedDistY").set(&height, 1);
Nader Jawad6701a602021-02-23 18:14:22 -0800216 mBuilder->uniform("uDistanceStretchedX").set(&distanceStretchedX, 1);
217 mBuilder->uniform("uDistanceStretchedY").set(&distanceStretchedY, 1);
Nader Jawadebb26b12021-04-01 15:54:09 -0700218 mBuilder->uniform("uInverseDistanceStretchedX").set(&inverseDistanceStretchedX, 1);
219 mBuilder->uniform("uInverseDistanceStretchedY").set(&inverseDistanceStretchedY, 1);
Nader Jawad6701a602021-02-23 18:14:22 -0800220 mBuilder->uniform("uDistDiffX").set(&diffX, 1);
221 mBuilder->uniform("uDistDiffY").set(&diffY, 1);
222 mBuilder->uniform("uOverscrollX").set(&normOverScrollDistX, 1);
223 mBuilder->uniform("uOverscrollY").set(&normOverScrollDistY, 1);
224 mBuilder->uniform("uScrollX").set(&ZERO, 1);
225 mBuilder->uniform("uScrollY").set(&ZERO, 1);
Nader Jawad197743f2021-04-19 19:45:13 -0700226 mBuilder->uniform("viewportWidth").set(&width, 1);
227 mBuilder->uniform("viewportHeight").set(&height, 1);
Nader Jawad6701a602021-02-23 18:14:22 -0800228
Nader Jawad197743f2021-04-19 19:45:13 -0700229 return mBuilder->makeShader(nullptr, false);
Nader Jawad6701a602021-02-23 18:14:22 -0800230}
231
232sk_sp<SkRuntimeEffect> StretchEffect::getStretchEffect() {
Brian Osman64ee3242021-04-20 19:46:39 +0000233 const static SkRuntimeEffect::Result instance = SkRuntimeEffect::MakeForShader(stretchShader);
Nader Jawad6701a602021-02-23 18:14:22 -0800234 return instance.effect;
John Reck5cb290b2021-02-01 13:47:31 -0500235}
236
Nader Jawadbc341862021-05-06 10:48:18 -0700237/**
238 * Helper method that maps the input texture position to the stretch position
239 * based on the given overscroll value that represents an overscroll from
240 * either the top or left
241 * @param overscroll current overscroll value
242 * @param input normalized input position (can be x or y) on the input texture
243 * @return stretched position of the input normalized from 0 to 1
244 */
245float reverseMapStart(float overscroll, float input) {
246 float numerator = (-input * overscroll * overscroll) -
247 (2 * input * overscroll) - input;
248 float denominator = 1.f + (.3f * overscroll) +
249 (.7f * input * overscroll * overscroll) + (.7f * input * overscroll);
250 return -(numerator / denominator);
251}
252
253/**
254 * Helper method that maps the input texture position to the stretch position
255 * based on the given overscroll value that represents an overscroll from
256 * either the bottom or right
257 * @param overscroll current overscroll value
258 * @param input normalized input position (can be x or y) on the input texture
259 * @return stretched position of the input normalized from 0 to 1
260 */
261float reverseMapEnd(float overscroll, float input) {
262 float numerator = (.3f * overscroll * overscroll) -
263 (.3f * input * overscroll * overscroll) +
264 (1.3f * input * overscroll) - overscroll - input;
265 float denominator = (.7f * input * overscroll * overscroll) -
266 (.7f * input * overscroll) - (.7f * overscroll * overscroll) +
267 overscroll - 1.f;
268 return numerator / denominator;
269}
270
271/**
272 * Calculates the normalized stretch position given the normalized input
273 * position. This handles calculating the overscroll from either the
274 * top or left vs bottom or right depending on the sign of the given overscroll
275 * value
276 *
277 * @param overscroll unit vector of overscroll from -1 to 1 indicating overscroll
278 * from the bottom or right vs top or left respectively
279 * @param normalizedInput the
280 * @return
281 */
282float computeReverseOverscroll(float overscroll, float normalizedInput) {
283 float distanceStretched = 1.f / (1.f + abs(overscroll));
284 float distanceDiff = distanceStretched - 1.f;
285 if (overscroll > 0) {
286 float output = reverseMapStart(overscroll, normalizedInput);
287 if (output <= 1.0f) {
288 return output;
289 } else if (output >= distanceStretched){
290 return output - distanceDiff;
291 }
292 }
293
294 if (overscroll < 0) {
295 float output = reverseMapEnd(overscroll, normalizedInput);
296 if (output >= 0.f) {
297 return output;
298 } else if (output < 0.f){
299 return output + distanceDiff;
300 }
301 }
302 return normalizedInput;
303}
304
305float StretchEffect::computeStretchedPositionX(float normalizedX) const {
306 return computeReverseOverscroll(mStretchDirection.x(), normalizedX);
307}
308
309float StretchEffect::computeStretchedPositionY(float normalizedY) const {
310 return computeReverseOverscroll(mStretchDirection.y(), normalizedY);
311}
312
John Reck5cb290b2021-02-01 13:47:31 -0500313} // namespace android::uirenderer