Merge "Add RuntimeShader API to RenderEffect"

This commit is contained in:
Derek Sollenberger
2022-01-04 14:03:42 +00:00
committed by Android (Google) Code Review
11 changed files with 521 additions and 9 deletions

View File

@@ -16525,6 +16525,7 @@ package android.graphics {
method @NonNull public static android.graphics.RenderEffect createColorFilterEffect(@NonNull android.graphics.ColorFilter);
method @NonNull public static android.graphics.RenderEffect createOffsetEffect(float, float);
method @NonNull public static android.graphics.RenderEffect createOffsetEffect(float, float, @NonNull android.graphics.RenderEffect);
method @NonNull public static android.graphics.RenderEffect createRuntimeShaderEffect(@NonNull android.graphics.RuntimeShader, @NonNull String);
method @NonNull public static android.graphics.RenderEffect createShaderEffect(@NonNull android.graphics.Shader);
}

View File

@@ -290,6 +290,22 @@ public final class RenderEffect {
return new RenderEffect(nativeCreateShaderEffect(shader.getNativeInstance()));
}
/**
* Create a {@link RenderEffect} that executes the provided {@link RuntimeShader} and passes
* the contents of the {@link android.graphics.RenderNode} that this RenderEffect is installed
* on as an input to the shader.
* @param shader the runtime shader that will bind the inputShaderName to the RenderEffect input
* @param uniformShaderName the uniform name defined in the RuntimeShader's program to which
* the contents of the RenderNode will be bound
*/
@NonNull
public static RenderEffect createRuntimeShaderEffect(
@NonNull RuntimeShader shader, @NonNull String uniformShaderName) {
return new RenderEffect(
nativeCreateRuntimeShaderEffect(shader.getNativeShaderBuilder(),
uniformShaderName));
}
private final long mNativeRenderEffect;
/* only constructed from static factory methods */
@@ -318,5 +334,7 @@ public final class RenderEffect {
private static native long nativeCreateBlendModeEffect(long dst, long src, int blendmode);
private static native long nativeCreateChainEffect(long outer, long inner);
private static native long nativeCreateShaderEffect(long shader);
private static native long nativeCreateRuntimeShaderEffect(
long shaderBuilder, String inputShaderName);
private static native long nativeGetFinalizer();
}

View File

