Merge "Refactor RuntimeShader to support easier assignment of uniforms."

This commit is contained in:
TreeHugger Robot
2021-01-15 20:16:55 +00:00
committed by Android (Google) Code Review
14 changed files with 335 additions and 127 deletions

View File

@@ -220,7 +220,7 @@ public final class RecordingCanvas extends BaseRecordingCanvas {
CanvasProperty<Float> progress, RuntimeShader shader) {
nDrawRipple(mNativeCanvasWrapper, cx.getNativeContainer(), cy.getNativeContainer(),
radius.getNativeContainer(), paint.getNativeContainer(),
progress.getNativeContainer(), shader.getNativeShaderFactory());
progress.getNativeContainer(), shader.getNativeShaderBuilder());
}
/**

View File

@@ -17,7 +17,6 @@
package android.graphics;
import android.annotation.NonNull;
import android.annotation.Nullable;
import libcore.util.NativeAllocationRegistry;
@@ -33,14 +32,12 @@ public class RuntimeShader extends Shader {
RuntimeShader.class.getClassLoader(), nativeGetFinalizer());
}
private byte[] mUniforms;
private Shader[] mInputShaders;
private boolean mIsOpaque;
/**
* Current native shader factory instance.
* Current native shader builder instance.
*/
private long mNativeInstanceRuntimeShaderFactory;
private long mNativeInstanceRuntimeShaderBuilder;
/**
* Creates a new RuntimeShader.
@@ -50,80 +47,86 @@ public class RuntimeShader extends Shader {
* on number of uniforms declared by sksl.
* @param isOpaque True if all pixels have alpha 1.0f.
*/
public RuntimeShader(@NonNull String sksl, @Nullable byte[] uniforms, boolean isOpaque) {
this(sksl, uniforms, null, isOpaque, ColorSpace.get(ColorSpace.Named.SRGB));
}
/**
* Creates a new RuntimeShader.
*
* @param sksl The text of SKSL program to run on the GPU.
* @param uniforms Array of parameters passed by the SKSL shader. Array size depends
* on number of uniforms declared by sksl.
* @param shaderInputs Array of shaders passed to the SKSL shader. Array size depends
* on the number of input shaders declared in the sksl
* @param isOpaque True if all pixels have alpha 1.0f.
*/
public RuntimeShader(@NonNull String sksl, @Nullable byte[] uniforms,
@Nullable Shader[] shaderInputs, boolean isOpaque) {
this(sksl, uniforms, shaderInputs, isOpaque, ColorSpace.get(ColorSpace.Named.SRGB));
}
private RuntimeShader(@NonNull String sksl, @Nullable byte[] uniforms,
@Nullable Shader[] shaderInputs, boolean isOpaque,
ColorSpace colorSpace) {
super(colorSpace);
mUniforms = uniforms;
mInputShaders = shaderInputs;
public RuntimeShader(@NonNull String sksl, boolean isOpaque) {
super(ColorSpace.get(ColorSpace.Named.SRGB));
mIsOpaque = isOpaque;
mNativeInstanceRuntimeShaderFactory = nativeCreateShaderFactory(sksl);
NoImagePreloadHolder.sRegistry.registerNativeAllocation(this,
mNativeInstanceRuntimeShaderFactory);
mNativeInstanceRuntimeShaderBuilder = nativeCreateBuilder(sksl);
NoImagePreloadHolder.sRegistry.registerNativeAllocation(
this, mNativeInstanceRuntimeShaderBuilder);
}
/**
* Sets new value for shader parameters.
* Sets the uniform value corresponding to this shader. If the shader does not have a uniform
* with that name or if the uniform is declared with a type other than float then an
* IllegalArgumentException is thrown.
*
* @param uniforms Array of parameters passed by the SKSL shader. Array size depends
* on number of uniforms declared by mSksl.
* @param uniformName name matching the uniform declared in the SKSL shader
* @param value
*/
public void updateUniforms(@Nullable byte[] uniforms) {
mUniforms = uniforms;
public void setUniform(@NonNull String uniformName, float value) {
setUniform(uniformName, new float[] {value});
}
/**
* Sets the uniform value corresponding to this shader. If the shader does not have a uniform
* with that name or if the uniform is declared with a type other than float2/vec2 then an
* IllegalArgumentException is thrown.
*
* @param uniformName name matching the uniform declared in the SKSL shader
* @param value1
* @param value2
*/
public void setUniform(@NonNull String uniformName, float value1, float value2) {
setUniform(uniformName, new float[] {value1, value2});
}
/**
* Sets the uniform value corresponding to this shader. If the shader does not have a uniform
* with that name or if the uniform is declared with a type other than a vecN/floatN where N is
* the size of the values array then an IllegalArgumentException is thrown.
*
* @param uniformName name matching the uniform declared in the SKSL shader
* @param values
*/
public void setUniform(@NonNull String uniformName, float[] values) {
nativeUpdateUniforms(mNativeInstanceRuntimeShaderBuilder, uniformName, values);
discardNativeInstance();
}
/**
* Sets new values for the shaders that serve as inputs to this shader.
* Sets the uniform shader that is declares as input to this shader. If the shader does not
* have a uniform shader with that name then an IllegalArgumentException is thrown.
*
* @param shaderInputs Array of Shaders passed into the SKSL shader. Array size depends
* on number of input shaders declared by sksl.
* @param shaderName name matching the uniform declared in the SKSL shader
* @param shader shader passed into the SKSL shader for sampling
*/
public void updateInputShaders(@Nullable Shader[] shaderInputs) {
mInputShaders = shaderInputs;
public void setInputShader(@NonNull String shaderName, @NonNull Shader shader) {
nativeUpdateShader(
mNativeInstanceRuntimeShaderBuilder, shaderName, shader.getNativeInstance());
discardNativeInstance();
}
/** @hide */
@Override
protected long createNativeInstance(long nativeMatrix, boolean filterFromPaint) {
long[] nativeShaders = mInputShaders.length > 0 ? new long[mInputShaders.length] : null;
for (int i = 0; i < mInputShaders.length; i++) {
nativeShaders[i] = mInputShaders[i].getNativeInstance(filterFromPaint);
}
return nativeCreate(mNativeInstanceRuntimeShaderFactory, nativeMatrix, mUniforms,
nativeShaders, colorSpace().getNativeInstance(), mIsOpaque);
return nativeCreateShader(mNativeInstanceRuntimeShaderBuilder, nativeMatrix, mIsOpaque);
}
public long getNativeShaderFactory() {
return mNativeInstanceRuntimeShaderFactory;
public long getNativeShaderBuilder() {
return mNativeInstanceRuntimeShaderBuilder;
}
private static native long nativeCreate(long shaderFactory, long matrix, byte[] inputs,
long[] shaderInputs, long colorSpaceHandle, boolean isOpaque);
private static native long nativeCreateShaderFactory(String sksl);
public boolean isOpaque() {
return mIsOpaque;
}
private static native long nativeGetFinalizer();
private static native long nativeCreateBuilder(String sksl);
private static native long nativeCreateShader(
long shaderBuilder, long matrix, boolean isOpaque);
private static native void nativeUpdateUniforms(
long shaderBuilder, String uniformName, float[] uniforms);
private static native void nativeUpdateShader(
long shaderBuilder, String shaderName, long shader);
}

View File

@@ -819,10 +819,10 @@ void SkiaCanvas::drawRipple(uirenderer::CanvasPropertyPrimitive* x,
uirenderer::CanvasPropertyPrimitive* radius,
uirenderer::CanvasPropertyPaint* paint,
uirenderer::CanvasPropertyPrimitive* progress,
sk_sp<SkRuntimeEffect> runtimeEffect) {
const SkRuntimeShaderBuilder& effectBuilder) {
sk_sp<uirenderer::skiapipeline::AnimatedRipple> drawable(
new uirenderer::skiapipeline::AnimatedRipple(x, y, radius, paint, progress,
runtimeEffect));
effectBuilder));
mCanvas->drawDrawable(drawable.get());
}

View File

@@ -152,7 +152,7 @@ public:
uirenderer::CanvasPropertyPrimitive* radius,
uirenderer::CanvasPropertyPaint* paint,
uirenderer::CanvasPropertyPrimitive* progress,
sk_sp<SkRuntimeEffect> runtimeEffect) override;
const SkRuntimeShaderBuilder& effectBuilder) override;
virtual void drawLayer(uirenderer::DeferredLayerUpdater* layerHandle) override;
virtual void drawRenderNode(uirenderer::RenderNode* renderNode) override;

