blob: 2757c3952dbb133e6d4cf94643a2c4f394b87bd7 [file] [log] [blame]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "StretchEffect.h"
#include <SkImageFilter.h>
#include <SkRefCnt.h>
#include <SkRuntimeEffect.h>
#include <SkString.h>
#include <SkSurface.h>
#include <include/effects/SkImageFilters.h>
#include <memory>
namespace android::uirenderer {
static const SkString stretchShader = SkString(R"(
uniform shader uContentTexture;
// multiplier to apply to scale effect
uniform float uMaxStretchIntensity;
// Maximum percentage to stretch beyond bounds of target
uniform float uStretchAffectedDistX;
uniform float uStretchAffectedDistY;
// Distance stretched as a function of the normalized overscroll times
// scale intensity
uniform float uDistanceStretchedX;
uniform float uDistanceStretchedY;
uniform float uInverseDistanceStretchedX;
uniform float uInverseDistanceStretchedY;
uniform float uDistDiffX;
// Difference between the peak stretch amount and overscroll amount normalized
uniform float uDistDiffY;
// Horizontal offset represented as a ratio of pixels divided by the target width
uniform float uScrollX;
// Vertical offset represented as a ratio of pixels divided by the target height
uniform float uScrollY;
// Normalized overscroll amount in the horizontal direction
uniform float uOverscrollX;
// Normalized overscroll amount in the vertical direction
uniform float uOverscrollY;
uniform float viewportWidth; // target height in pixels
uniform float viewportHeight; // target width in pixels
// uInterpolationStrength is the intensity of the interpolation.
// if uInterpolationStrength is 0, then the stretch is constant for all the
// uStretchAffectedDist. if uInterpolationStrength is 1, then stretch intensity
// is interpolated based on the pixel position in the uStretchAffectedDist area;
// The closer we are from the scroll anchor point, the more it stretches,
// and the other way around.
uniform float uInterpolationStrength;
float easeIn(float t, float d) {
return t * d;
}
float computeOverscrollStart(
float inPos,
float overscroll,
float uStretchAffectedDist,
float uInverseStretchAffectedDist,
float distanceStretched,
float interpolationStrength
) {
float offsetPos = uStretchAffectedDist - inPos;
float posBasedVariation = mix(
1. ,easeIn(offsetPos, uInverseStretchAffectedDist), interpolationStrength);
float stretchIntensity = overscroll * posBasedVariation;
return distanceStretched - (offsetPos / (1. + stretchIntensity));
}
float computeOverscrollEnd(
float inPos,
float overscroll,
float reverseStretchDist,
float uStretchAffectedDist,
float uInverseStretchAffectedDist,
float distanceStretched,
float interpolationStrength,
float viewportDimension
) {
float offsetPos = inPos - reverseStretchDist;
float posBasedVariation = mix(
1. ,easeIn(offsetPos, uInverseStretchAffectedDist), interpolationStrength);
float stretchIntensity = (-overscroll) * posBasedVariation;
return viewportDimension - (distanceStretched - (offsetPos / (1. + stretchIntensity)));
}
// Prefer usage of return values over out parameters as it enables
// SKSL to properly inline method calls and works around potential GPU
// driver issues on Wembly. See b/182566543 for details
float computeOverscroll(
float inPos,
float overscroll,
float uStretchAffectedDist,
float uInverseStretchAffectedDist,
float distanceStretched,
float distanceDiff,
float interpolationStrength,
float viewportDimension
) {
if (overscroll > 0) {
if (inPos <= uStretchAffectedDist) {
return computeOverscrollStart(
inPos,
overscroll,
uStretchAffectedDist,
uInverseStretchAffectedDist,
distanceStretched,
interpolationStrength
);
} else {
return distanceDiff + inPos;
}
} else if (overscroll < 0) {
float stretchAffectedDist = viewportDimension - uStretchAffectedDist;
if (inPos >= stretchAffectedDist) {
return computeOverscrollEnd(
inPos,
overscroll,
stretchAffectedDist,
uStretchAffectedDist,
uInverseStretchAffectedDist,
distanceStretched,
interpolationStrength,
viewportDimension
);
} else {
return -distanceDiff + inPos;
}
} else {
return inPos;
}
}
vec4 main(vec2 coord) {
float inU = coord.x;
float inV = coord.y;
float outU;
float outV;
inU += uScrollX;
inV += uScrollY;
outU = computeOverscroll(
inU,
uOverscrollX,
uStretchAffectedDistX,
uInverseDistanceStretchedX,
uDistanceStretchedX,
uDistDiffX,
uInterpolationStrength,
viewportWidth
);
outV = computeOverscroll(
inV,
uOverscrollY,
uStretchAffectedDistY,
uInverseDistanceStretchedY,
uDistanceStretchedY,
uDistDiffY,
uInterpolationStrength,
viewportHeight
);
coord.x = outU;
coord.y = outV;
return uContentTexture.eval(coord);
})");
static const float ZERO = 0.f;
static const float INTERPOLATION_STRENGTH_VALUE = 0.7f;
static const char CONTENT_TEXTURE[] = "uContentTexture";
sk_sp<SkShader> StretchEffect::getShader(float width, float height,
const sk_sp<SkImage>& snapshotImage,
const SkMatrix* matrix) const {
if (isEmpty()) {
return nullptr;
}
float normOverScrollDistX = mStretchDirection.x();
float normOverScrollDistY = mStretchDirection.y();
float distanceStretchedX = width / (1 + abs(normOverScrollDistX));
float distanceStretchedY = height / (1 + abs(normOverScrollDistY));
float inverseDistanceStretchedX = 1.f / width;
float inverseDistanceStretchedY = 1.f / height;
float diffX = distanceStretchedX - width;
float diffY = distanceStretchedY - height;
if (mBuilder == nullptr) {
mBuilder = std::make_unique<SkRuntimeShaderBuilder>(getStretchEffect());
}
mBuilder->child(CONTENT_TEXTURE) =
snapshotImage->makeShader(SkTileMode::kClamp, SkTileMode::kClamp,
SkSamplingOptions(SkFilterMode::kLinear), matrix);
mBuilder->uniform("uInterpolationStrength").set(&INTERPOLATION_STRENGTH_VALUE, 1);
mBuilder->uniform("uStretchAffectedDistX").set(&width, 1);
mBuilder->uniform("uStretchAffectedDistY").set(&height, 1);
mBuilder->uniform("uDistanceStretchedX").set(&distanceStretchedX, 1);
mBuilder->uniform("uDistanceStretchedY").set(&distanceStretchedY, 1);
mBuilder->uniform("uInverseDistanceStretchedX").set(&inverseDistanceStretchedX, 1);
mBuilder->uniform("uInverseDistanceStretchedY").set(&inverseDistanceStretchedY, 1);
mBuilder->uniform("uDistDiffX").set(&diffX, 1);
mBuilder->uniform("uDistDiffY").set(&diffY, 1);
mBuilder->uniform("uOverscrollX").set(&normOverScrollDistX, 1);
mBuilder->uniform("uOverscrollY").set(&normOverScrollDistY, 1);
mBuilder->uniform("uScrollX").set(&ZERO, 1);
mBuilder->uniform("uScrollY").set(&ZERO, 1);
mBuilder->uniform("viewportWidth").set(&width, 1);
mBuilder->uniform("viewportHeight").set(&height, 1);
auto result = mBuilder->makeShader();
mBuilder->child(CONTENT_TEXTURE) = nullptr;
return result;
}
sk_sp<SkRuntimeEffect> StretchEffect::getStretchEffect() {
const static SkRuntimeEffect::Result instance = SkRuntimeEffect::MakeForShader(stretchShader);
return instance.effect;
}
/**
* Helper method that maps the input texture position to the stretch position
* based on the given overscroll value that represents an overscroll from
* either the top or left
* @param overscroll current overscroll value
* @param input normalized input position (can be x or y) on the input texture
* @return stretched position of the input normalized from 0 to 1
*/
float reverseMapStart(float overscroll, float input) {
float numerator = (-input * overscroll * overscroll) -
(2 * input * overscroll) - input;
float denominator = 1.f + (.3f * overscroll) +
(.7f * input * overscroll * overscroll) + (.7f * input * overscroll);
return -(numerator / denominator);
}
/**
* Helper method that maps the input texture position to the stretch position
* based on the given overscroll value that represents an overscroll from
* either the bottom or right
* @param overscroll current overscroll value
* @param input normalized input position (can be x or y) on the input texture
* @return stretched position of the input normalized from 0 to 1
*/
float reverseMapEnd(float overscroll, float input) {
float numerator = (.3f * overscroll * overscroll) -
(.3f * input * overscroll * overscroll) +
(1.3f * input * overscroll) - overscroll - input;
float denominator = (.7f * input * overscroll * overscroll) -
(.7f * input * overscroll) - (.7f * overscroll * overscroll) +
overscroll - 1.f;
return numerator / denominator;
}
/**
* Calculates the normalized stretch position given the normalized input
* position. This handles calculating the overscroll from either the
* top or left vs bottom or right depending on the sign of the given overscroll
* value
*
* @param overscroll unit vector of overscroll from -1 to 1 indicating overscroll
* from the bottom or right vs top or left respectively
* @param normalizedInput the
* @return
*/
float computeReverseOverscroll(float overscroll, float normalizedInput) {
float distanceStretched = 1.f / (1.f + abs(overscroll));
float distanceDiff = distanceStretched - 1.f;
if (overscroll > 0) {
float output = reverseMapStart(overscroll, normalizedInput);
if (output <= 1.0f) {
return output;
} else if (output >= distanceStretched){
return output - distanceDiff;
}
}
if (overscroll < 0) {
float output = reverseMapEnd(overscroll, normalizedInput);
if (output >= 0.f) {
return output;
} else if (output < 0.f){
return output + distanceDiff;
}
}
return normalizedInput;
}
float StretchEffect::computeStretchedPositionX(float normalizedX) const {
return computeReverseOverscroll(mStretchDirection.x(), normalizedX);
}
float StretchEffect::computeStretchedPositionY(float normalizedY) const {
return computeReverseOverscroll(mStretchDirection.y(), normalizedY);
}
} // namespace android::uirenderer