diff options
author | Nader Jawad <njawad@google.com> | 2021-04-19 19:45:13 -0700 |
---|---|---|
committer | Nader Jawad <njawad@google.com> | 2021-05-03 18:08:47 -0700 |
commit | 197743ff9c6ffb7bc96004c38b518fd3941948b0 (patch) | |
tree | 833d7f67ff16d4bf478710e0c711fe4ffce06844 | |
parent | 9443a3e84d73d3423ede16e158b641fb320910dd (diff) |
Update hole punch logic in HWUI
--Updated HWUI holepunch logic for SurfaceView to
also apply the stretch to the hole punch
--Updated RenderNode callbacks to also include
an offset from the ancestor RenderNode that also
has a stretch configured on it
--Added new test activity to verify hole punch
logic
Bug: 179047472
Test: manual
Change-Id: Ibbaf8248a31839ba9dc352ecb9fef54e1276918e
24 files changed, 557 insertions, 121 deletions
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index ac70dff4f03e..4f0e1afeace9 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -146,8 +146,9 @@ public final class SurfaceControl implements Parcelable { private static native void nativeSetBlurRegions(long transactionObj, long nativeObj, float[][] regions, int length); private static native void nativeSetStretchEffect(long transactionObj, long nativeObj, - float left, float top, float right, float bottom, float vecX, float vecY, - float maxStretchAmount); + float width, float height, float vecX, float vecY, + float maxStretchAmountX, float maxStretchAmountY, float childRelativeLeft, + float childRelativeTop, float childRelativeRight, float childRelativeBottom); private static native boolean nativeClearContentFrameStats(long nativeObject); private static native boolean nativeGetContentFrameStats(long nativeObject, WindowContentFrameStats outStats); @@ -3038,11 +3039,14 @@ public final class SurfaceControl implements Parcelable { /** * @hide */ - public Transaction setStretchEffect(SurfaceControl sc, float left, float top, float right, - float bottom, float vecX, float vecY, float maxStretchAmount) { + public Transaction setStretchEffect(SurfaceControl sc, float width, float height, + float vecX, float vecY, float maxStretchAmountX, + float maxStretchAmountY, float childRelativeLeft, float childRelativeTop, float childRelativeRight, + float childRelativeBottom) { checkPreconditions(sc); - nativeSetStretchEffect(mNativeObject, sc.mNativeObject, left, top, right, bottom, - vecX, vecY, maxStretchAmount); + nativeSetStretchEffect(mNativeObject, sc.mNativeObject, width, height, + vecX, vecY, maxStretchAmountX, maxStretchAmountY, childRelativeLeft, childRelativeTop, + childRelativeRight, childRelativeBottom); return this; } diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 7bdf5cf879f3..2fce4348cbb0 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -1471,10 +1471,13 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } @Override - public void applyStretch(long frameNumber, float left, float top, float right, - float bottom, float vecX, float vecY, float maxStretch) { - mRtTransaction.setStretchEffect(mSurfaceControl, left, top, right, bottom, vecX, vecY, - maxStretch); + public void applyStretch(long frameNumber, float width, float height, + float vecX, float vecY, float maxStretchX, float maxStretchY, + float childRelativeLeft, float childRelativeTop, float childRelativeRight, + float childRelativeBottom) { + mRtTransaction.setStretchEffect(mSurfaceControl, width, height, vecX, vecY, + maxStretchX, maxStretchY, childRelativeLeft, childRelativeTop, + childRelativeRight, childRelativeBottom); applyOrMergeTransaction(mRtTransaction, frameNumber); } diff --git a/core/java/android/widget/EdgeEffect.java b/core/java/android/widget/EdgeEffect.java index 4d2d9e86f1a6..9398d38d51bf 100644 --- a/core/java/android/widget/EdgeEffect.java +++ b/core/java/android/widget/EdgeEffect.java @@ -641,14 +641,10 @@ public class EdgeEffect { boolean hasValidVectors = Float.isFinite(vecX) && Float.isFinite(vecY); if (right > left && bottom > top && mWidth > 0 && mHeight > 0 && hasValidVectors) { renderNode.stretch( - left, - top, - right, - bottom, - vecX, - vecY, - mWidth, - mHeight + vecX, // horizontal stretch intensity + vecY, // vertical stretch intensity + mWidth, // max horizontal stretch in pixels + mHeight // max vertical stretch in pixels ); } } else { diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index 27f82f1b52dd..d528428a0e83 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -626,12 +626,24 @@ static void nativeSetBlurRegions(JNIEnv* env, jclass clazz, jlong transactionObj } static void nativeSetStretchEffect(JNIEnv* env, jclass clazz, jlong transactionObj, - jlong nativeObject, jfloat left, jfloat top, jfloat right, - jfloat bottom, jfloat vecX, jfloat vecY, - jfloat maxStretchAmount) { + jlong nativeObject, jfloat width, jfloat height, + jfloat vecX, jfloat vecY, + jfloat maxStretchAmountX, jfloat maxStretchAmountY, + jfloat childRelativeLeft, jfloat childRelativeTop, + jfloat childRelativeRight, jfloat childRelativeBottom) { auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); - SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject); - transaction->setStretchEffect(ctrl, left, top, right, bottom, vecX, vecY, maxStretchAmount); + auto* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject); + auto stretch = StretchEffect{ + .width = width, + .height = height, + .vectorX = vecX, + .vectorY = vecY, + .maxAmountX = maxStretchAmountX, + .maxAmountY = maxStretchAmountY, + .mappedChildBounds = FloatRect( + childRelativeLeft, childRelativeTop, childRelativeRight, childRelativeBottom) + }; + transaction->setStretchEffect(ctrl, stretch); } static void nativeSetSize(JNIEnv* env, jclass clazz, jlong transactionObj, @@ -1829,7 +1841,7 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (void*)nativeSetLayerStack }, {"nativeSetBlurRegions", "(JJ[[FI)V", (void*)nativeSetBlurRegions }, - {"nativeSetStretchEffect", "(JJFFFFFFF)V", + {"nativeSetStretchEffect", "(JJFFFFFFFFFF)V", (void*) nativeSetStretchEffect }, {"nativeSetShadowRadius", "(JJF)V", (void*)nativeSetShadowRadius }, diff --git a/graphics/java/android/graphics/RenderNode.java b/graphics/java/android/graphics/RenderNode.java index 6fcd8d09a770..01fd231d011f 100644 --- a/graphics/java/android/graphics/RenderNode.java +++ b/graphics/java/android/graphics/RenderNode.java @@ -280,8 +280,10 @@ public final class RenderNode { * * @hide */ - default void applyStretch(long frameNumber, float left, float top, float right, - float bottom, float vecX, float vecY, float maxStretch) { } + default void applyStretch(long frameNumber, float width, float height, + float vecX, float vecY, + float maxStretchX, float maxStretchY, float childRelativeLeft, + float childRelativeTop, float childRelativeRight, float childRelativeBottom) { } /** * Called by native on RenderThread to notify that the view is no longer in the @@ -326,10 +328,13 @@ public final class RenderNode { } @Override - public void applyStretch(long frameNumber, float left, float top, float right, float bottom, - float vecX, float vecY, float maxStretch) { + public void applyStretch(long frameNumber, float width, float height, + float vecX, float vecY, float maxStretchX, float maxStretchY, float childRelativeLeft, + float childRelativeTop, float childRelativeRight, float childRelativeBottom) { for (PositionUpdateListener pul : mListeners) { - pul.applyStretch(frameNumber, left, top, right, bottom, vecX, vecY, maxStretch); + pul.applyStretch(frameNumber, width, height, vecX, vecY, maxStretchX, + maxStretchY, childRelativeLeft, childRelativeTop, childRelativeRight, + childRelativeBottom); } } } @@ -719,19 +724,15 @@ public final class RenderNode { } /** @hide */ - public boolean stretch(float left, float top, float right, float bottom, - float vecX, float vecY, float maxStretchAmountX, float maxStretchAmountY) { + public boolean stretch(float vecX, float vecY, + float maxStretchAmountX, float maxStretchAmountY) { if (Float.isInfinite(vecX) || Float.isNaN(vecX)) { throw new IllegalArgumentException("vecX must be a finite, non-NaN value " + vecX); } 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( - "Stretch region must not be empty, got " - + new RectF(left, top, right, bottom).toString()); - } + if (maxStretchAmountX <= 0.0f) { throw new IllegalArgumentException( "The max horizontal stretch amount must be >0, got " + maxStretchAmountX); @@ -742,10 +743,6 @@ public final class RenderNode { } return nStretch( mNativeRenderNode, - left, - top, - right, - bottom, vecX, vecY, maxStretchAmountX, @@ -1701,8 +1698,8 @@ public final class RenderNode { private static native boolean nClearStretch(long renderNode); @CriticalNative - private static native boolean nStretch(long renderNode, float left, float top, float right, - float bottom, float vecX, float vecY, float maxStretchX, float maxStretchY); + private static native boolean nStretch(long renderNode, float vecX, float vecY, + float maxStretchX, float maxStretchY); @CriticalNative private static native boolean nHasShadow(long renderNode); diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index f2c48bb8d2bc..02123090e1c4 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -467,6 +467,7 @@ cc_defaults { "pipeline/skia/HolePunch.cpp", "pipeline/skia/SkiaDisplayList.cpp", "pipeline/skia/SkiaRecordingCanvas.cpp", + "pipeline/skia/StretchMask.cpp", "pipeline/skia/RenderNodeDrawable.cpp", "pipeline/skia/ReorderBarrierDrawables.cpp", "pipeline/skia/TransformCanvas.cpp", diff --git a/libs/hwui/DamageAccumulator.cpp b/libs/hwui/DamageAccumulator.cpp index 0bf948006ea0..94fe243378b1 100644 --- a/libs/hwui/DamageAccumulator.cpp +++ b/libs/hwui/DamageAccumulator.cpp @@ -197,6 +197,27 @@ static void applyTransforms(DirtyStack* frame, DirtyStack* end) { } } +static void computeTransformImpl(const DirtyStack* frame, const DirtyStack* end, + Matrix4* outMatrix) { + while (frame != end) { + switch (frame->type) { + case TransformRenderNode: + frame->renderNode->applyViewPropertyTransforms(*outMatrix); + break; + case TransformMatrix4: + outMatrix->multiply(*frame->matrix4); + break; + case TransformNone: + // nothing to be done + break; + default: + LOG_ALWAYS_FATAL("Tried to compute transform with an invalid type: %d", + frame->type); + } + frame = frame->prev; + } +} + void DamageAccumulator::applyRenderNodeTransform(DirtyStack* frame) { if (frame->pendingDirty.isEmpty()) { return; @@ -249,19 +270,38 @@ void DamageAccumulator::finish(SkRect* totalDirty) { mHead->pendingDirty.setEmpty(); } -const StretchEffect* DamageAccumulator::findNearestStretchEffect() const { +DamageAccumulator::StretchResult DamageAccumulator::findNearestStretchEffect() const { DirtyStack* frame = mHead; while (frame->prev != frame) { - frame = frame->prev; if (frame->type == TransformRenderNode) { + const auto& renderNode = frame->renderNode; + const auto& frameRenderNodeProperties = renderNode->properties(); const auto& effect = - frame->renderNode->properties().layerProperties().getStretchEffect(); + frameRenderNodeProperties.layerProperties().getStretchEffect(); + const float width = (float) renderNode->getWidth(); + const float height = (float) renderNode->getHeight(); if (!effect.isEmpty()) { - return &effect; + Matrix4 stretchMatrix; + computeTransformImpl(mHead, frame, &stretchMatrix); + Rect stretchRect = Rect(0.f, 0.f, width, height); + stretchMatrix.mapRect(stretchRect); + + return StretchResult{ + .stretchEffect = &effect, + .childRelativeBounds = SkRect::MakeLTRB( + stretchRect.left, + stretchRect.top, + stretchRect.right, + stretchRect.bottom + ), + .width = width, + .height = height + }; } } + frame = frame->prev; } - return nullptr; + return StretchResult{}; } } /* namespace uirenderer */ diff --git a/libs/hwui/DamageAccumulator.h b/libs/hwui/DamageAccumulator.h index 89ee0e34055b..90a35174d929 100644 --- a/libs/hwui/DamageAccumulator.h +++ b/libs/hwui/DamageAccumulator.h @@ -21,6 +21,7 @@ #include <SkMatrix.h> #include <SkRect.h> +#include <effects/StretchEffect.h> #include "utils/Macros.h" @@ -35,7 +36,6 @@ namespace uirenderer { struct DirtyStack; class RenderNode; class Matrix4; -class StretchEffect; class DamageAccumulator { PREVENT_COPY_AND_ASSIGN(DamageAccumulator); @@ -63,7 +63,29 @@ public: void finish(SkRect* totalDirty); - const StretchEffect* findNearestStretchEffect() const; + struct StretchResult { + /** + * Stretch parameters configured on the stretch container + */ + const StretchEffect* stretchEffect; + + /** + * Bounds of the child relative to the stretch container + */ + const SkRect childRelativeBounds; + + /** + * Width of the stretch container + */ + const float width; + + /** + * Height of the stretch container + */ + const float height; + }; + + [[nodiscard]] StretchResult findNearestStretchEffect() const; private: void pushCommon(); diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp index e9eae3d14760..fce2e1fa9970 100644 --- a/libs/hwui/RenderNode.cpp +++ b/libs/hwui/RenderNode.cpp @@ -194,6 +194,9 @@ void RenderNode::pushLayerUpdate(TreeInfo& info) { SkRect dirty; info.damageAccumulator->peekAtDirty(&dirty); info.layerUpdateQueue->enqueueLayerWithDamage(this, dirty); + if (!dirty.isEmpty()) { + mStretchMask.markDirty(); + } // There might be prefetched layers that need to be accounted for. // That might be us, so tell CanvasContext that this layer is in the @@ -302,6 +305,12 @@ void RenderNode::pushStagingPropertiesChanges(TreeInfo& info) { damageSelf(info); info.damageAccumulator->popTransform(); syncProperties(); + + const StretchEffect& stagingStretch = + mProperties.layerProperties().getStretchEffect(); + if (stagingStretch.isEmpty()) { + mStretchMask.clear(); + } // We could try to be clever and only re-damage if the matrix changed. // However, we don't need to worry about that. The cost of over-damaging // here is only going to be a single additional map rect of this node diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h index 988141fe191d..6a0b1aafd7c7 100644 --- a/libs/hwui/RenderNode.h +++ b/libs/hwui/RenderNode.h @@ -40,6 +40,7 @@ #include "pipeline/skia/SkiaLayer.h" #include <vector> +#include <pipeline/skia/StretchMask.h> class SkBitmap; class SkPaint; @@ -127,6 +128,8 @@ public: } } + StretchMask& getStretchMask() { return mStretchMask; } + VirtualLightRefBase* getUserContext() const { return mUserContext.get(); } void setUserContext(VirtualLightRefBase* context) { mUserContext = context; } @@ -286,6 +289,7 @@ private: UsageHint mUsageHint = UsageHint::Unknown; bool mHasHolePunches; + StretchMask mStretchMask; // METHODS & FIELDS ONLY USED BY THE SKIA RENDERER public: diff --git a/libs/hwui/effects/StretchEffect.cpp b/libs/hwui/effects/StretchEffect.cpp index 6eb6e1ee4a5c..1519d69d7053 100644 --- a/libs/hwui/effects/StretchEffect.cpp +++ b/libs/hwui/effects/StretchEffect.cpp @@ -189,17 +189,12 @@ static const float ZERO = 0.f; static const float CONTENT_DISTANCE_STRETCHED = 1.f; static const float INTERPOLATION_STRENGTH_VALUE = 0.7f; -sk_sp<SkShader> StretchEffect::getShader(const sk_sp<SkImage>& snapshotImage) const { +sk_sp<SkShader> StretchEffect::getShader(float width, float height, + const sk_sp<SkImage>& snapshotImage) const { if (isEmpty()) { return nullptr; } - if (mStretchShader != nullptr) { - return mStretchShader; - } - - float viewportWidth = stretchArea.width(); - float viewportHeight = stretchArea.height(); float normOverScrollDistX = mStretchDirection.x(); float normOverScrollDistY = mStretchDirection.y(); float distanceStretchedX = CONTENT_DISTANCE_STRETCHED / (1 + abs(normOverScrollDistX)); @@ -228,12 +223,10 @@ sk_sp<SkShader> StretchEffect::getShader(const sk_sp<SkImage>& snapshotImage) co 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); - - mStretchShader = mBuilder->makeShader(nullptr, false); + mBuilder->uniform("viewportWidth").set(&width, 1); + mBuilder->uniform("viewportHeight").set(&height, 1); - return mStretchShader; + return mBuilder->makeShader(nullptr, false); } sk_sp<SkRuntimeEffect> StretchEffect::getStretchEffect() { diff --git a/libs/hwui/effects/StretchEffect.h b/libs/hwui/effects/StretchEffect.h index 546d53b1a2df..61537f0b5a2c 100644 --- a/libs/hwui/effects/StretchEffect.h +++ b/libs/hwui/effects/StretchEffect.h @@ -26,19 +26,15 @@ namespace android::uirenderer { -// TODO: Inherit from base RenderEffect type? class StretchEffect { public: - enum class StretchInterpolator { - SmoothStep, - }; - StretchEffect(const SkRect& area, const SkVector& direction, float maxStretchAmountX, + StretchEffect(const SkVector& direction, + float maxStretchAmountX, float maxStretchAmountY) - : stretchArea(area) - , maxStretchAmountX(maxStretchAmountX) + : maxStretchAmountX(maxStretchAmountX) , maxStretchAmountY(maxStretchAmountY) - , mStretchDirection(direction) {} + , mStretchDirection(direction) { } StretchEffect() {} @@ -51,14 +47,18 @@ public: } StretchEffect& operator=(const StretchEffect& other) { - this->stretchArea = other.stretchArea; this->mStretchDirection = other.mStretchDirection; - this->mStretchShader = other.mStretchShader; this->maxStretchAmountX = other.maxStretchAmountX; this->maxStretchAmountY = other.maxStretchAmountY; return *this; } + bool operator==(const StretchEffect& other) const { + return mStretchDirection == other.mStretchDirection && + maxStretchAmountX == other.maxStretchAmountX && + maxStretchAmountY == other.maxStretchAmountY; + } + void mergeWith(const StretchEffect& other) { if (other.isEmpty()) { return; @@ -67,33 +67,26 @@ public: *this = other; return; } - setStretchDirection(mStretchDirection + other.mStretchDirection); + mStretchDirection += other.mStretchDirection; if (isEmpty()) { return setEmpty(); } - stretchArea.join(other.stretchArea); maxStretchAmountX = std::max(maxStretchAmountX, other.maxStretchAmountX); maxStretchAmountY = std::max(maxStretchAmountY, other.maxStretchAmountY); } - sk_sp<SkShader> getShader(const sk_sp<SkImage>& snapshotImage) const; + sk_sp<SkShader> getShader(float width, float height, + const sk_sp<SkImage>& snapshotImage) const; - SkRect stretchArea {0, 0, 0, 0}; float maxStretchAmountX = 0; float maxStretchAmountY = 0; - void setStretchDirection(const SkVector& direction) { - mStretchShader = 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<SkShader> mStretchShader; }; } // namespace android::uirenderer diff --git a/libs/hwui/jni/android_graphics_RenderNode.cpp b/libs/hwui/jni/android_graphics_RenderNode.cpp index fffa80614370..5131c646c4a4 100644 --- a/libs/hwui/jni/android_graphics_RenderNode.cpp +++ b/libs/hwui/jni/android_graphics_RenderNode.cpp @@ -180,12 +180,10 @@ 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 maxX, + jfloat vX, jfloat vY, jfloat maxX, jfloat maxY) { - StretchEffect effect = StretchEffect(SkRect::MakeLTRB(left, top, right, bottom), - {.fX = vX, .fY = vY}, maxX, maxY); - RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + auto* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + StretchEffect effect = StretchEffect({.fX = vX, .fY = vY}, maxX, maxY); renderNode->mutateStagingProperties().mutateLayerProperties().mutableStretchEffect().mergeWith( effect); renderNode->setPropertyFieldsDirty(RenderNode::GENERIC); @@ -643,13 +641,15 @@ static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject, void handleStretchEffect(const TreeInfo& info, const Matrix4& transform) { // Search up to find the nearest stretcheffect parent - const StretchEffect* effect = info.damageAccumulator->findNearestStretchEffect(); + const DamageAccumulator::StretchResult result = + info.damageAccumulator->findNearestStretchEffect(); + const StretchEffect* effect = result.stretchEffect; if (!effect) { return; } - uirenderer::Rect area = effect->stretchArea; - transform.mapRect(area); + const auto& childRelativeBounds = result.childRelativeBounds; + JNIEnv* env = jnienv(); jobject localref = env->NewLocalRef(mWeakRef); @@ -661,9 +661,17 @@ static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject, #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, stretchDirection.fX, stretchDirection.fY, - effect->maxStretchAmountX, effect->maxStretchAmountY); + info.canvasContext.getFrameNumber(), + result.width, + result.height, + stretchDirection.fX, + stretchDirection.fY, + effect->maxStretchAmountX, + effect->maxStretchAmountY, + childRelativeBounds.left(), + childRelativeBounds.top(), + childRelativeBounds.right(), + childRelativeBounds.bottom()); #endif env->DeleteLocalRef(localref); } @@ -739,7 +747,7 @@ static const JNINativeMethod gMethods[] = { {"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", "(JFFFFFFFF)Z", (void*)android_view_RenderNode_stretch}, + {"nStretch", "(JFFFF)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}, @@ -814,7 +822,7 @@ int register_android_view_RenderNode(JNIEnv* env) { gPositionListener_PositionChangedMethod = GetMethodIDOrDie(env, clazz, "positionChanged", "(JIIII)V"); gPositionListener_ApplyStretchMethod = - GetMethodIDOrDie(env, clazz, "applyStretch", "(JFFFFFFF)V"); + GetMethodIDOrDie(env, clazz, "applyStretch", "(JFFFFFFFFFF)V"); gPositionListener_PositionLostMethod = GetMethodIDOrDie(env, clazz, "positionLost", "(J)V"); return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods)); diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp index 77d99a67b602..1ae06d082744 100644 --- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp +++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp @@ -16,6 +16,7 @@ #include "RenderNodeDrawable.h" #include <SkPaintFilterCanvas.h> +#include "StretchMask.h" #include "RenderNode.h" #include "SkiaDisplayList.h" #include "TransformCanvas.h" @@ -245,17 +246,37 @@ void RenderNodeDrawable::drawContent(SkCanvas* canvas) const { "SurfaceID|%" PRId64, renderNode->uniqueId()).c_str(), nullptr); } - if (renderNode->hasHolePunches()) { - TransformCanvas transformCanvas(canvas); - displayList->draw(&transformCanvas); - } - const StretchEffect& stretch = properties.layerProperties().getStretchEffect(); if (stretch.isEmpty()) { + // If we don't have any stretch effects, issue the filtered + // canvas draw calls to make sure we still punch a hole + // with the same canvas transformation + clip into the target + // canvas then draw the layer on top + if (renderNode->hasHolePunches()) { + TransformCanvas transformCanvas(canvas, SkBlendMode::kClear); + displayList->draw(&transformCanvas); + } canvas->drawImageRect(snapshotImage, bounds, bounds, sampling, &paint, SkCanvas::kStrict_SrcRectConstraint); } else { - sk_sp<SkShader> stretchShader = stretch.getShader(snapshotImage); + // If we do have stretch effects and have hole punches, + // then create a mask and issue the filtered draw calls to + // get the corresponding hole punches. + // Then apply the stretch to the mask and draw the mask to + // the destination + if (renderNode->hasHolePunches()) { + GrRecordingContext* context = canvas->recordingContext(); + StretchMask& stretchMask = renderNode->getStretchMask(); + stretchMask.draw(context, + stretch, + bounds, + displayList, + canvas); + } + + sk_sp<SkShader> stretchShader = stretch.getShader(bounds.width(), + bounds.height(), + snapshotImage); paint.setShader(stretchShader); canvas->drawRect(bounds, paint); } diff --git a/libs/hwui/pipeline/skia/StretchMask.cpp b/libs/hwui/pipeline/skia/StretchMask.cpp new file mode 100644 index 000000000000..2bbd8a4fa028 --- /dev/null +++ b/libs/hwui/pipeline/skia/StretchMask.cpp @@ -0,0 +1,64 @@ +/* + * 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 "StretchMask.h" +#include "SkSurface.h" +#include "SkCanvas.h" +#include "TransformCanvas.h" +#include "SkiaDisplayList.h" + +using android::uirenderer::StretchMask; + +void StretchMask::draw(GrRecordingContext* context, + const StretchEffect& stretch, + const SkRect& bounds, + skiapipeline::SkiaDisplayList* displayList, + SkCanvas* canvas) { + float width = bounds.width(); + float height = bounds.height(); + if (mMaskSurface == nullptr || mMaskSurface->width() != width || + mMaskSurface->height() != height) { + // Create a new surface if we don't have one or our existing size does + // not match. + mMaskSurface = SkSurface::MakeRenderTarget( + context, + SkBudgeted::kYes, + SkImageInfo::Make( + width, + height, + SkColorType::kAlpha_8_SkColorType, + SkAlphaType::kPremul_SkAlphaType) + ); + mIsDirty = true; + } + + if (mIsDirty) { + SkCanvas* maskCanvas = mMaskSurface->getCanvas(); + maskCanvas->drawColor(0, SkBlendMode::kClear); + TransformCanvas transformCanvas(maskCanvas, SkBlendMode::kSrcOver); + displayList->draw(&transformCanvas); + } + + sk_sp<SkImage> maskImage = mMaskSurface->makeImageSnapshot(); + sk_sp<SkShader> maskStretchShader = stretch.getShader( + width, height, maskImage); + + SkPaint maskPaint; + maskPaint.setShader(maskStretchShader); + maskPaint.setBlendMode(SkBlendMode::kDstOut); + canvas->drawRect(bounds, maskPaint); + + mIsDirty = false; +}
\ No newline at end of file diff --git a/libs/hwui/pipeline/skia/StretchMask.h b/libs/hwui/pipeline/skia/StretchMask.h new file mode 100644 index 000000000000..dc698b8e57ff --- /dev/null +++ b/libs/hwui/pipeline/skia/StretchMask.h @@ -0,0 +1,62 @@ +/* + * 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. + */ +#pragma once + +#include "GrRecordingContext.h" +#include <effects/StretchEffect.h> +#include <SkSurface.h> +#include "SkiaDisplayList.h" + +namespace android::uirenderer { + +/** + * Helper class used to create/cache an SkSurface instance + * to create a mask that is used to draw a stretched hole punch + */ +class StretchMask { + public: + /** + * Release the current surface used for the stretch mask + */ + void clear() { + mMaskSurface = nullptr; + } + + /** + * Reset the dirty flag to re-create the stretch mask on the next draw + * pass + */ + void markDirty() { + mIsDirty = true; + } + + /** + * Draws the stretch mask into the given target canvas + * @param context GrRecordingContext used to create the surface if necessary + * @param stretch StretchEffect to apply to the mask + * @param bounds Target bounds to draw into the given canvas + * @param displayList List of drawing commands to render into the stretch mask + * @param canvas Target canvas to draw the mask into + */ + void draw(GrRecordingContext* context, + const StretchEffect& stretch, const SkRect& bounds, + skiapipeline::SkiaDisplayList* displayList, SkCanvas* canvas); +private: + sk_sp<SkSurface> mMaskSurface; + bool mIsDirty = true; +}; + +} diff --git a/libs/hwui/pipeline/skia/TransformCanvas.cpp b/libs/hwui/pipeline/skia/TransformCanvas.cpp index a6e4c4cf9ca7..6777c00c4655 100644 --- a/libs/hwui/pipeline/skia/TransformCanvas.cpp +++ b/libs/hwui/pipeline/skia/TransformCanvas.cpp @@ -28,8 +28,8 @@ void TransformCanvas::onDrawAnnotation(const SkRect& rect, const char* key, SkDa SkRRect roundRect = SkRRect::MakeRectXY(rect, radiusX, radiusY); SkPaint paint; - paint.setColor(0); - paint.setBlendMode(SkBlendMode::kClear); + paint.setColor(SkColors::kBlack); + paint.setBlendMode(mHolePunchBlendMode); mWrappedCanvas->drawRRect(roundRect, paint); } } diff --git a/libs/hwui/pipeline/skia/TransformCanvas.h b/libs/hwui/pipeline/skia/TransformCanvas.h index 47f77f107441..685b71d017e9 100644 --- a/libs/hwui/pipeline/skia/TransformCanvas.h +++ b/libs/hwui/pipeline/skia/TransformCanvas.h @@ -17,10 +17,12 @@ #include <include/core/SkCanvas.h> #include "SkPaintFilterCanvas.h" +#include <effects/StretchEffect.h> class TransformCanvas : public SkPaintFilterCanvas { public: - TransformCanvas(SkCanvas* target) : SkPaintFilterCanvas(target), mWrappedCanvas(target) {} + TransformCanvas(SkCanvas* target, SkBlendMode blendmode) : + SkPaintFilterCanvas(target), mWrappedCanvas(target), mHolePunchBlendMode(blendmode) {} protected: bool onFilter(SkPaint& paint) const override; @@ -32,4 +34,5 @@ protected: private: // We don't own the canvas so just maintain a raw pointer to it SkCanvas* mWrappedCanvas; + const SkBlendMode mHolePunchBlendMode; }; diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml index 6bf44920ee26..04a55d6038b0 100644 --- a/tests/HwAccelerationTest/AndroidManifest.xml +++ b/tests/HwAccelerationTest/AndroidManifest.xml @@ -409,6 +409,15 @@ </intent-filter> </activity> + <activity android:name="ScrollingStretchSurfaceViewActivity" + android:label="SurfaceView/Scrolling Stretched SurfaceView" + 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="GetBitmapSurfaceViewActivity" android:label="SurfaceView/GetBitmap with Camera source" android:exported="true"> diff --git a/tests/HwAccelerationTest/res/layout/scrolling_stretch_surfaceview.xml b/tests/HwAccelerationTest/res/layout/scrolling_stretch_surfaceview.xml new file mode 100644 index 000000000000..77f5e60dc091 --- /dev/null +++ b/tests/HwAccelerationTest/res/layout/scrolling_stretch_surfaceview.xml @@ -0,0 +1,76 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> + +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + android:overScrollMode="always" + android:fillViewport="true" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + > + <LinearLayout + android:layout_width="match_parent" + android:layout_height="100dp" + android:layout_marginTop="100dp" + android:orientation="horizontal" + > + + <ImageView + android:id="@+id/vertical_imageview" + android:layout_width="0dp" + android:layout_weight="1" + android:layout_height="match_parent"/> + <FrameLayout + android:id="@+id/vertical_surfaceview_container" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1"/> + </LinearLayout> + + <HorizontalScrollView + android:overScrollMode="always" + android:layout_width="400dp" + android:layout_height="0dp" + android:background="#FF0000" + android:layout_weight="1" + > + <LinearLayout + android:layout_width="400dp" + android:layout_height="400dp" + android:layout_marginLeft="100dp" + android:orientation="vertical"> + + <ImageView + android:id="@+id/horizontal_imageview" + android:layout_width="100dp" + android:layout_weight="1" + android:layout_height="0dp"/> + + <FrameLayout + android:id="@+id/horizontal_surfaceview_container" + android:layout_width="100dp" + android:layout_height="0dp" + android:layout_weight="1"/> + + </LinearLayout> + </HorizontalScrollView> + </LinearLayout> +</ScrollView>
\ No newline at end of file diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/PositionListenerActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/PositionListenerActivity.java index 6b6287da2711..2ad034cd143e 100644 --- a/tests/HwAccelerationTest/src/com/android/test/hwui/PositionListenerActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/PositionListenerActivity.java @@ -74,12 +74,10 @@ public class PositionListenerActivity extends Activity { float maxStretchAmount = 100f; // Although we could do this in a single call, the real one won't be - so mimic that if (dir.x != 0f) { - node.stretch(0f, 0f, (float) getWidth(), (float) getHeight(), - dir.x, 0f, maxStretchAmount, maxStretchAmount); + node.stretch(dir.x, 0f, maxStretchAmount, maxStretchAmount); } if (dir.y != 0f) { - node.stretch(0f, 0f, (float) getWidth(), (float) getHeight(), - 0f, dir.y, maxStretchAmount, maxStretchAmount); + node.stretch(0f, dir.y, maxStretchAmount, maxStretchAmount); } } }; @@ -94,10 +92,13 @@ public class PositionListenerActivity extends Activity { int mCurrentCount = 0; int mTranslateY = 0; Rect mPosition = new Rect(); - RectF mStretchArea = new RectF(); + float mWidth = 0f; + float mHeight = 0f; + RectF mMappedBounds = new RectF(); float mStretchX = 0.0f; float mStretchY = 0.0f; - float mStretchMax = 0.0f; + float mStretchMaxX = 0.0f; + float mStretchMaxY = 0.0f; MyPositionReporter(Context c) { super(c); @@ -128,9 +129,12 @@ public class PositionListenerActivity extends Activity { } void updateText() { - setText(String.format("%d: Position %s, stretch area %s, vec %f,%f, amount %f", - mCurrentCount, mPosition.toShortString(), mStretchArea.toShortString(), - mStretchX, mStretchY, mStretchMax)); + String posText = + "%d: Position %s, stretch width %f, height %f, vec %f,%f, amountX %f amountY %f mappedBounds %s"; + setText(String.format(posText, + mCurrentCount, mPosition.toShortString(), mWidth, mHeight, + mStretchX, mStretchY, mStretchMaxX, mStretchMaxY, + mMappedBounds.toShortString())); } @Override @@ -143,13 +147,19 @@ public class PositionListenerActivity extends Activity { } @Override - public void applyStretch(long frameNumber, float left, float top, float right, float bottom, - float vecX, float vecY, float maxStretch) { + public void applyStretch(long frameNumber, float width, float height, + float vecX, float vecY, + float maxStretchX, float maxStretchY, float childRelativeLeft, + float childRelativeTop, float childRelativeRight, float childRelativeBottom) { getHandler().postAtFrontOfQueue(() -> { - mStretchArea.set(left, top, right, bottom); + mWidth = width; + mHeight = height; mStretchX = vecX; mStretchY = vecY; - mStretchMax = maxStretch; + mStretchMaxX = maxStretchX; + mStretchMaxY = maxStretchY; + mMappedBounds.set(childRelativeLeft, childRelativeTop, childRelativeRight, + childRelativeBottom); updateText(); }); } diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ScrollingStretchSurfaceViewActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ScrollingStretchSurfaceViewActivity.java new file mode 100644 index 000000000000..040bff5d74d8 --- /dev/null +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ScrollingStretchSurfaceViewActivity.java @@ -0,0 +1,113 @@ +/* + * 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.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.view.SurfaceHolder; +import android.view.SurfaceHolder.Callback; +import android.view.SurfaceView; +import android.widget.FrameLayout; +import android.widget.ImageView; + +public class ScrollingStretchSurfaceViewActivity extends Activity implements Callback { + + SurfaceView mVerticalSurfaceView; + SurfaceView mHorizontalSurfaceView; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.scrolling_stretch_surfaceview); + + mVerticalSurfaceView = new SurfaceView(this); + mVerticalSurfaceView.getHolder().addCallback(this); + + mHorizontalSurfaceView = new SurfaceView(this); + mHorizontalSurfaceView.getHolder().addCallback(this); + + FrameLayout verticalContainer = findViewById(R.id.vertical_surfaceview_container); + verticalContainer.addView(mVerticalSurfaceView, + new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.MATCH_PARENT)); + + FrameLayout horizontalContainer = findViewById(R.id.horizontal_surfaceview_container); + horizontalContainer.addView(mHorizontalSurfaceView, + new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.MATCH_PARENT)); + + ImageView verticalImageView = findViewById(R.id.vertical_imageview); + verticalImageView.setImageDrawable(new LineDrawable()); + + ImageView horizontalImageView = findViewById(R.id.horizontal_imageview); + horizontalImageView.setImageDrawable(new LineDrawable()); + } + + @Override + public void surfaceCreated(SurfaceHolder holder) { + } + + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + Canvas canvas = holder.lockCanvas(); + + drawLine(canvas, width, height); + holder.unlockCanvasAndPost(canvas); + } + + private static void drawLine(Canvas canvas, int width, int height) { + canvas.drawColor(Color.GRAY); + + Paint paint = new Paint(); + paint.setAntiAlias(true); + paint.setColor(Color.GREEN); + paint.setStyle(Paint.Style.STROKE); + paint.setStrokeWidth(10f); + canvas.drawLine(0, 0, width, height, paint); + } + + private static class LineDrawable extends Drawable { + @Override + public void draw(Canvas canvas) { + drawLine(canvas, getBounds().width(), getBounds().height()); + } + + @Override + public void setAlpha(int alpha) { + // NO-OP + } + + @Override + public void setColorFilter(ColorFilter colorFilter) { + // NO-OP + } + + @Override + public int getOpacity() { + return 0; + } + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + } +} diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/StretchShaderActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/StretchShaderActivity.java index ade94a9672bc..3307c36d9d1a 100644 --- a/tests/HwAccelerationTest/src/com/android/test/hwui/StretchShaderActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/StretchShaderActivity.java @@ -409,10 +409,6 @@ public class StretchShaderActivity extends Activity { if (mStretchDistance > 0 && canvas instanceof RecordingCanvas) { Rect bounds = getBounds(); ((RecordingCanvas) canvas).mNode.stretch( - 0, - 0, - bounds.width(), - bounds.height(), mOverScrollX, mOverScrollY, mStretchDistance, diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/StretchySurfaceViewActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/StretchySurfaceViewActivity.java index 67b9be580ba6..acb872cd23b8 100644 --- a/tests/HwAccelerationTest/src/com/android/test/hwui/StretchySurfaceViewActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/StretchySurfaceViewActivity.java @@ -99,7 +99,7 @@ public class StretchySurfaceViewActivity extends Activity implements Callback { super.onDraw(canvas); RenderNode node = ((RecordingCanvas) canvas).mNode; - node.stretch(0f, 0f, getWidth(), getHeight() / 2f, 0f, + node.stretch(0f, 1f, 400f, 400f); } }; |