View File

@@ -161,7 +161,7 @@ struct CanvasOp<CanvasOpType::DrawRippleProperty> {
}
SkRuntimeShaderBuilder::BuilderUniform radiusU =
runtimeEffectBuilder.uniform("in_maxRadius");
runtimeEffectBuilder.uniform("in_radius");
if (radiusU.fVar != nullptr) {
radiusU = radius->value;
}

View File

@@ -31,7 +31,7 @@
class SkAnimatedImage;
class SkCanvasState;
class SkRuntimeEffect;
class SkRuntimeShaderBuilder;
class SkVertices;
namespace minikin {
@@ -139,7 +139,7 @@ public:
uirenderer::CanvasPropertyPrimitive* radius,
uirenderer::CanvasPropertyPaint* paint,
uirenderer::CanvasPropertyPrimitive* progress,
sk_sp<SkRuntimeEffect> runtimeEffect) = 0;
const SkRuntimeShaderBuilder& effectBuilder) = 0;
virtual void drawLayer(uirenderer::DeferredLayerUpdater* layerHandle) = 0;
virtual void drawRenderNode(uirenderer::RenderNode* renderNode) = 0;

View File

@@ -1,3 +1,6 @@
#undef LOG_TAG
#define LOG_TAG "ShaderJNI"
#include "GraphicsJNI.h"
#include "SkColorFilter.h"
#include "SkGradientShader.h"
@@ -232,53 +235,72 @@ static jlong ComposeShader_create(JNIEnv* env, jobject o, jlong matrixPtr,
///////////////////////////////////////////////////////////////////////////////////////////////
static jlong RuntimeShader_create(JNIEnv* env, jobject, jlong shaderFactory, jlong matrixPtr,
jbyteArray inputs, jlongArray inputShaders, jlong colorSpaceHandle, jboolean isOpaque) {
SkRuntimeEffect* effect = reinterpret_cast<SkRuntimeEffect*>(shaderFactory);
AutoJavaByteArray arInputs(env, inputs);
std::vector<sk_sp<SkShader>> shaderVector;
if (inputShaders) {
jsize shaderCount = env->GetArrayLength(inputShaders);
shaderVector.resize(shaderCount);
jlong* arrayPtr = env->GetLongArrayElements(inputShaders, NULL);
for (int i = 0; i < shaderCount; i++) {
shaderVector[i] = sk_ref_sp(reinterpret_cast<SkShader*>(arrayPtr[i]));
}
env->ReleaseLongArrayElements(inputShaders, arrayPtr, 0);
}
sk_sp<SkData> fData;
fData = SkData::MakeWithCopy(arInputs.ptr(), arInputs.length());
const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
sk_sp<SkShader> shader = effect->makeShader(fData, shaderVector.data(), shaderVector.size(),
matrix, isOpaque == JNI_TRUE);
ThrowIAE_IfNull(env, shader);
return reinterpret_cast<jlong>(shader.release());
}
///////////////////////////////////////////////////////////////////////////////////////////////
static jlong RuntimeShader_createShaderFactory(JNIEnv* env, jobject, jstring sksl) {
static jlong RuntimeShader_createShaderBuilder(JNIEnv* env, jobject, jstring sksl) {
ScopedUtfChars strSksl(env, sksl);
auto result = SkRuntimeEffect::Make(SkString(strSksl.c_str()));
sk_sp<SkRuntimeEffect> effect = std::get<0>(result);
if (!effect) {
if (effect.get() == nullptr) {
const auto& err = std::get<1>(result);
doThrowIAE(env, err.c_str());
return 0;
}
return reinterpret_cast<jlong>(effect.release());
return reinterpret_cast<jlong>(new SkRuntimeShaderBuilder(std::move(effect)));
}
///////////////////////////////////////////////////////////////////////////////////////////////
static void Effect_safeUnref(SkRuntimeEffect* effect) {
SkSafeUnref(effect);
static void SkRuntimeShaderBuilder_delete(SkRuntimeShaderBuilder* builder) {
delete builder;
}
static jlong RuntimeShader_getNativeFinalizer(JNIEnv*, jobject) {
return static_cast<jlong>(reinterpret_cast<uintptr_t>(&Effect_safeUnref));
return static_cast<jlong>(reinterpret_cast<uintptr_t>(&SkRuntimeShaderBuilder_delete));
}
static jlong RuntimeShader_create(JNIEnv* env, jobject, jlong shaderBuilder, jlong matrixPtr,
jboolean isOpaque) {
SkRuntimeShaderBuilder* builder = reinterpret_cast<SkRuntimeShaderBuilder*>(shaderBuilder);
const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
sk_sp<SkShader> shader = builder->makeShader(matrix, isOpaque == JNI_TRUE);
ThrowIAE_IfNull(env, shader);
return reinterpret_cast<jlong>(shader.release());
}
static inline int ThrowIAEFmt(JNIEnv* env, const char* fmt, ...) {
va_list args;
va_start(args, fmt);
return jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", fmt, args);
va_end(args);
}
static void RuntimeShader_updateUniforms(JNIEnv* env, jobject, jlong shaderBuilder,
jstring jUniformName, jfloatArray jvalues) {
SkRuntimeShaderBuilder* builder = reinterpret_cast<SkRuntimeShaderBuilder*>(shaderBuilder);
ScopedUtfChars name(env, jUniformName);
AutoJavaFloatArray autoValues(env, jvalues, 0, kRO_JNIAccess);
SkRuntimeShaderBuilder::BuilderUniform uniform = builder->uniform(name.c_str());
if (uniform.fVar == nullptr) {
ThrowIAEFmt(env, "unable to find uniform named %s", name.c_str());
} else if (!uniform.set<float>(autoValues.ptr(), autoValues.length())) {
ThrowIAEFmt(env, "mismatch in byte size for uniform [expected: %zu actual: %zu]",
uniform.fVar->sizeInBytes(), sizeof(float) * autoValues.length());
}
}
static void RuntimeShader_updateShader(JNIEnv* env, jobject, jlong shaderBuilder,
jstring jUniformName, jlong shaderHandle) {
SkRuntimeShaderBuilder* builder = reinterpret_cast<SkRuntimeShaderBuilder*>(shaderBuilder);
ScopedUtfChars name(env, jUniformName);
SkShader* shader = reinterpret_cast<SkShader*>(shaderHandle);
SkRuntimeShaderBuilder::BuilderChild child = builder->child(name.c_str());
if (child.fIndex == -1) {
ThrowIAEFmt(env, "unable to find shader named %s", name.c_str());
return;
}
builder->child(name.c_str()) = sk_ref_sp(shader);
}
///////////////////////////////////////////////////////////////////////////////////////////////
@@ -313,10 +335,11 @@ static const JNINativeMethod gComposeShaderMethods[] = {
};
static const JNINativeMethod gRuntimeShaderMethods[] = {
{ "nativeGetFinalizer", "()J", (void*)RuntimeShader_getNativeFinalizer },
{ "nativeCreate", "(JJ[B[JJZ)J", (void*)RuntimeShader_create },
{ "nativeCreateShaderFactory", "(Ljava/lang/String;)J",
(void*)RuntimeShader_createShaderFactory },
{"nativeGetFinalizer", "()J", (void*)RuntimeShader_getNativeFinalizer},
{"nativeCreateShader", "(JJZ)J", (void*)RuntimeShader_create},
{"nativeCreateBuilder", "(Ljava/lang/String;)J", (void*)RuntimeShader_createShaderBuilder},
{"nativeUpdateUniforms", "(JLjava/lang/String;[F)V", (void*)RuntimeShader_updateUniforms},
{"nativeUpdateShader", "(JLjava/lang/String;J)V", (void*)RuntimeShader_updateShader},
};
int register_android_graphics_Shader(JNIEnv* env)

View File

@@ -144,7 +144,8 @@ static void android_view_DisplayListCanvas_drawCircleProps(CRITICAL_JNI_PARAMS_C
static void android_view_DisplayListCanvas_drawRippleProps(CRITICAL_JNI_PARAMS_COMMA jlong canvasPtr,
jlong xPropPtr, jlong yPropPtr,
jlong radiusPropPtr, jlong paintPropPtr,
jlong progressPropPtr, jlong effectPtr) {
jlong progressPropPtr,
jlong builderPtr) {
Canvas* canvas = reinterpret_cast<Canvas*>(canvasPtr);
CanvasPropertyPrimitive* xProp = reinterpret_cast<CanvasPropertyPrimitive*>(xPropPtr);
CanvasPropertyPrimitive* yProp = reinterpret_cast<CanvasPropertyPrimitive*>(yPropPtr);
@@ -152,8 +153,8 @@ static void android_view_DisplayListCanvas_drawRippleProps(CRITICAL_JNI_PARAMS_C
CanvasPropertyPaint* paintProp = reinterpret_cast<CanvasPropertyPaint*>(paintPropPtr);
CanvasPropertyPrimitive* progressProp =
reinterpret_cast<CanvasPropertyPrimitive*>(progressPropPtr);
SkRuntimeEffect* effect = reinterpret_cast<SkRuntimeEffect*>(effectPtr);
canvas->drawRipple(xProp, yProp, radiusProp, paintProp, progressProp, sk_ref_sp(effect));
SkRuntimeShaderBuilder* builder = reinterpret_cast<SkRuntimeShaderBuilder*>(builderPtr);
canvas->drawRipple(xProp, yProp, radiusProp, paintProp, progressProp, *builder);
}
static void android_view_DisplayListCanvas_drawWebViewFunctor(CRITICAL_JNI_PARAMS_COMMA jlong canvasPtr, jint functor) {

View File

@@ -61,13 +61,13 @@ public:
uirenderer::CanvasPropertyPrimitive* radius,
uirenderer::CanvasPropertyPaint* paint,
uirenderer::CanvasPropertyPrimitive* progress,
sk_sp<SkRuntimeEffect> runtimeEffect)
const SkRuntimeShaderBuilder& effectBuilder)
: mX(x)
, mY(y)
, mRadius(radius)
, mPaint(paint)
, mProgress(progress)
, mRuntimeEffectBuilder(std::move(runtimeEffect)) {}
, mRuntimeEffectBuilder(effectBuilder) {}
protected:
virtual SkRect onGetBounds() override {
@@ -83,7 +83,7 @@ protected:
}
SkRuntimeShaderBuilder::BuilderUniform radiusU =
mRuntimeEffectBuilder.uniform("in_maxRadius");
mRuntimeEffectBuilder.uniform("in_radius");
if (radiusU.fVar != nullptr) {
radiusU = mRadius->value;
}

View File

@@ -90,9 +90,9 @@ void SkiaRecordingCanvas::drawRipple(uirenderer::CanvasPropertyPrimitive* x,
uirenderer::CanvasPropertyPrimitive* radius,
uirenderer::CanvasPropertyPaint* paint,
uirenderer::CanvasPropertyPrimitive* progress,
sk_sp<SkRuntimeEffect> runtimeEffect) {
const SkRuntimeShaderBuilder& effectBuilder) {
drawDrawable(mDisplayList->allocateDrawable<AnimatedRipple>(x, y, radius, paint, progress,
runtimeEffect));
effectBuilder));
}
void SkiaRecordingCanvas::enableZ(bool enableZ) {

View File

@@ -71,7 +71,7 @@ public:
uirenderer::CanvasPropertyPrimitive* radius,
uirenderer::CanvasPropertyPaint* paint,
uirenderer::CanvasPropertyPrimitive* progress,
sk_sp<SkRuntimeEffect> runtimeEffect) override;
const SkRuntimeShaderBuilder& effectBuilder) override;
virtual void drawVectorDrawable(VectorDrawableRoot* vectorDrawable) override;

View File

@@ -1016,6 +1016,15 @@
</intent-filter>
</activity>
<activity android:name="RippleActivity"
android:label="Animation/Ripple Animation"
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="MultiProducerActivity"
android:label="Threads/Multiple Producers"
android:exported="true">

View File

@@ -35,9 +35,6 @@ import android.graphics.Shader;
import android.os.Bundle;
import android.view.View;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@SuppressWarnings({"UnusedDeclaration"})
public class ColorFiltersMutateActivity extends Activity {
@Override
@@ -54,11 +51,13 @@ public class ColorFiltersMutateActivity extends Activity {
private final Paint mLightingPaint;
private final Paint mBlendPaint;
private final Paint mShaderPaint;
private final RuntimeShader mRuntimeShader;
private float mSaturation = 0.0f;
private int mLightAdd = 0;
private int mLightMul = 0;
private int mPorterDuffColor = 0;
private float mShaderParam1 = 0.0f;
static final String sSkSL =
"in shader bitmapShader;\n"
@@ -67,8 +66,6 @@ public class ColorFiltersMutateActivity extends Activity {
+ " return half4(sample(bitmapShader, xy).rgb, param1);\n"
+ "}\n";
private byte[] mUniforms = new byte[4];
BitmapsView(Context c) {
super(c);
@@ -86,11 +83,13 @@ public class ColorFiltersMutateActivity extends Activity {
mBlendPaint = new Paint();
mBlendPaint.setColorFilter(new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_OVER));
mRuntimeShader = new RuntimeShader(sSkSL, false);
mRuntimeShader.setUniform("param1", mShaderParam1);
mRuntimeShader.setInputShader("bitmapShader", new BitmapShader(mBitmap1,
Shader.TileMode.CLAMP,
Shader.TileMode.CLAMP));
mShaderPaint = new Paint();
Shader[] inputShaders = { new BitmapShader(mBitmap1, Shader.TileMode.CLAMP,
Shader.TileMode.CLAMP) };
mShaderPaint.setShader(new RuntimeShader(sSkSL, mUniforms, inputShaders, true));
setShaderParam1(0.0f);
mShaderPaint.setShader(mRuntimeShader);
ObjectAnimator sat = ObjectAnimator.ofFloat(this, "saturation", 1.0f);
sat.setDuration(1000);
@@ -177,20 +176,15 @@ public class ColorFiltersMutateActivity extends Activity {
}
public void setShaderParam1(float value) {
RuntimeShader shader = (RuntimeShader) mShaderPaint.getShader();
ByteBuffer buffer = ByteBuffer.wrap(mUniforms);
buffer.order(ByteOrder.LITTLE_ENDIAN);
buffer.putFloat(value);
shader.updateUniforms(mUniforms);
mShaderParam1 = value;
mRuntimeShader.setUniform("param1", mShaderParam1);
invalidate();
}
// If either valueFrom or valueTo is null, then a getter function will also be derived
// and called by the animator class.
public float getShaderParam1() {
ByteBuffer buffer = ByteBuffer.wrap(mUniforms);
buffer.order(ByteOrder.LITTLE_ENDIAN);
return buffer.getFloat();
return mShaderParam1;
}
@Override

View File

@@ -0,0 +1,178 @@
/*
* 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.content.Context;
import android.graphics.Canvas;
import android.graphics.CanvasProperty;
import android.graphics.Paint;
import android.graphics.RecordingCanvas;
import android.graphics.RuntimeShader;
import android.os.Bundle;
import android.os.Trace;
import android.view.RenderNodeAnimator;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;
import java.util.ArrayList;
public class RippleActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final LinearLayout layout = new LinearLayout(this);
layout.setOrientation(LinearLayout.VERTICAL);
layout.addView(new RippleView(this),
new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
setContentView(layout);
}
static class RippleView extends View {
static final int DURATION = 1000;
static final int MAX_RADIUS = 250;
private boolean mToggle = false;
ArrayList<RenderNodeAnimator> mRunningAnimations = new ArrayList<RenderNodeAnimator>();
CanvasProperty<Float> mX;
CanvasProperty<Float> mY;
CanvasProperty<Float> mRadius;
CanvasProperty<Float> mProgress;
CanvasProperty<Paint> mPaint;
RuntimeShader mRuntimeShader;
static final String sSkSL = ""
+ "uniform float2 in_origin;"
+ "uniform float in_progress;\n"
+ "uniform float in_maxRadius;\n"
+ "uniform shader in_paintColor;\n"
+ "float dist2(float2 p0, float2 pf) { return sqrt((pf.x - p0.x) * (pf.x - p0.x) + "
+ "(pf.y - p0.y) * (pf.y - p0.y)); }\n"
+ "float mod2(float a, float b) { return a - (b * floor(a / b)); }\n"
+ "float rand(float2 src) { return fract(sin(dot(src.xy, float2(12.9898, 78.233)))"
+ " * 43758.5453123); }\n"
+ "float4 main(float2 p)\n"
+ "{\n"
+ " float fraction = in_progress;\n"
+ " float2 fragCoord = p;//sk_FragCoord.xy;\n"
+ " float maxDist = in_maxRadius;\n"
+ " float fragDist = dist2(in_origin, fragCoord.xy);\n"
+ " float circleRadius = maxDist * fraction;\n"
+ " float colorVal = (fragDist - circleRadius) / maxDist;\n"
+ " float d = fragDist < circleRadius \n"
+ " ? 1. - abs(colorVal * 2. * smoothstep(0., 1., fraction)) \n"
+ " : 1. - abs(colorVal * 3.);\n"
+ " d = smoothstep(0., 1., d);\n"
+ " float divider = 2.;\n"
+ " float x = floor(fragCoord.x / divider);\n"
+ " float y = floor(fragCoord.y / divider);\n"
+ " float density = .95;\n"
+ " d = rand(float2(x, y)) > density ? d : d * .2;\n"
+ " d = d * rand(float2(fraction, x * y));\n"
+ " float alpha = 1. - pow(fraction, 3.);\n"
+ " return float4(sample(in_paintColor).rgb, d * alpha);\n"
+ "}";
RippleView(Context c) {
super(c);
setClickable(true);
mX = CanvasProperty.createFloat(200.0f);
mY = CanvasProperty.createFloat(200.0f);
mRadius = CanvasProperty.createFloat(150.0f);
mProgress = CanvasProperty.createFloat(0.0f);
Paint p = new Paint();
p.setAntiAlias(true);
p.setColor(0xFFFF0000);
mPaint = CanvasProperty.createPaint(p);
mRuntimeShader = new RuntimeShader(sSkSL, false);
mRuntimeShader.setUniform("in_maxRadius", MAX_RADIUS);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (canvas.isHardwareAccelerated()) {
RecordingCanvas recordingCanvas = (RecordingCanvas) canvas;
recordingCanvas.drawRipple(mX, mY, mRadius, mPaint, mProgress, mRuntimeShader);
}
}
@Override
public boolean performClick() {
for (int i = 0; i < mRunningAnimations.size(); i++) {
mRunningAnimations.get(i).cancel();
}
mRunningAnimations.clear();
mToggle = !mToggle;
mRunningAnimations.add(new RenderNodeAnimator(
mX, mToggle ? 400.0f : 200.0f));
mRunningAnimations.add(new RenderNodeAnimator(
mY, mToggle ? 600.0f : 200.0f));
mRunningAnimations.add(new RenderNodeAnimator(
mRadius, mToggle ? MAX_RADIUS : 150.0f));
mRunningAnimations.add(new RenderNodeAnimator(
mProgress, mToggle ? 1.0f : 0.0f));
mRunningAnimations.add(new RenderNodeAnimator(
mPaint, RenderNodeAnimator.PAINT_ALPHA, 64.0f));
// Will be "chained" to run after the above
mRunningAnimations.add(new RenderNodeAnimator(
mPaint, RenderNodeAnimator.PAINT_ALPHA, 255.0f));
for (int i = 0; i < mRunningAnimations.size(); i++) {
RenderNodeAnimator anim = mRunningAnimations.get(i);
anim.setDuration(DURATION);
anim.setTarget(this);
if (i == (mRunningAnimations.size() - 1)) {
// "chain" test
anim.setStartValue(64.0f);
anim.setStartDelay(anim.getDuration());
}
anim.start();
}
if (mToggle) {
post(new Runnable() {
@Override
public void run() {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "pretendBusy");
try {
Thread.sleep(DURATION);
} catch (InterruptedException e) {
}
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
});
}
return true;
}
}
}