Wire up SurfaceView stretch effect

Bug: 179047472
Test: StretchySurfaceViewActivity

Change-Id: I7f3d582cc66fb732a557e9332edc6d186db2335c
This commit is contained in:
John Reck
2021-02-08 19:22:59 -05:00
parent 02e5c21052
commit 5d53a7540a
13 changed files with 353 additions and 7 deletions

View File

@@ -141,6 +141,9 @@ public final class SurfaceControl implements Parcelable {
int layerStack);
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);
private static native boolean nativeClearContentFrameStats(long nativeObject);
private static native boolean nativeGetContentFrameStats(long nativeObject, WindowContentFrameStats outStats);
@@ -2948,6 +2951,17 @@ public final class SurfaceControl implements Parcelable {
return this;
}
/**
* @hide
*/
public Transaction setStretchEffect(SurfaceControl sc, float left, float top, float right,
float bottom, float vecX, float vecY, float maxStretchAmount) {
checkPreconditions(sc);
nativeSetStretchEffect(mNativeObject, sc.mNativeObject, left, top, right, bottom,
vecX, vecY, maxStretchAmount);
return this;
}
/**
* @hide
*/

View File

@@ -1443,6 +1443,14 @@ 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);
applyRtTransaction(frameNumber);
}
@Override
public void positionLost(long frameNumber) {
if (DEBUG) {

View File

@@ -580,6 +580,15 @@ static void nativeSetBlurRegions(JNIEnv* env, jclass clazz, jlong transactionObj
transaction->setBlurRegions(ctrl, blurRegionVector);
}
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) {
auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);
transaction->setStretchEffect(ctrl, left, top, right, bottom, vecX, vecY, maxStretchAmount);
}
static void nativeSetSize(JNIEnv* env, jclass clazz, jlong transactionObj,
jlong nativeObject, jint w, jint h) {
auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
@@ -1754,6 +1763,8 @@ static const JNINativeMethod sSurfaceControlMethods[] = {
(void*)nativeSetLayerStack },
{"nativeSetBlurRegions", "(JJ[[FI)V",
(void*)nativeSetBlurRegions },
{"nativeSetStretchEffect", "(JJFFFFFFF)V",
(void*) nativeSetStretchEffect },
{"nativeSetShadowRadius", "(JJF)V",
(void*)nativeSetShadowRadius },
{"nativeSetFrameRate", "(JJFIZ)V",

View File

@@ -271,6 +271,16 @@ public final class RenderNode {
*/
void positionChanged(long frameNumber, int left, int top, int right, int bottom);
/**
* Call to apply a stretch effect to any child SurfaceControl layers
*
* TODO: Fold this into positionChanged & have HWUI do the ASurfaceControl calls?
*
* @hide
*/
default void applyStretch(long frameNumber, float left, float top, float right,
float bottom, float vecX, float vecY, float maxStretch) { }
/**
* Called by native on RenderThread to notify that the view is no longer in the
* draw tree. UI thread is blocked at this point.
@@ -312,6 +322,14 @@ public final class RenderNode {
pul.positionLost(frameNumber);
}
}
@Override
public void applyStretch(long frameNumber, float left, float top, float right, float bottom,
float vecX, float vecY, float maxStretch) {
for (PositionUpdateListener pul : mListeners) {
pul.applyStretch(frameNumber, left, top, right, bottom, vecX, vecY, maxStretch);
}
}
}
/**
@@ -707,7 +725,7 @@ public final class RenderNode {
if (1.0 < vecY || vecY < -1.0) {
throw new IllegalArgumentException("vecY must be in the range [-1, 1], was " + vecY);
}
if (top <= bottom || right <= left) {
if (top >= bottom || left >= right) {
throw new IllegalArgumentException(
"Stretch region must not be empty, got "
+ new RectF(left, top, right, bottom).toString());

View File

@@ -249,5 +249,20 @@ void DamageAccumulator::finish(SkRect* totalDirty) {
mHead->pendingDirty.setEmpty();
}
const StretchEffect* DamageAccumulator::findNearestStretchEffect() const {
DirtyStack* frame = mHead;
while (frame->prev != frame) {
frame = frame->prev;
if (frame->type == TransformRenderNode) {
const auto& effect =
frame->renderNode->properties().layerProperties().getStretchEffect();
if (!effect.isEmpty()) {
return &effect;
}
}
}
return nullptr;
}
} /* namespace uirenderer */
} /* namespace android */

View File

@@ -35,6 +35,7 @@ namespace uirenderer {
struct DirtyStack;
class RenderNode;
class Matrix4;
class StretchEffect;
class DamageAccumulator {
PREVENT_COPY_AND_ASSIGN(DamageAccumulator);
@@ -62,6 +63,8 @@ public:
void finish(SkRect* totalDirty);
const StretchEffect* findNearestStretchEffect() const;
private:
void pushCommon();
void applyMatrix4Transform(DirtyStack* frame);

View File

@@ -226,6 +226,9 @@ void RenderNode::prepareTreeImpl(TreeObserver& observer, TreeInfo& info, bool fu
if (!mProperties.getAllowForceDark()) {
info.disableForceDark++;
}
if (!mProperties.layerProperties().getStretchEffect().isEmpty()) {
info.stretchEffectCount++;
}
uint32_t animatorDirtyMask = 0;
if (CC_LIKELY(info.runAnimations)) {
@@ -267,6 +270,9 @@ void RenderNode::prepareTreeImpl(TreeObserver& observer, TreeInfo& info, bool fu
if (!mProperties.getAllowForceDark()) {
info.disableForceDark--;
}
if (!mProperties.layerProperties().getStretchEffect().isEmpty()) {
info.stretchEffectCount--;
}
info.damageAccumulator->popTransform();
}

View File

@@ -70,6 +70,7 @@ LayerProperties& LayerProperties::operator=(const LayerProperties& other) {
setXferMode(other.xferMode());
setColorFilter(other.getColorFilter());
setImageFilter(other.getImageFilter());
mStretchEffect = other.mStretchEffect;
return *this;
}

View File

@@ -98,6 +98,8 @@ public:
const SkISize screenSize;
int stretchEffectCount = 0;
struct Out {
bool hasFunctors = false;
// This is only updated if evaluateAnimations is true

View File

@@ -25,6 +25,7 @@
#include <renderthread/CanvasContext.h>
#endif
#include <TreeInfo.h>
#include <effects/StretchEffect.h>
#include <hwui/Paint.h>
#include <utils/TraceUtils.h>
@@ -549,6 +550,7 @@ static void android_view_RenderNode_endAllAnimators(JNIEnv* env, jobject clazz,
// ----------------------------------------------------------------------------
jmethodID gPositionListener_PositionChangedMethod;
jmethodID gPositionListener_ApplyStretchMethod;
jmethodID gPositionListener_PositionLostMethod;
static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject,
@@ -571,6 +573,11 @@ static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject,
Matrix4 transform;
info.damageAccumulator->computeCurrentTransform(&transform);
const RenderProperties& props = node.properties();
if (info.stretchEffectCount) {
handleStretchEffect(info, transform);
}
uirenderer::Rect bounds(props.getWidth(), props.getHeight());
transform.mapRect(bounds);
@@ -613,7 +620,7 @@ static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject,
JNIEnv* env = jnienv();
jobject localref = env->NewLocalRef(mWeakRef);
if (CC_UNLIKELY(!localref)) {
jnienv()->DeleteWeakGlobalRef(mWeakRef);
env->DeleteWeakGlobalRef(mWeakRef);
mWeakRef = nullptr;
return;
}
@@ -634,6 +641,32 @@ static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject,
return env;
}
void handleStretchEffect(const TreeInfo& info, const Matrix4& transform) {
// Search up to find the nearest stretcheffect parent
const StretchEffect* effect = info.damageAccumulator->findNearestStretchEffect();
if (!effect) {
return;
}
uirenderer::Rect area = effect->stretchArea;
transform.mapRect(area);
JNIEnv* env = jnienv();
jobject localref = env->NewLocalRef(mWeakRef);
if (CC_UNLIKELY(!localref)) {
env->DeleteWeakGlobalRef(mWeakRef);
mWeakRef = nullptr;
return;
}
#ifdef __ANDROID__ // Layoutlib does not support CanvasContext
env->CallVoidMethod(localref, gPositionListener_ApplyStretchMethod,
info.canvasContext.getFrameNumber(), area.left, area.top,
area.right, area.bottom, effect->stretchDirection.fX,
effect->stretchDirection.fY, effect->maxStretchAmount);
#endif
env->DeleteLocalRef(localref);
}
void doUpdatePositionAsync(jlong frameNumber, jint left, jint top,
jint right, jint bottom) {
ATRACE_NAME("Update SurfaceView position");
@@ -775,6 +808,8 @@ int register_android_view_RenderNode(JNIEnv* env) {
jclass clazz = FindClassOrDie(env, "android/graphics/RenderNode$PositionUpdateListener");
gPositionListener_PositionChangedMethod = GetMethodIDOrDie(env, clazz,
"positionChanged", "(JIIII)V");
gPositionListener_ApplyStretchMethod =
GetMethodIDOrDie(env, clazz, "applyStretch", "(JFFFFFFF)V");
gPositionListener_PositionLostMethod = GetMethodIDOrDie(env, clazz,
"positionLost", "(J)V");
return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));

View File

@@ -400,6 +400,15 @@
</intent-filter>
</activity>
<activity android:name="StretchySurfaceViewActivity"
android:label="SurfaceView/Stretchy Movement"
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

@@ -19,8 +19,13 @@ package com.android.test.hwui;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.PointF;
import android.graphics.RecordingCanvas;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.RenderNode;
import android.os.Bundle;
import android.view.MotionEvent;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.ScrollView;
@@ -38,7 +43,46 @@ public class PositionListenerActivity extends Activity {
ProgressBar spinner = new ProgressBar(this, null, android.R.attr.progressBarStyleLarge);
layout.addView(spinner);
ScrollView scrollingThing = new ScrollView(this);
ScrollView scrollingThing = new ScrollView(this) {
int setting = 0;
PointF opts[] = new PointF[] {
new PointF(0, 0),
new PointF(0, -1f),
new PointF(1f, 0),
new PointF(0, 1f),
new PointF(-1f, 0),
new PointF(-1f, 1f),
};
{
setWillNotDraw(false);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (ev.getActionMasked() == MotionEvent.ACTION_UP) {
setting = (setting + 1) % opts.length;
invalidate();
}
return super.onTouchEvent(ev);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
RenderNode node = ((RecordingCanvas) canvas).mNode;
PointF dir = opts[setting];
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);
}
if (dir.y != 0f) {
node.stretch(0f, 0f, (float) getWidth(), (float) getHeight(),
0f, dir.y, maxStretchAmount);
}
}
};
scrollingThing.addView(new MyPositionReporter(this));
layout.addView(scrollingThing);
@@ -49,6 +93,11 @@ public class PositionListenerActivity extends Activity {
RenderNode mNode;
int mCurrentCount = 0;
int mTranslateY = 0;
Rect mPosition = new Rect();
RectF mStretchArea = new RectF();
float mStretchX = 0.0f;
float mStretchY = 0.0f;
float mStretchMax = 0.0f;
MyPositionReporter(Context c) {
super(c);
@@ -78,18 +127,36 @@ public class PositionListenerActivity extends Activity {
canvas.drawRenderNode(mNode);
}
void updateText() {
setText(String.format("%d: Position %s, stretch area %s, vec %f,%f, amount %f",
mCurrentCount, mPosition.toShortString(), mStretchArea.toShortString(),
mStretchX, mStretchY, mStretchMax));
}
@Override
public void positionChanged(long frameNumber, int left, int top, int right, int bottom) {
post(() -> {
getHandler().postAtFrontOfQueue(() -> {
mCurrentCount++;
setText(String.format("%d: Position [%d, %d, %d, %d]", mCurrentCount,
left, top, right, bottom));
mPosition.set(left, top, right, bottom);
updateText();
});
}
@Override
public void applyStretch(long frameNumber, float left, float top, float right, float bottom,
float vecX, float vecY, float maxStretch) {
getHandler().postAtFrontOfQueue(() -> {
mStretchArea.set(left, top, right, bottom);
mStretchX = vecX;
mStretchY = vecY;
mStretchMax = maxStretch;
updateText();
});
}
@Override
public void positionLost(long frameNumber) {
post(() -> {
getHandler().postAtFrontOfQueue(() -> {
mCurrentCount++;
setText(mCurrentCount + " No position");
});

View File

@@ -0,0 +1,157 @@
/*
* 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.animation.ObjectAnimator;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RecordingCanvas;
import android.graphics.RenderNode;
import android.os.Bundle;
import android.view.Gravity;
import android.view.SurfaceHolder;
import android.view.SurfaceHolder.Callback;
import android.view.SurfaceView;
import android.view.animation.LinearInterpolator;
import android.widget.FrameLayout;
public class StretchySurfaceViewActivity extends Activity implements Callback {
SurfaceView mSurfaceView;
ObjectAnimator mAnimator;
class MySurfaceView extends SurfaceView {
boolean mSlow;
boolean mScaled;
int mToggle = 0;
public MySurfaceView(Context context) {
super(context);
setOnClickListener(v -> {
mToggle = (mToggle + 1) % 4;
mSlow = (mToggle & 0x2) != 0;
mScaled = (mToggle & 0x1) != 0;
mSurfaceView.setScaleX(mScaled ? 1.6f : 1f);
mSurfaceView.setScaleY(mScaled ? 0.8f : 1f);
setTitle("Slow=" + mSlow + ", scaled=" + mScaled);
invalidate();
});
setWillNotDraw(false);
}
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
if (mSlow) {
try {
Thread.sleep(16);
} catch (InterruptedException e) {}
}
}
public void setMyTranslationY(float ty) {
setTranslationY(ty);
if (mSlow) {
invalidate();
}
}
public float getMyTranslationY() {
return getTranslationY();
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
FrameLayout content = new FrameLayout(this) {
{
setWillNotDraw(false);
}
@Override
protected void onDraw(Canvas canvas) {
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(10f);
canvas.drawLine(0f, 0f, getWidth(), getHeight(), paint);
super.onDraw(canvas);
RenderNode node = ((RecordingCanvas) canvas).mNode;
node.stretch(0f, 0f, getWidth(), getHeight() / 2f, 0f, 1f, 400f);
}
};
mSurfaceView = new MySurfaceView(this);
mSurfaceView.getHolder().addCallback(this);
final float density = getResources().getDisplayMetrics().density;
int size = (int) (200 * density);
content.addView(mSurfaceView, new FrameLayout.LayoutParams(
size, size, Gravity.CENTER_HORIZONTAL | Gravity.TOP));
mAnimator = ObjectAnimator.ofFloat(mSurfaceView, "myTranslationY",
0, size);
mAnimator.setRepeatMode(ObjectAnimator.REVERSE);
mAnimator.setRepeatCount(ObjectAnimator.INFINITE);
mAnimator.setDuration(1000);
mAnimator.setInterpolator(new LinearInterpolator());
setContentView(content);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
Canvas canvas = holder.lockCanvas();
canvas.drawColor(Color.WHITE);
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);
holder.unlockCanvasAndPost(canvas);
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
@Override
protected void onResume() {
super.onResume();
mAnimator.start();
}
@Override
protected void onPause() {
mAnimator.pause();
super.onPause();
}
}