@@ -127,6 +127,32 @@ static jlong createShaderEffect(
return reinterpret_cast<jlong>(shaderFilter.release());
}
static inline int ThrowIAEFmt(JNIEnv* env, const char* fmt, ...) {
va_list args;
va_start(args, fmt);
int ret = jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", fmt, args);
va_end(args);
return ret;
}
static jlong createRuntimeShaderEffect(JNIEnv* env, jobject, jlong shaderBuilderHandle,
jstring inputShaderName) {
SkRuntimeShaderBuilder* builder =
reinterpret_cast<SkRuntimeShaderBuilder*>(shaderBuilderHandle);
ScopedUtfChars name(env, inputShaderName);
if (builder->child(name.c_str()).fChild == nullptr) {
ThrowIAEFmt(env,
"unable to find a uniform with the name '%s' of the correct "
"type defined by the provided RuntimeShader",
name.c_str());
return 0;
}
sk_sp<SkImageFilter> filter = SkImageFilters::RuntimeShader(*builder, name.c_str(), nullptr);
return reinterpret_cast<jlong>(filter.release());
}
static void RenderEffect_safeUnref(SkImageFilter* filter) {
SkSafeUnref(filter);
}
@@ -136,15 +162,16 @@ static jlong getRenderEffectFinalizer(JNIEnv*, jobject) {
}
static const JNINativeMethod gRenderEffectMethods[] = {
{"nativeGetFinalizer", "()J", (void*)getRenderEffectFinalizer},
{"nativeCreateOffsetEffect", "(FFJ)J", (void*)createOffsetEffect},
{"nativeCreateBlurEffect", "(FFJI)J", (void*)createBlurEffect},
{"nativeCreateBitmapEffect", "(JFFFFFFFF)J", (void*)createBitmapEffect},
{"nativeCreateColorFilterEffect", "(JJ)J", (void*)createColorFilterEffect},
{"nativeCreateBlendModeEffect", "(JJI)J", (void*)createBlendModeEffect},
{"nativeCreateChainEffect", "(JJ)J", (void*)createChainEffect},
{"nativeCreateShaderEffect", "(J)J", (void*)createShaderEffect}
};
{"nativeGetFinalizer", "()J", (void*)getRenderEffectFinalizer},
{"nativeCreateOffsetEffect", "(FFJ)J", (void*)createOffsetEffect},
{"nativeCreateBlurEffect", "(FFJI)J", (void*)createBlurEffect},
{"nativeCreateBitmapEffect", "(JFFFFFFFF)J", (void*)createBitmapEffect},
{"nativeCreateColorFilterEffect", "(JJ)J", (void*)createColorFilterEffect},
{"nativeCreateBlendModeEffect", "(JJI)J", (void*)createBlendModeEffect},
{"nativeCreateChainEffect", "(JJ)J", (void*)createChainEffect},
{"nativeCreateShaderEffect", "(J)J", (void*)createShaderEffect},
{"nativeCreateRuntimeShaderEffect", "(JLjava/lang/String;)J",
(void*)createRuntimeShaderEffect}};
int register_android_graphics_RenderEffect(JNIEnv* env) {
android::RegisterMethodsOrDie(env, "android/graphics/RenderEffect",

View File

@@ -32,6 +32,10 @@ android_test {
"**/*.java",
"**/*.kt",
],
static_libs: [
"androidx.cardview_cardview",
],
platform_apis: true,
certificate: "platform",
}

View File

@@ -789,6 +789,15 @@
</intent-filter>
</activity>
<activity android:name="RenderEffectViewActivity"
android:label="RenderEffect/View"
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="StretchShaderActivity"
android:label="RenderEffect/Stretch"
android:exported="true">

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 690 KiB

View File

@@ -0,0 +1,205 @@
<?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"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/TopLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="8dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:text="Sample Card #1"/>
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:layout_marginBottom="16dp"
android:clickable="true"
android:focusable="true"
android:minHeight="148dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:paddingBottom="8dp">
<ImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:contentDescription="Logo"
android:src="@drawable/icon"/>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.0"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textStyle="bold"
android:text="Image Transition"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:ellipsize="end"
android:maxLines="1"
android:text="Touch the image to trigger the animation"/>
</LinearLayout>
</LinearLayout>
<com.android.test.hwui.BitmapTransitionView
android:layout_width="match_parent"
android:layout_height="194dp"
android:padding="8dp"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:text="Sample Card #2"/>
<androidx.cardview.widget.CardView
android:id="@+id/CardView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:layout_marginBottom="16dp"
android:clickable="true"
android:focusable="true"
android:minHeight="148dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="16dp"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:paddingBottom="8dp">
<ImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:contentDescription="Logo"
android:src="@drawable/icon"/>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.0"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textStyle="bold"
android:text="View Group Manipulation"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:ellipsize="end"
android:maxLines="1"
android:text="Tap the card to trigger the animation"/>
</LinearLayout>
</LinearLayout>
<ImageView
android:layout_width="match_parent"
android:layout_height="194dp"
android:background="@android:color/transparent"
android:src="@drawable/weather_2"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:paddingBottom="8dp"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<RatingBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="Card Rating"
android:isIndicator="true"
android:numStars="5"
android:rating="4.5"
android:stepSize="0.5"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:text="Category 4.5 Storm"/>
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:maxLines="3"
android:textIsSelectable="true"
android:text="Lorem ipsum dolor sit amet, nec no nominavi scaevola. Per et
sint sapientem, nobis perpetua salutandi mei te. Quo tamquam probatus
reprehendunt in. Eos esse purto eruditi ea. Enim tation persius ut sea,
eos ad consul populo. Ne eum solet altera. Cibo eligendi et est, electram
theophrastus te vel eu."/>
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
</LinearLayout>
</ScrollView>

View File

@@ -41,4 +41,5 @@
<item name="android:spotShadowAlpha">1</item>
-->
</style>
</resources>

View File

@@ -0,0 +1,138 @@
/*
* 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.ValueAnimator
import android.content.Context
import android.graphics.BitmapShader
import android.graphics.Canvas
import android.graphics.ImageDecoder
import android.graphics.Matrix
import android.graphics.Paint
import android.graphics.RuntimeShader
import android.graphics.Shader
import android.util.AttributeSet
import android.view.View
class BitmapTransitionView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private val mPaint = Paint()
private val mImageA = ImageDecoder.decodeBitmap(
ImageDecoder.createSource(context.resources, R.drawable.large_photo))
private val mImageB = ImageDecoder.decodeBitmap(
ImageDecoder.createSource(context.resources, R.drawable.very_large_photo))
private val mShaderA = BitmapShader(mImageA, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
private val mShaderB = BitmapShader(mImageB, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
private val mShader = RuntimeShader(AGSL, false)
private var mCurrentProgress = -1f
private var mForwardProgress = true
private var mCurrentAnimator = ValueAnimator.ofFloat(-1f, 1f)
init {
isClickable = true
mCurrentAnimator.duration = 1500
mCurrentAnimator.addUpdateListener { animation ->
mCurrentProgress = animation.animatedValue as Float
postInvalidate()
}
}
override fun performClick(): Boolean {
if (super.performClick()) return true
if (mCurrentAnimator.isRunning) {
mCurrentAnimator.reverse()
return true
}
if (mForwardProgress) {
mCurrentAnimator.setFloatValues(-1f, 1f)
mForwardProgress = false
} else {
mCurrentAnimator.setFloatValues(1f, -1f)
mForwardProgress = true
}
mCurrentAnimator.start()
postInvalidate()
return true
}
override fun onSizeChanged(width: Int, height: Int, oldWidth: Int, oldHeight: Int) {
val matrixA = Matrix()
val matrixB = Matrix()
matrixA.postScale(width.toFloat() / mImageA.width, height.toFloat() / mImageA.height)
matrixB.postScale(width.toFloat() / mImageB.width, height.toFloat() / mImageB.height)
mShaderA.setLocalMatrix(matrixA)
mShaderB.setLocalMatrix(matrixB)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
mShader.setInputShader("imageA", mShaderA)
mShader.setInputShader("imageB", mShaderB)
mShader.setIntUniform("imageDimensions", width, height)
mShader.setFloatUniform("progress", mCurrentProgress)
mPaint.shader = mShader
canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), mPaint)
}
private companion object {
const val AGSL = """
uniform shader imageA;
uniform shader imageB;
uniform ivec2 imageDimensions;
uniform float progress;
const vec2 iSize = vec2(48.0, 48.0);
const float iDir = 0.5;
const float iRand = 0.81;
float hash12(vec2 p) {
vec3 p3 = fract(vec3(p.xyx) * .1031);
p3 += dot(p3, p3.yzx + 33.33);
return fract((p3.x + p3.y) * p3.z);
}
float ramp(float2 p) {
return mix(hash12(p),
dot(p/vec2(imageDimensions), float2(iDir, 1 - iDir)),
iRand);
}
half4 main(float2 p) {
float2 lowRes = p / iSize;
float2 cellCenter = (floor(lowRes) + 0.5) * iSize;
float2 posInCell = fract(lowRes) * 2 - 1;
float v = ramp(cellCenter) + progress;
float distToCenter = max(abs(posInCell.x), abs(posInCell.y));
return distToCenter > v ? imageA.eval(p).rgb1 : imageB.eval(p).rgb1;
}
"""
}
}

View File

@@ -0,0 +1,109 @@
/*
* 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.ValueAnimator
import android.app.Activity
import android.graphics.Bitmap
import android.graphics.BitmapShader
import android.graphics.ImageDecoder
import android.graphics.Matrix
import android.graphics.Shader
import android.graphics.RenderEffect
import android.graphics.RuntimeShader
import android.os.Bundle
import android.view.View
class RenderEffectViewActivity : Activity() {
private val mDropsShader = RuntimeShader(dropsAGSL, false)
private var mDropsAnimator = ValueAnimator.ofFloat(0f, 1f)
private var mStartTime = System.currentTimeMillis()
private lateinit var mScratchesImage: Bitmap
private lateinit var mScratchesShader: BitmapShader
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.view_runtime_shader)
val dropsView = findViewById<View>(R.id.CardView)!!
dropsView.isClickable = true
dropsView.setOnClickListener {
if (mDropsAnimator.isRunning) {
mDropsAnimator.cancel()
dropsView.setRenderEffect(null)
} else {
mDropsAnimator.start()
}
}
val imgSource = ImageDecoder.createSource(resources, R.drawable.scratches)
mScratchesImage = ImageDecoder.decodeBitmap(imgSource)
mScratchesShader = BitmapShader(mScratchesImage,
Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
mDropsAnimator.duration = 1000
mDropsAnimator.repeatCount = ValueAnimator.INFINITE
mDropsAnimator.addUpdateListener { _ ->
val viewWidth = dropsView.width.toFloat()
val viewHeight = dropsView.height.toFloat()
val scratchesMatrix = Matrix()
scratchesMatrix.postScale(viewWidth / mScratchesImage.width,
viewHeight / mScratchesImage.height)
mScratchesShader.setLocalMatrix(scratchesMatrix)
mDropsShader.setInputShader("scratches", mScratchesShader)
mDropsShader.setFloatUniform("elapsedSeconds",
(System.currentTimeMillis() - mStartTime) / 1000f)
mDropsShader.setFloatUniform("viewDimensions", viewWidth, viewHeight)
val dropsEffect = RenderEffect.createRuntimeShaderEffect(mDropsShader, "background")
val blurEffect = RenderEffect.createBlurEffect(10f, 10f, Shader.TileMode.CLAMP)
dropsView.setRenderEffect(RenderEffect.createChainEffect(dropsEffect, blurEffect))
}
}
private companion object {
const val dropsAGSL = """
uniform float elapsedSeconds;
uniform vec2 viewDimensions;
uniform shader background;
uniform shader scratches;
vec2 dropsUV(vec2 fragCoord ) {
vec2 uv = fragCoord.xy / viewDimensions.xy; // 0 <> 1
vec2 offs = vec2(0.);
return (offs + uv).xy;
}
const vec3 iFrostColorRGB = vec3(0.5, 0.5, 0.5);
const float iFrostColorAlpha = .3;
half4 main(float2 fragCoord) {
half4 bg = background.eval(dropsUV(fragCoord)*viewDimensions.xy);
float2 scratchCoord = fragCoord.xy / viewDimensions.xy;;
scratchCoord += 1.5;
scratchCoord = mod(scratchCoord, 1);
half scratch = scratches.eval(scratchCoord*viewDimensions.xy).r;
bg.rgb = mix(bg.rgb, iFrostColorRGB, iFrostColorAlpha);
bg.rgb = mix(bg.rgb, half3(1), pow(scratch,3));
return bg;
}
"""
}
}