blob: 1519d69d705348ae47edfc03b7c37ab64f877fde [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 Jawadebb26b12021-04-01 15:54:09 -070071 float easeInCubic(float t, float d) {
72 float tmp = t * d;
73 return tmp * tmp * tmp;
74 }
75
Nader Jawad7148aa72021-03-31 13:11:57 -070076 float computeOverscrollStart(
Nader Jawad6701a602021-02-23 18:14:22 -080077 float inPos,
78 float overscroll,
79 float uStretchAffectedDist,
Nader Jawadebb26b12021-04-01 15:54:09 -070080 float uInverseStretchAffectedDist,
Michel Comin Escude9ed86142021-04-13 12:30:51 -070081 float distanceStretched,
82 float interpolationStrength
Nader Jawad6701a602021-02-23 18:14:22 -080083 ) {
84 float offsetPos = uStretchAffectedDist - inPos;
Michel Comin Escude9ed86142021-04-13 12:30:51 -070085 float posBasedVariation = mix(
86 1. ,easeInCubic(offsetPos, uInverseStretchAffectedDist), interpolationStrength);
Nader Jawad6701a602021-02-23 18:14:22 -080087 float stretchIntensity = overscroll * posBasedVariation;
Nader Jawad7148aa72021-03-31 13:11:57 -070088 return distanceStretched - (offsetPos / (1. + stretchIntensity));
Nader Jawad6701a602021-02-23 18:14:22 -080089 }
90
Nader Jawad7148aa72021-03-31 13:11:57 -070091 float computeOverscrollEnd(
Nader Jawad6701a602021-02-23 18:14:22 -080092 float inPos,
93 float overscroll,
94 float reverseStretchDist,
95 float uStretchAffectedDist,
Nader Jawadebb26b12021-04-01 15:54:09 -070096 float uInverseStretchAffectedDist,
Michel Comin Escude9ed86142021-04-13 12:30:51 -070097 float distanceStretched,
98 float interpolationStrength
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(
102 1. ,easeInCubic(offsetPos, uInverseStretchAffectedDist), interpolationStrength);
Nader Jawad6701a602021-02-23 18:14:22 -0800103 float stretchIntensity = (-overscroll) * posBasedVariation;
Nader Jawad7148aa72021-03-31 13:11:57 -0700104 return 1 - (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,
117 float interpolationStrength
Nader Jawad6701a602021-02-23 18:14:22 -0800118 ) {
Nader Jawad7148aa72021-03-31 13:11:57 -0700119 float outPos = inPos;
120 if (overscroll > 0) {
Nader Jawad6701a602021-02-23 18:14:22 -0800121 if (inPos <= uStretchAffectedDist) {
Nader Jawad7148aa72021-03-31 13:11:57 -0700122 outPos = computeOverscrollStart(
Nader Jawad6701a602021-02-23 18:14:22 -0800123 inPos,
124 overscroll,
125 uStretchAffectedDist,
Nader Jawadebb26b12021-04-01 15:54:09 -0700126 uInverseStretchAffectedDist,
Michel Comin Escude9ed86142021-04-13 12:30:51 -0700127 distanceStretched,
128 interpolationStrength
Nader Jawad6701a602021-02-23 18:14:22 -0800129 );
130 } else if (inPos >= distanceStretched) {
131 outPos = distanceDiff + inPos;
132 }
133 }
134 if (overscroll < 0) {
135 float stretchAffectedDist = 1. - uStretchAffectedDist;
136 if (inPos >= stretchAffectedDist) {
Nader Jawad7148aa72021-03-31 13:11:57 -0700137 outPos = computeOverscrollEnd(
Nader Jawad6701a602021-02-23 18:14:22 -0800138 inPos,
139 overscroll,
140 stretchAffectedDist,
141 uStretchAffectedDist,
Nader Jawadebb26b12021-04-01 15:54:09 -0700142 uInverseStretchAffectedDist,
Michel Comin Escude9ed86142021-04-13 12:30:51 -0700143 distanceStretched,
144 interpolationStrength
Nader Jawad6701a602021-02-23 18:14:22 -0800145 );
146 } else if (inPos < stretchAffectedDist) {
147 outPos = -distanceDiff + inPos;
148 }
149 }
Nader Jawad7148aa72021-03-31 13:11:57 -0700150 return outPos;
Nader Jawad6701a602021-02-23 18:14:22 -0800151 }
152
153 vec4 main(vec2 coord) {
154 // Normalize SKSL pixel coordinate into a unit vector
155 float inU = coord.x / viewportWidth;
156 float inV = coord.y / viewportHeight;
157 float outU;
158 float outV;
159 float stretchIntensity;
160 // Add the normalized scroll position within scrolling list
161 inU += uScrollX;
162 inV += uScrollY;
163 outU = inU;
164 outV = inV;
Nader Jawad7148aa72021-03-31 13:11:57 -0700165 outU = computeOverscroll(
Nader Jawad6701a602021-02-23 18:14:22 -0800166 inU,
167 uOverscrollX,
Nader Jawada225d212021-03-17 15:12:58 -0700168 uStretchAffectedDistX,
Nader Jawadebb26b12021-04-01 15:54:09 -0700169 uInverseDistanceStretchedX,
Nader Jawad6701a602021-02-23 18:14:22 -0800170 uDistanceStretchedX,
Michel Comin Escude9ed86142021-04-13 12:30:51 -0700171 uDistDiffX,
172 uInterpolationStrength
Nader Jawad6701a602021-02-23 18:14:22 -0800173 );
Nader Jawad7148aa72021-03-31 13:11:57 -0700174 outV = computeOverscroll(
Nader Jawad6701a602021-02-23 18:14:22 -0800175 inV,
176 uOverscrollY,
Nader Jawada225d212021-03-17 15:12:58 -0700177 uStretchAffectedDistY,
Nader Jawadebb26b12021-04-01 15:54:09 -0700178 uInverseDistanceStretchedY,
Nader Jawad6701a602021-02-23 18:14:22 -0800179 uDistanceStretchedY,
Michel Comin Escude9ed86142021-04-13 12:30:51 -0700180 uDistDiffY,
181 uInterpolationStrength
Nader Jawad6701a602021-02-23 18:14:22 -0800182 );
183 coord.x = outU * viewportWidth;
184 coord.y = outV * viewportHeight;
185 return sample(uContentTexture, coord);
186 })");
187
188static const float ZERO = 0.f;
Nader Jawadebb26b12021-04-01 15:54:09 -0700189static const float CONTENT_DISTANCE_STRETCHED = 1.f;
Michel Comin Escude9ed86142021-04-13 12:30:51 -0700190static const float INTERPOLATION_STRENGTH_VALUE = 0.7f;
Nader Jawad6701a602021-02-23 18:14:22 -0800191
Nader Jawad197743f2021-04-19 19:45:13 -0700192sk_sp<SkShader> StretchEffect::getShader(float width, float height,
193 const sk_sp<SkImage>& snapshotImage) 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 Jawadebb26b12021-04-01 15:54:09 -0700200 float distanceStretchedX = CONTENT_DISTANCE_STRETCHED / (1 + abs(normOverScrollDistX));
201 float distanceStretchedY = CONTENT_DISTANCE_STRETCHED / (1 + abs(normOverScrollDistY));
Nader Jawaddbc85fc2021-04-02 22:47:27 -0700202 float inverseDistanceStretchedX = 1.f / CONTENT_DISTANCE_STRETCHED;
203 float inverseDistanceStretchedY = 1.f / CONTENT_DISTANCE_STRETCHED;
Nader Jawadebb26b12021-04-01 15:54:09 -0700204 float diffX = distanceStretchedX - CONTENT_DISTANCE_STRETCHED;
205 float diffY = distanceStretchedY - CONTENT_DISTANCE_STRETCHED;
Nader Jawad6701a602021-02-23 18:14:22 -0800206
207 if (mBuilder == nullptr) {
208 mBuilder = std::make_unique<SkRuntimeShaderBuilder>(getStretchEffect());
209 }
210
211 mBuilder->child("uContentTexture") = snapshotImage->makeShader(
212 SkTileMode::kClamp, SkTileMode::kClamp, SkSamplingOptions(SkFilterMode::kLinear));
Michel Comin Escude9ed86142021-04-13 12:30:51 -0700213 mBuilder->uniform("uInterpolationStrength").set(&INTERPOLATION_STRENGTH_VALUE, 1);
Nader Jawadebb26b12021-04-01 15:54:09 -0700214 mBuilder->uniform("uStretchAffectedDistX").set(&CONTENT_DISTANCE_STRETCHED, 1);
215 mBuilder->uniform("uStretchAffectedDistY").set(&CONTENT_DISTANCE_STRETCHED, 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
237} // namespace android::uirenderer