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
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index ac70dff..4f0e1af 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -146,8 +146,9 @@
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 @@
/**
* @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 7bdf5cf..2fce434 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -1471,10 +1471,13 @@
}
@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 4d2d9e8..9398d38 100644
--- a/core/java/android/widget/EdgeEffect.java
+++ b/core/java/android/widget/EdgeEffect.java
@@ -641,14 +641,10 @@
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 27f82f1..d528428 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -626,12 +626,24 @@
}
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 @@
(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 6fcd8d0..01fd231 100644
--- a/graphics/java/android/graphics/RenderNode.java
+++ b/graphics/java/android/graphics/RenderNode.java
@@ -280,8 +280,10 @@
*
* @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 @@
}
@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 @@
}
/** @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 @@
}
return nStretch(
mNativeRenderNode,
- left,
- top,
- right,
- bottom,
vecX,
vecY,
maxStretchAmountX,
@@ -1701,8 +1698,8 @@
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 f2c48bb..0212309 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -467,6 +467,7 @@
"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 0bf9480..94fe243 100644
--- a/libs/hwui/DamageAccumulator.cpp
+++ b/libs/hwui/DamageAccumulator.cpp
@@ -197,6 +197,27 @@
}
}
+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 @@
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 89ee0e3..90a3517 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 @@
struct DirtyStack;
class RenderNode;
class Matrix4;
-class StretchEffect;
class DamageAccumulator {
PREVENT_COPY_AND_ASSIGN(DamageAccumulator);
@@ -63,7 +63,29 @@
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 e9eae3d..fce2e1f 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -194,6 +194,9 @@
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 @@
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 988141f..6a0b1aa 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 @@
}
}
+ StretchMask& getStretchMask() { return mStretchMask; }
+
VirtualLightRefBase* getUserContext() const { return mUserContext.get(); }
void setUserContext(VirtualLightRefBase* context) { mUserContext = context; }
@@ -286,6 +289,7 @@
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 6eb6e1e..1519d69 100644
--- a/libs/hwui/effects/StretchEffect.cpp
+++ b/libs/hwui/effects/StretchEffect.cpp
@@ -189,17 +189,12 @@
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 @@
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);
+ mBuilder->uniform("viewportWidth").set(&width, 1);
+ mBuilder->uniform("viewportHeight").set(&height, 1);
- mStretchShader = mBuilder->makeShader(nullptr, false);
-
- 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 546d53b..61537f0 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 @@
}
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 @@
*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 fffa806..5131c64 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_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 @@
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 @@
#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 @@
{"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 @@
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 77d99a6..1ae06d0 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 @@
"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 0000000..2bbd8a4
--- /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 0000000..dc698b8
--- /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 a6e4c4c..6777c00 100644
--- a/libs/hwui/pipeline/skia/TransformCanvas.cpp
+++ b/libs/hwui/pipeline/skia/TransformCanvas.cpp
@@ -28,8 +28,8 @@
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 47f77f1..685b71d 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 @@
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 6bf4492..04a55d6 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 0000000..77f5e60
--- /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 6b6287d..2ad034c 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 @@
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 @@
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 @@
}
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 @@
}
@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 0000000..040bff5
--- /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 ade94a9..3307c36 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 @@
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 67b9be5..acb872c 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 @@
super.onDraw(canvas);
RenderNode node = ((RecordingCanvas) canvas).mNode;
- node.stretch(0f, 0f, getWidth(), getHeight() / 2f, 0f,
+ node.stretch(0f,
1f, 400f, 400f);
}
};