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
This commit is contained in:
Nader Jawad
2021-04-19 19:45:13 -07:00
parent 9443a3e84d
commit 197743ff9c
24 changed files with 557 additions and 121 deletions

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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 {

View File

@@ -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 },

View File

@@ -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);

View File

@@ -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",

View File

@@ -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 */

View File

@@ -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();

View File

@@ -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

View File

@@ -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:

View File

@@ -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);
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() {

View File

@@ -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

View File

@@ -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));

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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;
};
}

View File

@@ -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);
}
}

View File

@@ -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;
};

View File

@@ -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">

View File

@@ -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>

View File

@@ -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();
});
}

View File

@@ -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) {
}
}

View File

@@ -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,

View File

@@ -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);
}
};