diff options
author | Nader Jawad <njawad@google.com> | 2021-02-23 18:14:22 -0800 |
---|---|---|
committer | Nader Jawad <njawad@google.com> | 2021-03-03 14:56:17 -0800 |
commit | 6701a6014f6fea668abc25b804491b50b0c35afc (patch) | |
tree | bcd1612ee73f5a8aa60f0d7d20d9c6c96e033d4f | |
parent | b5b5aa24ac457709844370f85d6ceaa7f8d3d2cc (diff) |
Wire SKSL based stretch shader to HWUI
--Ported SKSL based stretch shader from OpenGL prototype
--Hooked up the stretch APIs in RenderNode to the stretch
shader.
--Updated RenderNode layer logic to promote the RenderNode to
a layer if there is a stretch to be applied to it in order
to feed the layer as input to the stretch shader
Bug: 179047472
Test: builds + sample overscroll stretches + updated CTS test
Change-Id: I744ff70099fe251ce07f23d067bf13444a468c08
-rw-r--r-- | core/java/android/view/View.java | 3 | ||||
-rw-r--r-- | core/java/android/widget/EdgeEffect.java | 91 | ||||
-rw-r--r-- | core/java/android/widget/HorizontalScrollView.java | 20 | ||||
-rw-r--r-- | core/java/android/widget/ScrollView.java | 20 | ||||
-rw-r--r-- | graphics/java/android/graphics/RenderNode.java | 19 | ||||
-rw-r--r-- | libs/hwui/RenderProperties.h | 4 | ||||
-rw-r--r-- | libs/hwui/effects/StretchEffect.cpp | 190 | ||||
-rw-r--r-- | libs/hwui/effects/StretchEffect.h | 38 | ||||
-rw-r--r-- | libs/hwui/jni/android_graphics_RenderNode.cpp | 220 | ||||
-rw-r--r-- | libs/hwui/pipeline/skia/RenderNodeDrawable.cpp | 14 | ||||
-rw-r--r-- | tests/HwAccelerationTest/AndroidManifest.xml | 18 | ||||
-rw-r--r-- | tests/HwAccelerationTest/res/layout/stretch_layout.xml | 88 | ||||
-rw-r--r-- | tests/HwAccelerationTest/src/com/android/test/hwui/EdgeEffectStretchActivity.java | 36 | ||||
-rw-r--r-- | tests/HwAccelerationTest/src/com/android/test/hwui/StretchShaderActivity.java | 540 |
14 files changed, 1156 insertions, 145 deletions
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index ebef4646b0d9..ab7732b47ca4 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -22188,9 +22188,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * and hardware acceleration. */ boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) { - // Clear the overscroll effect: - // TODO: Use internal API instead of overriding the existing RenderEffect - setRenderEffect(null); final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated(); /* If an attached view draws to a HW canvas, it may use its RenderNode + DisplayList. diff --git a/core/java/android/widget/EdgeEffect.java b/core/java/android/widget/EdgeEffect.java index 1b62266c12e2..dc42ad583543 100644 --- a/core/java/android/widget/EdgeEffect.java +++ b/core/java/android/widget/EdgeEffect.java @@ -29,7 +29,6 @@ import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.RecordingCanvas; import android.graphics.Rect; -import android.graphics.RenderEffect; import android.graphics.RenderNode; import android.os.Build; import android.util.AttributeSet; @@ -83,6 +82,8 @@ public class EdgeEffect { public @interface EdgeEffectType { } + private static final float DEFAULT_MAX_STRETCH_INTENSITY = 1.5f; + @SuppressWarnings("UnusedDeclaration") private static final String TAG = "EdgeEffect"; @@ -128,6 +129,8 @@ public class EdgeEffect { private long mStartTime; private float mDuration; + private float mStretchIntensity = DEFAULT_MAX_STRETCH_INTENSITY; + private float mStretchDistance = -1f; private final Interpolator mInterpolator; @@ -146,6 +149,8 @@ public class EdgeEffect { private float mPullDistance; private final Rect mBounds = new Rect(); + private float mWidth; + private float mHeight; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123769450) private final Paint mPaint = new Paint(); private float mRadius; @@ -202,6 +207,19 @@ public class EdgeEffect { mBaseGlowScale = h > 0 ? Math.min(oh / h, 1.f) : 1.f; mBounds.set(mBounds.left, mBounds.top, width, (int) Math.min(height, h)); + + mWidth = width; + mHeight = height; + } + + /** + * Configure the distance in pixels to stretch the content. This is only consumed as part + * if {@link #setType(int)} is set to {@link #TYPE_STRETCH} + * @param stretchDistance Stretch distance in pixels when the target View is overscrolled + * @hide + */ + public void setStretchDistance(float stretchDistance) { + mStretchDistance = stretchDistance; } /** @@ -437,6 +455,13 @@ public class EdgeEffect { } /** + * @hide + */ + public void setMaxStretchIntensity(float stretchIntensity) { + mStretchIntensity = stretchIntensity; + } + + /** * Set or clear the blend mode. A blend mode defines how source pixels * (generated by a drawing command) are composited with the destination pixels * (content of the render target). @@ -520,23 +545,55 @@ public class EdgeEffect { RecordingCanvas recordingCanvas = (RecordingCanvas) canvas; if (mTmpMatrix == null) { mTmpMatrix = new Matrix(); - mTmpPoints = new float[4]; + mTmpPoints = new float[12]; } //noinspection deprecation recordingCanvas.getMatrix(mTmpMatrix); - mTmpPoints[0] = mBounds.width() * mDisplacement; - mTmpPoints[1] = mDistance * mBounds.height(); - mTmpPoints[2] = mTmpPoints[0]; - mTmpPoints[3] = 0; + + mTmpPoints[0] = 0; + mTmpPoints[1] = 0; // top-left + mTmpPoints[2] = mWidth; + mTmpPoints[3] = 0; // top-right + mTmpPoints[4] = mWidth; + mTmpPoints[5] = mHeight; // bottom-right + mTmpPoints[6] = 0; + mTmpPoints[7] = mHeight; // bottom-left + mTmpPoints[8] = mWidth * mDisplacement; + mTmpPoints[9] = 0; // drag start point + mTmpPoints[10] = mWidth * mDisplacement; + mTmpPoints[11] = mHeight * mDistance; // drag point mTmpMatrix.mapPoints(mTmpPoints); - float x = mTmpPoints[0] - mTmpPoints[2]; - float y = mTmpPoints[1] - mTmpPoints[3]; RenderNode renderNode = recordingCanvas.mNode; - // TODO: use stretchy RenderEffect and use internal API when it is ready - // TODO: wrap existing RenderEffect - renderNode.setRenderEffect(RenderEffect.createOffsetEffect(x, y)); + float left = renderNode.getLeft() + + min(mTmpPoints[0], mTmpPoints[2], mTmpPoints[4], mTmpPoints[6]); + float top = renderNode.getTop() + + min(mTmpPoints[1], mTmpPoints[3], mTmpPoints[5], mTmpPoints[7]); + float right = renderNode.getLeft() + + max(mTmpPoints[0], mTmpPoints[2], mTmpPoints[4], mTmpPoints[6]); + float bottom = renderNode.getTop() + + max(mTmpPoints[1], mTmpPoints[3], mTmpPoints[5], mTmpPoints[7]); + // assume rotations of increments of 90 degrees + float x = mTmpPoints[10] - mTmpPoints[8]; + float width = right - left; + float vecX = Math.max(-1f, Math.min(1f, x / width)); + float y = mTmpPoints[11] - mTmpPoints[9]; + float height = bottom - top; + float vecY = Math.max(-1f, Math.min(1f, y / height)); + renderNode.stretch( + left, + top, + right, + bottom, + vecX * mStretchIntensity, + vecY * mStretchIntensity, + // TODO (njawad/mount) figure out proper stretch distance from UX + // for now leverage placeholder logic if no stretch distance is provided to + // consume the displacement ratio times the minimum of the width or height + mStretchDistance > 0 ? mStretchDistance : + (mDisplacement * Math.min(mWidth, mHeight)) + ); } boolean oneLastFrame = false; @@ -548,6 +605,18 @@ public class EdgeEffect { return mState != STATE_IDLE || oneLastFrame; } + private float min(float f1, float f2, float f3, float f4) { + float min = Math.min(f1, f2); + min = Math.min(min, f3); + return Math.min(min, f4); + } + + private float max(float f1, float f2, float f3, float f4) { + float max = Math.max(f1, f2); + max = Math.max(max, f3); + return Math.max(max, f4); + } + /** * Return the maximum height that the edge effect will be drawn at given the original * {@link #setSize(int, int) input size}. diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java index 23915e06335a..bf552e2a501c 100644 --- a/core/java/android/widget/HorizontalScrollView.java +++ b/core/java/android/widget/HorizontalScrollView.java @@ -249,6 +249,26 @@ public class HorizontalScrollView extends FrameLayout { } /** + * API used for prototyping stretch effect parameters in framework sample apps + * @hide + */ + public void setEdgeEffectIntensity(float intensity) { + mEdgeGlowLeft.setMaxStretchIntensity(intensity); + mEdgeGlowRight.setMaxStretchIntensity(intensity); + invalidate(); + } + + /** + * API used for prototyping stretch effect parameters in the framework sample apps + * @hide + */ + public void setStretchDistance(float distance) { + mEdgeGlowLeft.setStretchDistance(distance); + mEdgeGlowRight.setStretchDistance(distance); + invalidate(); + } + + /** * Sets the right edge effect color. * * @param color The color for the right edge effect. diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java index 65f3da79afe0..30067296f967 100644 --- a/core/java/android/widget/ScrollView.java +++ b/core/java/android/widget/ScrollView.java @@ -281,6 +281,26 @@ public class ScrollView extends FrameLayout { } /** + * API used for prototyping stretch effect parameters in framework sample apps + * @hide + */ + public void setEdgeEffectIntensity(float intensity) { + mEdgeGlowTop.setMaxStretchIntensity(intensity); + mEdgeGlowBottom.setMaxStretchIntensity(intensity); + invalidate(); + } + + /** + * API used for prototyping stretch effect parameters in the framework sample apps + * @hide + */ + public void setStretchDistance(float distance) { + mEdgeGlowTop.setStretchDistance(distance); + mEdgeGlowBottom.setStretchDistance(distance); + invalidate(); + } + + /** * Sets the bottom edge effect color. * * @param color The color for the bottom edge effect. diff --git a/graphics/java/android/graphics/RenderNode.java b/graphics/java/android/graphics/RenderNode.java index f6f770be3de8..da5162b9e15e 100644 --- a/graphics/java/android/graphics/RenderNode.java +++ b/graphics/java/android/graphics/RenderNode.java @@ -719,11 +719,11 @@ public final class RenderNode { /** @hide */ public boolean stretch(float left, float top, float right, float bottom, float vecX, float vecY, float maxStretchAmount) { - if (1.0 < vecX || vecX < -1.0) { - throw new IllegalArgumentException("vecX must be in the range [-1, 1], was " + vecX); + if (Float.isInfinite(vecX) || Float.isNaN(vecX)) { + throw new IllegalArgumentException("vecX must be a finite, non-NaN value " + vecX); } - if (1.0 < vecY || vecY < -1.0) { - throw new IllegalArgumentException("vecY must be in the range [-1, 1], was " + vecY); + if (Float.isInfinite(vecY) || Float.isNaN(vecY)) { + throw new IllegalArgumentException("vecY must be a finite, non-NaN value " + vecY); } if (top >= bottom || left >= right) { throw new IllegalArgumentException( @@ -734,7 +734,16 @@ public final class RenderNode { throw new IllegalArgumentException( "The max stretch amount must be >0, got " + maxStretchAmount); } - return nStretch(mNativeRenderNode, left, top, right, bottom, vecX, vecY, maxStretchAmount); + return nStretch( + mNativeRenderNode, + left, + top, + right, + bottom, + vecX, + vecY, + maxStretchAmount + ); } /** diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h index 609706e2e49d..5540e2da78d9 100644 --- a/libs/hwui/RenderProperties.h +++ b/libs/hwui/RenderProperties.h @@ -552,8 +552,8 @@ public: bool promotedToLayer() const { return mLayerProperties.mType == LayerType::None && fitsOnLayer() && - (mComputedFields.mNeedLayerForFunctors || - mLayerProperties.mImageFilter != nullptr || + (mComputedFields.mNeedLayerForFunctors || mLayerProperties.mImageFilter != nullptr || + !mLayerProperties.getStretchEffect().isEmpty() || (!MathUtils::isZero(mPrimitiveFields.mAlpha) && mPrimitiveFields.mAlpha < 1 && mPrimitiveFields.mHasOverlappingRendering)); } diff --git a/libs/hwui/effects/StretchEffect.cpp b/libs/hwui/effects/StretchEffect.cpp index 51cbc7592861..d4fd1053b17f 100644 --- a/libs/hwui/effects/StretchEffect.cpp +++ b/libs/hwui/effects/StretchEffect.cpp @@ -15,13 +15,195 @@ */ #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 { -sk_sp<SkImageFilter> StretchEffect::getImageFilter() const { - // TODO: Implement & Cache - // Probably need to use mutable to achieve caching - return nullptr; +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 uStretchAffectedDist; + + // Distance stretched as a function of the normalized overscroll times + // scale intensity + uniform float uDistanceStretchedX; + uniform float uDistanceStretchedY; + 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 + + void computeOverscrollStart( + out float outPos, + float inPos, + float overscroll, + float uStretchAffectedDist, + float distanceStretched + ) { + float offsetPos = uStretchAffectedDist - inPos; + float posBasedVariation = smoothstep(0., uStretchAffectedDist, offsetPos); + float stretchIntensity = overscroll * posBasedVariation; + outPos = distanceStretched - (offsetPos / (1. + stretchIntensity)); + } + + void computeOverscrollEnd( + out float outPos, + float inPos, + float overscroll, + float reverseStretchDist, + float uStretchAffectedDist, + float distanceStretched + ) { + float offsetPos = inPos - reverseStretchDist; + float posBasedVariation = (smoothstep(0., uStretchAffectedDist, offsetPos)); + float stretchIntensity = (-overscroll) * posBasedVariation; + outPos = 1 - (distanceStretched - (offsetPos / (1. + stretchIntensity))); + } + + void computeOverscroll( + out float outPos, + float inPos, + float overscroll, + float uStretchAffectedDist, + float distanceStretched, + float distanceDiff + ) { + if (overscroll > 0) { + if (inPos <= uStretchAffectedDist) { + computeOverscrollStart( + outPos, + inPos, + overscroll, + uStretchAffectedDist, + distanceStretched + ); + } else if (inPos >= distanceStretched) { + outPos = distanceDiff + inPos; + } + } + if (overscroll < 0) { + float stretchAffectedDist = 1. - uStretchAffectedDist; + if (inPos >= stretchAffectedDist) { + computeOverscrollEnd( + outPos, + inPos, + overscroll, + stretchAffectedDist, + uStretchAffectedDist, + distanceStretched + ); + } else if (inPos < stretchAffectedDist) { + outPos = -distanceDiff + inPos; + } + } + } + + vec4 main(vec2 coord) { + // Normalize SKSL pixel coordinate into a unit vector + float inU = coord.x / viewportWidth; + float inV = coord.y / viewportHeight; + float outU; + float outV; + float stretchIntensity; + // Add the normalized scroll position within scrolling list + inU += uScrollX; + inV += uScrollY; + outU = inU; + outV = inV; + computeOverscroll( + outU, + inU, + uOverscrollX, + uStretchAffectedDist, + uDistanceStretchedX, + uDistDiffX + ); + computeOverscroll( + outV, + inV, + uOverscrollY, + uStretchAffectedDist, + uDistanceStretchedY, + uDistDiffY + ); + coord.x = outU * viewportWidth; + coord.y = outV * viewportHeight; + return sample(uContentTexture, coord); + })"); + +static const float ZERO = 0.f; + +sk_sp<SkImageFilter> StretchEffect::getImageFilter(const sk_sp<SkImage>& snapshotImage) const { + if (isEmpty()) { + return nullptr; + } + + if (mStretchFilter != nullptr) { + return mStretchFilter; + } + + float distanceNotStretchedX = maxStretchAmount / stretchArea.width(); + float distanceNotStretchedY = maxStretchAmount / stretchArea.height(); + float normOverScrollDistX = mStretchDirection.x(); + float normOverScrollDistY = mStretchDirection.y(); + float distanceStretchedX = maxStretchAmount / (1 + abs(normOverScrollDistX)); + float distanceStretchedY = maxStretchAmount / (1 + abs(normOverScrollDistY)); + float diffX = distanceStretchedX - distanceNotStretchedX; + float diffY = distanceStretchedY - distanceNotStretchedY; + float viewportWidth = stretchArea.width(); + float viewportHeight = stretchArea.height(); + + if (mBuilder == nullptr) { + mBuilder = std::make_unique<SkRuntimeShaderBuilder>(getStretchEffect()); + } + + mBuilder->child("uContentTexture") = snapshotImage->makeShader( + SkTileMode::kClamp, SkTileMode::kClamp, SkSamplingOptions(SkFilterMode::kLinear)); + mBuilder->uniform("uStretchAffectedDist").set(&maxStretchAmount, 1); + mBuilder->uniform("uDistanceStretchedX").set(&distanceStretchedX, 1); + mBuilder->uniform("uDistanceStretchedY").set(&distanceStretchedY, 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(&viewportWidth, 1); + mBuilder->uniform("viewportHeight").set(&viewportHeight, 1); + + mStretchFilter = SkImageFilters::Shader(mBuilder->makeShader(nullptr, false), + SkRect{0, 0, viewportWidth, viewportHeight}); + + return mStretchFilter; +} + +sk_sp<SkRuntimeEffect> StretchEffect::getStretchEffect() { + const static SkRuntimeEffect::Result instance = SkRuntimeEffect::Make(stretchShader); + return instance.effect; } } // namespace android::uirenderer
\ No newline at end of file diff --git a/libs/hwui/effects/StretchEffect.h b/libs/hwui/effects/StretchEffect.h index 7dfd6398765a..d2da06b31f68 100644 --- a/libs/hwui/effects/StretchEffect.h +++ b/libs/hwui/effects/StretchEffect.h @@ -18,9 +18,11 @@ #include "utils/MathUtils.h" +#include <SkImage.h> +#include <SkImageFilter.h> #include <SkPoint.h> #include <SkRect.h> -#include <SkImageFilter.h> +#include <SkRuntimeEffect.h> namespace android::uirenderer { @@ -31,15 +33,27 @@ public: SmoothStep, }; + StretchEffect(const SkRect& area, const SkVector& direction, float maxStretchAmount) + : stretchArea(area), maxStretchAmount(maxStretchAmount), mStretchDirection(direction) {} + + StretchEffect() {} + bool isEmpty() const { - return MathUtils::isZero(stretchDirection.x()) - && MathUtils::isZero(stretchDirection.y()); + return MathUtils::isZero(mStretchDirection.x()) && MathUtils::isZero(mStretchDirection.y()); } void setEmpty() { *this = StretchEffect{}; } + StretchEffect& operator=(const StretchEffect& other) { + this->stretchArea = other.stretchArea; + this->mStretchDirection = other.mStretchDirection; + this->mStretchFilter = nullptr; + this->maxStretchAmount = other.maxStretchAmount; + return *this; + } + void mergeWith(const StretchEffect& other) { if (other.isEmpty()) { return; @@ -48,7 +62,7 @@ public: *this = other; return; } - stretchDirection += other.stretchDirection; + setStretchDirection(mStretchDirection + other.mStretchDirection); if (isEmpty()) { return setEmpty(); } @@ -56,11 +70,23 @@ public: maxStretchAmount = std::max(maxStretchAmount, other.maxStretchAmount); } - sk_sp<SkImageFilter> getImageFilter() const; + sk_sp<SkImageFilter> getImageFilter(const sk_sp<SkImage>& snapshotImage) const; SkRect stretchArea {0, 0, 0, 0}; - SkVector stretchDirection {0, 0}; float maxStretchAmount = 0; + + void setStretchDirection(const SkVector& direction) { + mStretchFilter = nullptr; + mStretchDirection = direction; + } + + const SkVector getStretchDirection() const { return mStretchDirection; } + +private: + static sk_sp<SkRuntimeEffect> getStretchEffect(); + mutable SkVector mStretchDirection{0, 0}; + mutable std::unique_ptr<SkRuntimeShaderBuilder> mBuilder; + mutable sk_sp<SkImageFilter> mStretchFilter; }; } // namespace android::uirenderer diff --git a/libs/hwui/jni/android_graphics_RenderNode.cpp b/libs/hwui/jni/android_graphics_RenderNode.cpp index 5f60437b5cc7..fc7d0d181949 100644 --- a/libs/hwui/jni/android_graphics_RenderNode.cpp +++ b/libs/hwui/jni/android_graphics_RenderNode.cpp @@ -180,14 +180,13 @@ static jboolean android_view_RenderNode_clearStretch(CRITICAL_JNI_PARAMS_COMMA j } static jboolean android_view_RenderNode_stretch(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, - jfloat left, jfloat top, jfloat right, jfloat bottom, jfloat vX, jfloat vY, jfloat max) { + jfloat left, jfloat top, jfloat right, + jfloat bottom, jfloat vX, jfloat vY, jfloat max) { + StretchEffect effect = + StretchEffect(SkRect::MakeLTRB(left, top, right, bottom), {.fX = vX, .fY = vY}, max); RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); renderNode->mutateStagingProperties().mutateLayerProperties().mutableStretchEffect().mergeWith( - StretchEffect{ - .stretchArea = SkRect::MakeLTRB(left, top, right, bottom), - .stretchDirection = {.fX = vX, .fY = vY}, - .maxStretchAmount = max - }); + effect); renderNode->setPropertyFieldsDirty(RenderNode::GENERIC); return true; } @@ -659,10 +658,11 @@ static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject, return; } #ifdef __ANDROID__ // Layoutlib does not support CanvasContext + SkVector stretchDirection = effect->getStretchDirection(); env->CallVoidMethod(localref, gPositionListener_ApplyStretchMethod, info.canvasContext.getFrameNumber(), area.left, area.top, - area.right, area.bottom, effect->stretchDirection.fX, - effect->stretchDirection.fY, effect->maxStretchAmount); + area.right, area.bottom, stretchDirection.fX, stretchDirection.fY, + effect->maxStretchAmount); #endif env->DeleteLocalRef(localref); } @@ -702,106 +702,110 @@ static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject, const char* const kClassPathName = "android/graphics/RenderNode"; static const JNINativeMethod gMethods[] = { -// ---------------------------------------------------------------------------- -// Regular JNI -// ---------------------------------------------------------------------------- - { "nCreate", "(Ljava/lang/String;)J", (void*) android_view_RenderNode_create }, - { "nGetNativeFinalizer", "()J", (void*) android_view_RenderNode_getNativeFinalizer }, - { "nOutput", "(J)V", (void*) android_view_RenderNode_output }, - { "nGetUsageSize", "(J)I", (void*) android_view_RenderNode_getUsageSize }, - { "nGetAllocatedSize", "(J)I", (void*) android_view_RenderNode_getAllocatedSize }, - { "nAddAnimator", "(JJ)V", (void*) android_view_RenderNode_addAnimator }, - { "nEndAllAnimators", "(J)V", (void*) android_view_RenderNode_endAllAnimators }, - { "nRequestPositionUpdates", "(JLandroid/graphics/RenderNode$PositionUpdateListener;)V", (void*) android_view_RenderNode_requestPositionUpdates }, - -// ---------------------------------------------------------------------------- -// Critical JNI via @CriticalNative annotation in RenderNode.java -// ---------------------------------------------------------------------------- - { "nDiscardDisplayList", "(J)V", (void*) android_view_RenderNode_discardDisplayList }, - { "nIsValid", "(J)Z", (void*) android_view_RenderNode_isValid }, - { "nSetLayerType", "(JI)Z", (void*) android_view_RenderNode_setLayerType }, - { "nGetLayerType", "(J)I", (void*) android_view_RenderNode_getLayerType }, - { "nSetLayerPaint", "(JJ)Z", (void*) android_view_RenderNode_setLayerPaint }, - { "nSetStaticMatrix", "(JJ)Z", (void*) android_view_RenderNode_setStaticMatrix }, - { "nSetAnimationMatrix", "(JJ)Z", (void*) android_view_RenderNode_setAnimationMatrix }, - { "nGetAnimationMatrix", "(JJ)Z", (void*) android_view_RenderNode_getAnimationMatrix }, - { "nSetClipToBounds", "(JZ)Z", (void*) android_view_RenderNode_setClipToBounds }, - { "nGetClipToBounds", "(J)Z", (void*) android_view_RenderNode_getClipToBounds }, - { "nSetClipBounds", "(JIIII)Z", (void*) android_view_RenderNode_setClipBounds }, - { "nSetClipBoundsEmpty", "(J)Z", (void*) android_view_RenderNode_setClipBoundsEmpty }, - { "nSetProjectBackwards", "(JZ)Z", (void*) android_view_RenderNode_setProjectBackwards }, - { "nSetProjectionReceiver","(JZ)Z", (void*) android_view_RenderNode_setProjectionReceiver }, - - { "nSetOutlineRoundRect", "(JIIIIFF)Z", (void*) android_view_RenderNode_setOutlineRoundRect }, - { "nSetOutlinePath", "(JJF)Z", (void*) android_view_RenderNode_setOutlinePath }, - { "nSetOutlineEmpty", "(J)Z", (void*) android_view_RenderNode_setOutlineEmpty }, - { "nSetOutlineNone", "(J)Z", (void*) android_view_RenderNode_setOutlineNone }, - { "nClearStretch", "(J)Z", (void*) android_view_RenderNode_clearStretch }, - { "nStretch", "(JFFFFFFF)Z", (void*) android_view_RenderNode_stretch }, - { "nHasShadow", "(J)Z", (void*) android_view_RenderNode_hasShadow }, - { "nSetSpotShadowColor", "(JI)Z", (void*) android_view_RenderNode_setSpotShadowColor }, - { "nGetSpotShadowColor", "(J)I", (void*) android_view_RenderNode_getSpotShadowColor }, - { "nSetAmbientShadowColor","(JI)Z", (void*) android_view_RenderNode_setAmbientShadowColor }, - { "nGetAmbientShadowColor","(J)I", (void*) android_view_RenderNode_getAmbientShadowColor }, - { "nSetClipToOutline", "(JZ)Z", (void*) android_view_RenderNode_setClipToOutline }, - { "nSetRevealClip", "(JZFFF)Z", (void*) android_view_RenderNode_setRevealClip }, - - { "nSetAlpha", "(JF)Z", (void*) android_view_RenderNode_setAlpha }, - { "nSetRenderEffect", "(JJ)Z", (void*) android_view_RenderNode_setRenderEffect }, - { "nSetHasOverlappingRendering", "(JZ)Z", - (void*) android_view_RenderNode_setHasOverlappingRendering }, - { "nSetUsageHint", "(JI)V", (void*) android_view_RenderNode_setUsageHint }, - { "nSetElevation", "(JF)Z", (void*) android_view_RenderNode_setElevation }, - { "nSetTranslationX", "(JF)Z", (void*) android_view_RenderNode_setTranslationX }, - { "nSetTranslationY", "(JF)Z", (void*) android_view_RenderNode_setTranslationY }, - { "nSetTranslationZ", "(JF)Z", (void*) android_view_RenderNode_setTranslationZ }, - { "nSetRotation", "(JF)Z", (void*) android_view_RenderNode_setRotation }, - { "nSetRotationX", "(JF)Z", (void*) android_view_RenderNode_setRotationX }, - { "nSetRotationY", "(JF)Z", (void*) android_view_RenderNode_setRotationY }, - { "nSetScaleX", "(JF)Z", (void*) android_view_RenderNode_setScaleX }, - { "nSetScaleY", "(JF)Z", (void*) android_view_RenderNode_setScaleY }, - { "nSetPivotX", "(JF)Z", (void*) android_view_RenderNode_setPivotX }, - { "nSetPivotY", "(JF)Z", (void*) android_view_RenderNode_setPivotY }, - { "nResetPivot", "(J)Z", (void*) android_view_RenderNode_resetPivot }, - { "nSetCameraDistance", "(JF)Z", (void*) android_view_RenderNode_setCameraDistance }, - { "nSetLeft", "(JI)Z", (void*) android_view_RenderNode_setLeft }, - { "nSetTop", "(JI)Z", (void*) android_view_RenderNode_setTop }, - { "nSetRight", "(JI)Z", (void*) android_view_RenderNode_setRight }, - { "nSetBottom", "(JI)Z", (void*) android_view_RenderNode_setBottom }, - { "nGetLeft", "(J)I", (void*) android_view_RenderNode_getLeft }, - { "nGetTop", "(J)I", (void*) android_view_RenderNode_getTop }, - { "nGetRight", "(J)I", (void*) android_view_RenderNode_getRight }, - { "nGetBottom", "(J)I", (void*) android_view_RenderNode_getBottom }, - { "nSetLeftTopRightBottom","(JIIII)Z", (void*) android_view_RenderNode_setLeftTopRightBottom }, - { "nOffsetLeftAndRight", "(JI)Z", (void*) android_view_RenderNode_offsetLeftAndRight }, - { "nOffsetTopAndBottom", "(JI)Z", (void*) android_view_RenderNode_offsetTopAndBottom }, - - { "nHasOverlappingRendering", "(J)Z", (void*) android_view_RenderNode_hasOverlappingRendering }, - { "nGetClipToOutline", "(J)Z", (void*) android_view_RenderNode_getClipToOutline }, - { "nGetAlpha", "(J)F", (void*) android_view_RenderNode_getAlpha }, - { "nGetCameraDistance", "(J)F", (void*) android_view_RenderNode_getCameraDistance }, - { "nGetScaleX", "(J)F", (void*) android_view_RenderNode_getScaleX }, - { "nGetScaleY", "(J)F", (void*) android_view_RenderNode_getScaleY }, - { "nGetElevation", "(J)F", (void*) android_view_RenderNode_getElevation }, - { "nGetTranslationX", "(J)F", (void*) android_view_RenderNode_getTranslationX }, - { "nGetTranslationY", "(J)F", (void*) android_view_RenderNode_getTranslationY }, - { "nGetTranslationZ", "(J)F", (void*) android_view_RenderNode_getTranslationZ }, - { "nGetRotation", "(J)F", (void*) android_view_RenderNode_getRotation }, - { "nGetRotationX", "(J)F", (void*) android_view_RenderNode_getRotationX }, - { "nGetRotationY", "(J)F", (void*) android_view_RenderNode_getRotationY }, - { "nIsPivotExplicitlySet", "(J)Z", (void*) android_view_RenderNode_isPivotExplicitlySet }, - { "nHasIdentityMatrix", "(J)Z", (void*) android_view_RenderNode_hasIdentityMatrix }, - - { "nGetTransformMatrix", "(JJ)V", (void*) android_view_RenderNode_getTransformMatrix }, - { "nGetInverseTransformMatrix","(JJ)V", (void*) android_view_RenderNode_getInverseTransformMatrix }, - - { "nGetPivotX", "(J)F", (void*) android_view_RenderNode_getPivotX }, - { "nGetPivotY", "(J)F", (void*) android_view_RenderNode_getPivotY }, - { "nGetWidth", "(J)I", (void*) android_view_RenderNode_getWidth }, - { "nGetHeight", "(J)I", (void*) android_view_RenderNode_getHeight }, - { "nSetAllowForceDark", "(JZ)Z", (void*) android_view_RenderNode_setAllowForceDark }, - { "nGetAllowForceDark", "(J)Z", (void*) android_view_RenderNode_getAllowForceDark }, - { "nGetUniqueId", "(J)J", (void*) android_view_RenderNode_getUniqueId }, + // ---------------------------------------------------------------------------- + // Regular JNI + // ---------------------------------------------------------------------------- + {"nCreate", "(Ljava/lang/String;)J", (void*)android_view_RenderNode_create}, + {"nGetNativeFinalizer", "()J", (void*)android_view_RenderNode_getNativeFinalizer}, + {"nOutput", "(J)V", (void*)android_view_RenderNode_output}, + {"nGetUsageSize", "(J)I", (void*)android_view_RenderNode_getUsageSize}, + {"nGetAllocatedSize", "(J)I", (void*)android_view_RenderNode_getAllocatedSize}, + {"nAddAnimator", "(JJ)V", (void*)android_view_RenderNode_addAnimator}, + {"nEndAllAnimators", "(J)V", (void*)android_view_RenderNode_endAllAnimators}, + {"nRequestPositionUpdates", "(JLandroid/graphics/RenderNode$PositionUpdateListener;)V", + (void*)android_view_RenderNode_requestPositionUpdates}, + + // ---------------------------------------------------------------------------- + // Critical JNI via @CriticalNative annotation in RenderNode.java + // ---------------------------------------------------------------------------- + {"nDiscardDisplayList", "(J)V", (void*)android_view_RenderNode_discardDisplayList}, + {"nIsValid", "(J)Z", (void*)android_view_RenderNode_isValid}, + {"nSetLayerType", "(JI)Z", (void*)android_view_RenderNode_setLayerType}, + {"nGetLayerType", "(J)I", (void*)android_view_RenderNode_getLayerType}, + {"nSetLayerPaint", "(JJ)Z", (void*)android_view_RenderNode_setLayerPaint}, + {"nSetStaticMatrix", "(JJ)Z", (void*)android_view_RenderNode_setStaticMatrix}, + {"nSetAnimationMatrix", "(JJ)Z", (void*)android_view_RenderNode_setAnimationMatrix}, + {"nGetAnimationMatrix", "(JJ)Z", (void*)android_view_RenderNode_getAnimationMatrix}, + {"nSetClipToBounds", "(JZ)Z", (void*)android_view_RenderNode_setClipToBounds}, + {"nGetClipToBounds", "(J)Z", (void*)android_view_RenderNode_getClipToBounds}, + {"nSetClipBounds", "(JIIII)Z", (void*)android_view_RenderNode_setClipBounds}, + {"nSetClipBoundsEmpty", "(J)Z", (void*)android_view_RenderNode_setClipBoundsEmpty}, + {"nSetProjectBackwards", "(JZ)Z", (void*)android_view_RenderNode_setProjectBackwards}, + {"nSetProjectionReceiver", "(JZ)Z", (void*)android_view_RenderNode_setProjectionReceiver}, + + {"nSetOutlineRoundRect", "(JIIIIFF)Z", (void*)android_view_RenderNode_setOutlineRoundRect}, + {"nSetOutlinePath", "(JJF)Z", (void*)android_view_RenderNode_setOutlinePath}, + {"nSetOutlineEmpty", "(J)Z", (void*)android_view_RenderNode_setOutlineEmpty}, + {"nSetOutlineNone", "(J)Z", (void*)android_view_RenderNode_setOutlineNone}, + {"nClearStretch", "(J)Z", (void*)android_view_RenderNode_clearStretch}, + {"nStretch", "(JFFFFFFF)Z", (void*)android_view_RenderNode_stretch}, + {"nHasShadow", "(J)Z", (void*)android_view_RenderNode_hasShadow}, + {"nSetSpotShadowColor", "(JI)Z", (void*)android_view_RenderNode_setSpotShadowColor}, + {"nGetSpotShadowColor", "(J)I", (void*)android_view_RenderNode_getSpotShadowColor}, + {"nSetAmbientShadowColor", "(JI)Z", (void*)android_view_RenderNode_setAmbientShadowColor}, + {"nGetAmbientShadowColor", "(J)I", (void*)android_view_RenderNode_getAmbientShadowColor}, + {"nSetClipToOutline", "(JZ)Z", (void*)android_view_RenderNode_setClipToOutline}, + {"nSetRevealClip", "(JZFFF)Z", (void*)android_view_RenderNode_setRevealClip}, + + {"nSetAlpha", "(JF)Z", (void*)android_view_RenderNode_setAlpha}, + {"nSetRenderEffect", "(JJ)Z", (void*)android_view_RenderNode_setRenderEffect}, + {"nSetHasOverlappingRendering", "(JZ)Z", + (void*)android_view_RenderNode_setHasOverlappingRendering}, + {"nSetUsageHint", "(JI)V", (void*)android_view_RenderNode_setUsageHint}, + {"nSetElevation", "(JF)Z", (void*)android_view_RenderNode_setElevation}, + {"nSetTranslationX", "(JF)Z", (void*)android_view_RenderNode_setTranslationX}, + {"nSetTranslationY", "(JF)Z", (void*)android_view_RenderNode_setTranslationY}, + {"nSetTranslationZ", "(JF)Z", (void*)android_view_RenderNode_setTranslationZ}, + {"nSetRotation", "(JF)Z", (void*)android_view_RenderNode_setRotation}, + {"nSetRotationX", "(JF)Z", (void*)android_view_RenderNode_setRotationX}, + {"nSetRotationY", "(JF)Z", (void*)android_view_RenderNode_setRotationY}, + {"nSetScaleX", "(JF)Z", (void*)android_view_RenderNode_setScaleX}, + {"nSetScaleY", "(JF)Z", (void*)android_view_RenderNode_setScaleY}, + {"nSetPivotX", "(JF)Z", (void*)android_view_RenderNode_setPivotX}, + {"nSetPivotY", "(JF)Z", (void*)android_view_RenderNode_setPivotY}, + {"nResetPivot", "(J)Z", (void*)android_view_RenderNode_resetPivot}, + {"nSetCameraDistance", "(JF)Z", (void*)android_view_RenderNode_setCameraDistance}, + {"nSetLeft", "(JI)Z", (void*)android_view_RenderNode_setLeft}, + {"nSetTop", "(JI)Z", (void*)android_view_RenderNode_setTop}, + {"nSetRight", "(JI)Z", (void*)android_view_RenderNode_setRight}, + {"nSetBottom", "(JI)Z", (void*)android_view_RenderNode_setBottom}, + {"nGetLeft", "(J)I", (void*)android_view_RenderNode_getLeft}, + {"nGetTop", "(J)I", (void*)android_view_RenderNode_getTop}, + {"nGetRight", "(J)I", (void*)android_view_RenderNode_getRight}, + {"nGetBottom", "(J)I", (void*)android_view_RenderNode_getBottom}, + {"nSetLeftTopRightBottom", "(JIIII)Z", + (void*)android_view_RenderNode_setLeftTopRightBottom}, + {"nOffsetLeftAndRight", "(JI)Z", (void*)android_view_RenderNode_offsetLeftAndRight}, + {"nOffsetTopAndBottom", "(JI)Z", (void*)android_view_RenderNode_offsetTopAndBottom}, + + {"nHasOverlappingRendering", "(J)Z", + (void*)android_view_RenderNode_hasOverlappingRendering}, + {"nGetClipToOutline", "(J)Z", (void*)android_view_RenderNode_getClipToOutline}, + {"nGetAlpha", "(J)F", (void*)android_view_RenderNode_getAlpha}, + {"nGetCameraDistance", "(J)F", (void*)android_view_RenderNode_getCameraDistance}, + {"nGetScaleX", "(J)F", (void*)android_view_RenderNode_getScaleX}, + {"nGetScaleY", "(J)F", (void*)android_view_RenderNode_getScaleY}, + {"nGetElevation", "(J)F", (void*)android_view_RenderNode_getElevation}, + {"nGetTranslationX", "(J)F", (void*)android_view_RenderNode_getTranslationX}, + {"nGetTranslationY", "(J)F", (void*)android_view_RenderNode_getTranslationY}, + {"nGetTranslationZ", "(J)F", (void*)android_view_RenderNode_getTranslationZ}, + {"nGetRotation", "(J)F", (void*)android_view_RenderNode_getRotation}, + {"nGetRotationX", "(J)F", (void*)android_view_RenderNode_getRotationX}, + {"nGetRotationY", "(J)F", (void*)android_view_RenderNode_getRotationY}, + {"nIsPivotExplicitlySet", "(J)Z", (void*)android_view_RenderNode_isPivotExplicitlySet}, + {"nHasIdentityMatrix", "(J)Z", (void*)android_view_RenderNode_hasIdentityMatrix}, + + {"nGetTransformMatrix", "(JJ)V", (void*)android_view_RenderNode_getTransformMatrix}, + {"nGetInverseTransformMatrix", "(JJ)V", + (void*)android_view_RenderNode_getInverseTransformMatrix}, + + {"nGetPivotX", "(J)F", (void*)android_view_RenderNode_getPivotX}, + {"nGetPivotY", "(J)F", (void*)android_view_RenderNode_getPivotY}, + {"nGetWidth", "(J)I", (void*)android_view_RenderNode_getWidth}, + {"nGetHeight", "(J)I", (void*)android_view_RenderNode_getHeight}, + {"nSetAllowForceDark", "(JZ)Z", (void*)android_view_RenderNode_setAllowForceDark}, + {"nGetAllowForceDark", "(J)Z", (void*)android_view_RenderNode_getAllowForceDark}, + {"nGetUniqueId", "(J)J", (void*)android_view_RenderNode_getUniqueId}, }; int register_android_view_RenderNode(JNIEnv* env) { diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp index c01021221f37..cb0ff8d871d4 100644 --- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp +++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp @@ -169,8 +169,8 @@ void RenderNodeDrawable::forceDraw(SkCanvas* canvas) const { displayList->mProjectedOutline = nullptr; } -static bool layerNeedsPaint(const LayerProperties& properties, float alphaMultiplier, - SkPaint* paint) { +static bool layerNeedsPaint(const sk_sp<SkImage>& snapshotImage, const LayerProperties& properties, + float alphaMultiplier, SkPaint* paint) { if (alphaMultiplier < 1.0f || properties.alpha() < 255 || properties.xferMode() != SkBlendMode::kSrcOver || properties.getColorFilter() != nullptr || properties.getImageFilter() != nullptr || !properties.getStretchEffect().isEmpty()) { @@ -179,7 +179,8 @@ static bool layerNeedsPaint(const LayerProperties& properties, float alphaMultip paint->setColorFilter(sk_ref_sp(properties.getColorFilter())); sk_sp<SkImageFilter> imageFilter = sk_ref_sp(properties.getImageFilter()); - sk_sp<SkImageFilter> stretchFilter = properties.getStretchEffect().getImageFilter(); + sk_sp<SkImageFilter> stretchFilter = + properties.getStretchEffect().getImageFilter(snapshotImage); sk_sp<SkImageFilter> filter; if (imageFilter && stretchFilter) { filter = SkImageFilters::Compose( @@ -240,7 +241,8 @@ void RenderNodeDrawable::drawContent(SkCanvas* canvas) const { if (renderNode->getLayerSurface() && mComposeLayer) { SkASSERT(properties.effectiveLayerType() == LayerType::RenderLayer); SkPaint paint; - layerNeedsPaint(layerProperties, alphaMultiplier, &paint); + sk_sp<SkImage> snapshotImage = renderNode->getLayerSurface()->makeImageSnapshot(); + layerNeedsPaint(snapshotImage, layerProperties, alphaMultiplier, &paint); SkSamplingOptions sampling(SkFilterMode::kLinear); // surfaces for layers are created on LAYER_SIZE boundaries (which are >= layer size) so @@ -254,8 +256,8 @@ void RenderNodeDrawable::drawContent(SkCanvas* canvas) const { canvas->drawAnnotation(bounds, String8::format( "SurfaceID|%" PRId64, renderNode->uniqueId()).c_str(), nullptr); } - canvas->drawImageRect(renderNode->getLayerSurface()->makeImageSnapshot(), bounds, - bounds, sampling, &paint, SkCanvas::kStrict_SrcRectConstraint); + canvas->drawImageRect(snapshotImage, bounds, bounds, sampling, &paint, + SkCanvas::kStrict_SrcRectConstraint); if (!renderNode->getSkiaLayer()->hasRenderedSinceRepaint) { renderNode->getSkiaLayer()->hasRenderedSinceRepaint = true; diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml index 62ccb1a0b9db..6bf44920ee26 100644 --- a/tests/HwAccelerationTest/AndroidManifest.xml +++ b/tests/HwAccelerationTest/AndroidManifest.xml @@ -771,6 +771,24 @@ </intent-filter> </activity> + <activity android:name="StretchShaderActivity" + android:label="RenderEffect/Stretch" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="com.android.test.hwui.TEST"/> + </intent-filter> + </activity> + + <activity android:name="EdgeEffectStretchActivity" + android:label="RenderEffect/EdgeEffect stretch" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="com.android.test.hwui.TEST"/> + </intent-filter> + </activity> + <activity android:name="TextActivity" android:label="Text/Simple Text" android:theme="@android:style/Theme.NoTitleBar" diff --git a/tests/HwAccelerationTest/res/layout/stretch_layout.xml b/tests/HwAccelerationTest/res/layout/stretch_layout.xml new file mode 100644 index 000000000000..df5f297da729 --- /dev/null +++ b/tests/HwAccelerationTest/res/layout/stretch_layout.xml @@ -0,0 +1,88 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2010 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. +--> +<ScrollView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/scroll_view" + android:edgeEffectType="stretch" + android:layout_width="match_parent" + android:layout_height="match_parent" > + <LinearLayout + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <HorizontalScrollView + android:id="@+id/horizontal_scroll_view" + android:edgeEffectType="stretch" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <LinearLayout + android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <ImageView + android:layout_width="match_parent" + android:layout_height="200dp" + android:src="@drawable/sunset1"/> + <ImageView + android:layout_width="match_parent" + android:layout_height="200dp" + android:src="@drawable/sunset1"/> + <ImageView + android:layout_width="match_parent" + android:layout_height="200dp" + android:src="@drawable/sunset1"/> + <ImageView + android:layout_width="match_parent" + android:layout_height="200dp" + android:src="@drawable/sunset1"/> + <ImageView + android:layout_width="match_parent" + android:layout_height="200dp" + android:src="@drawable/sunset1"/> + <ImageView + android:layout_width="match_parent" + android:layout_height="200dp" + android:src="@drawable/sunset1"/> + </LinearLayout> + </HorizontalScrollView> + <ImageView + android:layout_width="match_parent" + android:layout_height="200dp" + android:src="@drawable/sunset1"/> + <ImageView + android:layout_width="match_parent" + android:layout_height="200dp" + android:src="@drawable/sunset1"/> + <ImageView + android:layout_width="match_parent" + android:layout_height="200dp" + android:src="@drawable/sunset1"/> + <ImageView + android:layout_width="match_parent" + android:layout_height="200dp" + android:src="@drawable/sunset1"/> + <ImageView + android:layout_width="match_parent" + android:layout_height="200dp" + android:src="@drawable/sunset1"/> + <ImageView + android:layout_width="match_parent" + android:layout_height="200dp" + android:src="@drawable/sunset1"/> + + </LinearLayout> +</ScrollView> diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/EdgeEffectStretchActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/EdgeEffectStretchActivity.java new file mode 100644 index 000000000000..f0e6299f09e9 --- /dev/null +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/EdgeEffectStretchActivity.java @@ -0,0 +1,36 @@ +/* + * 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. + */ + +package com.android.test.hwui; + +import android.app.Activity; +import android.os.Bundle; +import android.widget.HorizontalScrollView; +import android.widget.ScrollView; + +public class EdgeEffectStretchActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.stretch_layout); + HorizontalScrollView hsv = findViewById(R.id.horizontal_scroll_view); + hsv.setStretchDistance(50f); + + ScrollView sv = findViewById(R.id.scroll_view); + sv.setStretchDistance(50f); + } +} diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/StretchShaderActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/StretchShaderActivity.java new file mode 100644 index 000000000000..9bd933afa07d --- /dev/null +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/StretchShaderActivity.java @@ -0,0 +1,540 @@ +/* + * 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. + */ + +package com.android.test.hwui; + +import android.annotation.Nullable; +import android.app.Activity; +import android.graphics.Bitmap; +import android.graphics.BitmapShader; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorFilter; +import android.graphics.RecordingCanvas; +import android.graphics.Rect; +import android.graphics.RenderEffect; +import android.graphics.RuntimeShader; +import android.graphics.Shader; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.SeekBar; +import android.widget.TextView; + +public class StretchShaderActivity extends Activity { + + private static final float MAX_STRETCH_INTENSITY = 1.5f; + private static final float STRETCH_AFFECTED_DISTANCE = 1.0f; + + private float mScrollX = 0f; + private float mScrollY = 0f; + + private float mMaxStretchIntensity = MAX_STRETCH_INTENSITY; + private float mStretchAffectedDistance = STRETCH_AFFECTED_DISTANCE; + + private float mOverscrollX = 25f; + private float mOverscrollY = 25f; + + private RuntimeShader mRuntimeShader; + private ImageView mImageView; + private ImageView mTestImageView; + + private Bitmap mBitmap; + + private StretchDrawable mStretchDrawable = new StretchDrawable(); + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + LinearLayout linearLayout = new LinearLayout(this); + linearLayout.setOrientation(LinearLayout.VERTICAL); + + mBitmap = ((BitmapDrawable) getDrawable(R.drawable.sunset1)).getBitmap(); + mRuntimeShader = new RuntimeShader(SKSL, false); + + BitmapShader bitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, + Shader.TileMode.CLAMP); + mRuntimeShader.setInputShader("uContentTexture", bitmapShader); + + mImageView = new ImageView(this); + + mImageView.setRenderEffect(RenderEffect.createShaderEffect(mRuntimeShader)); + mImageView.setImageDrawable(new ColorDrawable(Color.CYAN)); + + TextView overscrollXText = new TextView(this); + overscrollXText.setText("Overscroll X"); + + SeekBar overscrollXBar = new SeekBar(this); + overscrollXBar.setProgress(0); + overscrollXBar.setMin(-50); + overscrollXBar.setMax(50); + overscrollXBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + mOverscrollX = progress; + overscrollXText.setText("Overscroll X: " + mOverscrollX); + updateShader(); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + + } + }); + + TextView overscrollYText = new TextView(this); + overscrollYText.setText("Overscroll Y"); + + SeekBar overscrollYBar = new SeekBar(this); + overscrollYBar.setProgress(0); + overscrollYBar.setMin(-50); + overscrollYBar.setMax(50); + overscrollYBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + mOverscrollY = progress; + overscrollYText.setText("Overscroll Y: " + mOverscrollY); + updateShader(); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + + } + }); + + TextView scrollXText = new TextView(this); + scrollXText.setText("Scroll X"); + SeekBar scrollXSeekBar = new SeekBar(this); + scrollXSeekBar.setMin(0); + scrollXSeekBar.setMax(100); + scrollXSeekBar.setProgress(0); + scrollXSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + mScrollX = (progress / 100f); + scrollXText.setText("Scroll X: " + mScrollY); + updateShader(); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + + } + }); + + TextView scrollYText = new TextView(this); + scrollYText.setText("Scroll Y"); + SeekBar scrollYSeekBar = new SeekBar(this); + scrollYSeekBar.setMin(0); + scrollYSeekBar.setMax(100); + scrollYSeekBar.setProgress(0); + scrollYSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + mScrollY = (progress / 100f); + scrollYText.setText("Scroll Y: " + mScrollY); + updateShader(); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + + } + }); + + TextView stretchIntensityText = new TextView(this); + int stretchProgress = (int) (mMaxStretchIntensity * 100); + stretchIntensityText.setText("StretchIntensity: " + mMaxStretchIntensity); + SeekBar stretchIntensitySeekbar = new SeekBar(this); + stretchIntensitySeekbar.setProgress(stretchProgress); + stretchIntensitySeekbar.setMin(1); + stretchIntensitySeekbar.setMax((int) (MAX_STRETCH_INTENSITY * 100)); + stretchIntensitySeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + mMaxStretchIntensity = progress / 100f; + stretchIntensityText.setText("StretchIntensity: " + mMaxStretchIntensity); + updateShader(); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + + } + }); + + TextView stretchDistanceText = new TextView(this); + stretchDistanceText.setText("StretchDistance"); + SeekBar stretchDistanceSeekbar = new SeekBar(this); + stretchDistanceSeekbar.setMin(0); + stretchDistanceSeekbar.setProgress((int) (mStretchAffectedDistance * 100)); + stretchDistanceSeekbar.setMax(100); + stretchDistanceSeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + mStretchAffectedDistance = progress / 100f; + stretchDistanceText.setText("StretchDistance: " + mStretchAffectedDistance); + updateShader(); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + + } + }); + + + linearLayout.addView(mImageView, + new LinearLayout.LayoutParams( + mBitmap.getWidth(), + mBitmap.getHeight()) + ); + + linearLayout.addView(overscrollXText, + new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT + )); + linearLayout.addView(overscrollXBar, + new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + ); + + linearLayout.addView(overscrollYText, + new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT + )); + linearLayout.addView(overscrollYBar, + new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + ); + + linearLayout.addView(scrollXText, + new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT + )); + + linearLayout.addView(scrollXSeekBar, + new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + )); + + linearLayout.addView(scrollYText, + new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT + )); + + linearLayout.addView(scrollYSeekBar, + new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + )); + + linearLayout.addView(stretchIntensityText, + new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT + ) + ); + + linearLayout.addView(stretchIntensitySeekbar, + new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT + ) + ); + + linearLayout.addView(stretchDistanceText, + new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT + )); + + linearLayout.addView(stretchDistanceSeekbar, + new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT + )); + + ImageView test = new ImageView(this); + mStretchDrawable.setBitmap(mBitmap); + test.setImageDrawable(mStretchDrawable); + + mTestImageView = test; + linearLayout.addView(test, + new LinearLayout.LayoutParams(mBitmap.getWidth(), mBitmap.getHeight())); + + setContentView(linearLayout); + + } + + @Override + protected void onResume() { + super.onResume(); + mImageView.getViewTreeObserver().addOnPreDrawListener( + new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + updateShader(); + mImageView.getViewTreeObserver().removeOnPreDrawListener(this); + return false; + } + }); + } + + private void updateShader() { + final float width = mImageView.getWidth(); + final float height = mImageView.getHeight(); + final float distanceNotStretched = mStretchAffectedDistance; + final float normOverScrollDistX = mOverscrollX / width; + final float normOverScrollDistY = mOverscrollY / height; + final float distanceStretchedX = + mStretchAffectedDistance + / (1 + Math.abs(normOverScrollDistX) * mMaxStretchIntensity); + final float distanceStretchedY = + mStretchAffectedDistance + / (1 + Math.abs(normOverScrollDistY) * mMaxStretchIntensity); + final float diffX = distanceStretchedX - distanceNotStretched; + final float diffY = distanceStretchedY - distanceNotStretched; + float uScrollX = mScrollX; + float uScrollY = mScrollY; + + mRuntimeShader.setUniform("uMaxStretchIntensity", mMaxStretchIntensity); + mRuntimeShader.setUniform("uStretchAffectedDist", mStretchAffectedDistance); + mRuntimeShader.setUniform("uDistanceStretchedX", distanceStretchedX); + mRuntimeShader.setUniform("uDistanceStretchedY", distanceStretchedY); + mRuntimeShader.setUniform("uDistDiffX", diffX); + mRuntimeShader.setUniform("uDistDiffY", diffY); + mRuntimeShader.setUniform("uOverscrollX", normOverScrollDistX); + mRuntimeShader.setUniform("uOverscrollY", normOverScrollDistY); + mRuntimeShader.setUniform("uScrollX", uScrollX); + mRuntimeShader.setUniform("uScrollY", uScrollY); + mRuntimeShader.setUniform("viewportWidth", width); + mRuntimeShader.setUniform("viewportHeight", height); + + mImageView.setRenderEffect(RenderEffect.createShaderEffect(mRuntimeShader)); + + mStretchDrawable.setStretchDistance(mStretchAffectedDistance); + mStretchDrawable.setOverscrollX(normOverScrollDistX); + mStretchDrawable.setOverscrollY(normOverScrollDistY); + } + + private static class StretchDrawable extends Drawable { + + private float mStretchDistance = 0; + private float mOverScrollX = 0f; + private float mOverScrollY = 0f; + private Bitmap mBitmap = null; + + public void setStretchDistance(float stretchDistance) { + mStretchDistance = stretchDistance; + invalidateSelf(); + } + + public void setOverscrollX(float overscrollX) { + mOverScrollX = overscrollX; + invalidateSelf(); + } + + public void setOverscrollY(float overscrollY) { + mOverScrollY = overscrollY; + invalidateSelf(); + } + + public void setBitmap(Bitmap bitmap) { + mBitmap = bitmap; + invalidateSelf(); + } + + @Override + public void draw(Canvas canvas) { + if (mStretchDistance > 0 && canvas instanceof RecordingCanvas) { + Rect bounds = getBounds(); + ((RecordingCanvas) canvas).mNode.stretch( + 0, + 0, + bounds.width(), + bounds.height(), + mOverScrollX, + mOverScrollY, + mStretchDistance + ); + } + if (mBitmap != null) { + canvas.drawBitmap(mBitmap, 0f, 0f, null); + } + } + + @Override + public void setAlpha(int alpha) { + + } + + @Override + public void setColorFilter(ColorFilter colorFilter) { + + } + + @Override + public int getOpacity() { + return 0; + } + } + + private static final String SKSL = "in shader uContentTexture;\n" + + "uniform float uMaxStretchIntensity; // multiplier to apply to scale effect\n" + + "uniform float uStretchAffectedDist; // Maximum percentage to stretch beyond bounds" + + " of target\n" + + "\n" + + "// Distance stretched as a function of the normalized overscroll times scale " + + "intensity\n" + + "uniform float uDistanceStretchedX;\n" + + "uniform float uDistanceStretchedY;\n" + + "uniform float uDistDiffX;\n" + + "uniform float uDistDiffY; // Difference between the peak stretch amount and " + + "overscroll amount normalized\n" + + "uniform float uScrollX; // Horizontal offset represented as a ratio of pixels " + + "divided by the target width\n" + + "uniform float uScrollY; // Vertical offset represented as a ratio of pixels " + + "divided by the target height\n" + + "uniform float uOverscrollX; // Normalized overscroll amount in the horizontal " + + "direction\n" + + "uniform float uOverscrollY; // Normalized overscroll amount in the vertical " + + "direction\n" + + "\n" + + "uniform float viewportWidth; // target height in pixels\n" + + "uniform float viewportHeight; // target width in pixels\n" + + "\n" + + "vec4 main(vec2 coord) {\n" + + "\n" + + " // Normalize SKSL pixel coordinate into a unit vector\n" + + " vec2 uv = vec2(coord.x / viewportWidth, coord.y / viewportHeight);\n" + + " float inU = uv.x;\n" + + " float inV = uv.y;\n" + + " float outU;\n" + + " float outV;\n" + + " float stretchIntensity;\n" + + "\n" + + " // Add the normalized scroll position within scrolling list\n" + + " inU += uScrollX;\n" + + " inV += uScrollY;\n" + + "\n" + + " outU = inU;\n" + + " outV = inV;\n" + + " if (uOverscrollX > 0) {\n" + + " if (inU <= uStretchAffectedDist) {\n" + + " inU = uStretchAffectedDist - inU;\n" + + " float posBasedVariation = smoothstep(0., uStretchAffectedDist, inU);\n" + + " stretchIntensity = uMaxStretchIntensity * uOverscrollX * " + + "posBasedVariation;\n" + + " outU = uDistanceStretchedX - (inU / (1. + stretchIntensity));\n" + + " } else {\n" + + " outU = uDistDiffX + inU;\n" + + " }\n" + + " }\n" + + "\n" + + " if (uOverscrollX < 0) {\n" + + " float stretchAffectedDist = 1. - uStretchAffectedDist;\n" + + " if (inU >= stretchAffectedDist) {\n" + + " inU = inU - stretchAffectedDist;\n" + + " float posBasedVariation = (smoothstep(0., uStretchAffectedDist, " + + "inU));\n" + + " stretchIntensity = uMaxStretchIntensity * (-uOverscrollX) * " + + "posBasedVariation;\n" + + " outU = 1 - (uDistanceStretchedX - (inU / (1. + stretchIntensity)))" + + ";\n" + + " } else if (inU < stretchAffectedDist) {\n" + + " outU = -uDistDiffX + inU;\n" + + " }\n" + + " }\n" + + "\n" + + " if (uOverscrollY > 0) {\n" + + " if (inV <= uStretchAffectedDist) {\n" + + " inV = uStretchAffectedDist - inV;\n" + + " float posBasedVariation = smoothstep(0., uStretchAffectedDist, inV);\n" + + " stretchIntensity = uMaxStretchIntensity * uOverscrollY * " + + "posBasedVariation;\n" + + " outV = uDistanceStretchedY - (inV / (1. + stretchIntensity));\n" + + " } else if (inV >= uStretchAffectedDist) {\n" + + " outV = uDistDiffY + inV;\n" + + " }\n" + + " }\n" + + "\n" + + " if (uOverscrollY < 0) {\n" + + " float stretchAffectedDist = 1. - uStretchAffectedDist;\n" + + " if (inV >= stretchAffectedDist) {\n" + + " inV = inV - stretchAffectedDist;\n" + + " float posBasedVariation = (smoothstep(0., uStretchAffectedDist, inV));\n" + + " stretchIntensity = uMaxStretchIntensity * (-uOverscrollY) * " + + "posBasedVariation;\n" + + " outV = 1 - (uDistanceStretchedY - (inV / (1. + stretchIntensity)));\n" + + " } else if (inV < stretchAffectedDist) {\n" + + " outV = -uDistDiffY + inV;\n" + + " }\n" + + " }\n" + + "\n" + + " uv.x = outU;\n" + + " uv.y = outV;\n" + + " coord.x = uv.x * viewportWidth;\n" + + " coord.y = uv.y * viewportHeight;\n" + + " return sample(uContentTexture, coord);\n" + + "}"; +} |