diff --git a/.rive_head b/.rive_head
index b9c6b3a5..88c207fc 100644
--- a/.rive_head
+++ b/.rive_head
@@ -1 +1 @@
-6e07d855c7adaba20b0fd985987f2ec32a5d2dab
+74e53aa129f5a03160ed395b23e5074d592fc307
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 37703b8c..d424960d 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -60,9 +60,10 @@
android:theme="@style/AppTheme" />
+
-
\ No newline at end of file
+
diff --git a/app/src/main/java/app/rive/runtime/example/MainActivity.kt b/app/src/main/java/app/rive/runtime/example/MainActivity.kt
index 2a1ebb85..e053d792 100644
--- a/app/src/main/java/app/rive/runtime/example/MainActivity.kt
+++ b/app/src/main/java/app/rive/runtime/example/MainActivity.kt
@@ -31,7 +31,8 @@ class MainActivity : AppCompatActivity() {
Pair(R.id.go_compose, ComposeActivity::class.java),
Pair(R.id.go_frame, FrameActivity::class.java),
Pair(R.id.go_dynamic_text, DynamicTextActivity::class.java),
- )
+ Pair(R.id.go_stress_test, StressTestActivity::class.java),
+ )
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
diff --git a/app/src/main/java/app/rive/runtime/example/StressTestActivity.kt b/app/src/main/java/app/rive/runtime/example/StressTestActivity.kt
new file mode 100644
index 00000000..a4034c23
--- /dev/null
+++ b/app/src/main/java/app/rive/runtime/example/StressTestActivity.kt
@@ -0,0 +1,152 @@
+package app.rive.runtime.example
+
+import android.content.Context
+import android.graphics.RectF
+import android.os.Bundle
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.appcompat.app.AppCompatActivity
+import app.rive.runtime.kotlin.RiveTextureView
+import app.rive.runtime.kotlin.core.*
+import app.rive.runtime.kotlin.renderers.Renderer
+import java.util.*
+import android.view.MotionEvent
+import androidx.lifecycle.DefaultLifecycleObserver
+import androidx.lifecycle.LifecycleObserver
+import kotlin.math.*
+
+
+class StressTestActivity : AppCompatActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_stress_test)
+
+ // Hides the app/action bar
+ supportActionBar?.hide()
+
+ // Attach the Rive view to the activity's root layout
+ val layout = findViewById(R.id.low_level_view_root)
+ val riveView = StressTestView(this)
+
+ layout.addView(riveView)
+ }
+}
+
+class StressTestView(context: Context) : RiveTextureView(context) {
+ private var instanceCount : Int = 1
+ private var totalElapsed : Float = 0f
+ private var totalFrames : Int = 0
+
+ override fun createObserver(): LifecycleObserver {
+ return object : DefaultLifecycleObserver {
+ /* Optionally override lifecycle methods. */
+ // override fun onCreate(owner: LifecycleOwner) {
+ // super.onCreate(owner)
+ // }
+ // override fun onDestroy(owner: LifecycleOwner) {
+ // super.onDestroy(owner)
+ // }
+ }
+ }
+
+ override fun createRenderer(): Renderer {
+ val renderer = object : Renderer() {
+
+ override fun draw() {
+ val step = animationInstance.effectiveDurationInSeconds / instanceCount
+ artboard.let {
+ save()
+ align(Fit.CONTAIN, Alignment.CENTER, RectF(0f, 0f, width, height), it.bounds)
+ val rows = (instanceCount + 6) / 7
+ val cols = min(instanceCount, 7)
+ translate(0f, (rows - 1) * -.5f * 200f)
+ for (j in 1 .. rows) {
+ save()
+ translate((cols - 1) * -.5f * 125f, 0f)
+ for (i in 1..cols) {
+ it.drawSkia(cppPointer)
+ // Draw each Marty at a slightly different animation offset to make sure
+ // the renderer can't cache things. This loop will advance the animation
+ // until it loops back around to the original point we started at before
+ // drawing.
+ animationInstance.advance(step)
+ animationInstance.apply()
+ artboard.advance(step)
+ translate(125f, 0f)
+ }
+ restore()
+ translate(0f, 200f)
+ }
+ restore()
+ }
+ }
+
+ override fun advance(elapsed: Float) {
+ // Actually advance the animation here. draw() will also call advance(), but the
+ // purpose of that is to draw each Marty at a slightly different animation offset,
+ // and it will loop back around to the original animation location.
+ animationInstance.advance(elapsed)
+ animationInstance.apply()
+ artboard.advance(elapsed)
+
+ totalElapsed += elapsed
+ totalFrames++
+
+ if (totalElapsed > 1f) {
+ val fps = totalFrames / totalElapsed
+ val fpsView =
+ ((getParent() as ViewGroup).getParent() as ViewGroup).getChildAt(1) as TextView
+ fpsView.text =
+ java.lang.String.format(
+ Locale.US,
+ "%d instances @ %.1f FPS (%.2f ms)",
+ instanceCount,
+ fps,
+ 1e3f / fps
+ )
+ totalElapsed = 0f
+ totalFrames = 0
+ }
+ }
+ }
+ // Call setup file only once we created the renderer.
+ setupFile(renderer)
+ return renderer
+ }
+
+ private lateinit var file: File
+
+ // Objects that the renderer needs for drawing
+ private lateinit var artboard: Artboard
+ private lateinit var animationInstance: LinearAnimationInstance
+
+ private fun setupFile(renderer: Renderer) {
+ // Keep a reference to the file to keep resources around.
+ file = resources.openRawResource(R.raw.marty).use { File(it.readBytes()) }
+ artboard = file.firstArtboard
+ animationInstance = artboard.animation(1)
+
+ // This will be deleted with its dependents.
+ renderer.dependencies.add(file)
+ }
+
+ override fun onTouchEvent(event: MotionEvent): Boolean {
+
+ val action: Int = event.getActionMasked()
+
+ return when (action) {
+ MotionEvent.ACTION_DOWN -> {
+ if (instanceCount < 7)
+ instanceCount += 2
+ else
+ instanceCount += 7
+ totalElapsed = 0f
+ totalFrames = 0
+ val fpsView = ((getParent() as ViewGroup).getParent() as ViewGroup).getChildAt(1) as TextView
+ fpsView.text = java.lang.String.format("%d instances", instanceCount)
+ true
+ }
+ else -> super.onTouchEvent(event)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_stress_test.xml b/app/src/main/res/layout/activity_stress_test.xml
new file mode 100644
index 00000000..b1917a05
--- /dev/null
+++ b/app/src/main/res/layout/activity_stress_test.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/main.xml b/app/src/main/res/layout/main.xml
index a9763217..775d859a 100644
--- a/app/src/main/res/layout/main.xml
+++ b/app/src/main/res/layout/main.xml
@@ -209,6 +209,15 @@
android:text="Dynamic Text"
android:textColor="@color/textColorPrimary" />
+
+
diff --git a/app/src/main/res/raw/marty.riv b/app/src/main/res/raw/marty.riv
new file mode 100644
index 00000000..abc309f3
Binary files /dev/null and b/app/src/main/res/raw/marty.riv differ
diff --git a/app/src/main/res/raw/paper.riv b/app/src/main/res/raw/paper.riv
new file mode 100644
index 00000000..aa64ee71
Binary files /dev/null and b/app/src/main/res/raw/paper.riv differ
diff --git a/app/src/main/res/raw/runner.riv b/app/src/main/res/raw/runner.riv
new file mode 100644
index 00000000..b6a0febd
Binary files /dev/null and b/app/src/main/res/raw/runner.riv differ
diff --git a/kotlin/src/main/cpp/src/bindings/bindings_renderer.cpp b/kotlin/src/main/cpp/src/bindings/bindings_renderer.cpp
index 129086df..626acdc8 100644
--- a/kotlin/src/main/cpp/src/bindings/bindings_renderer.cpp
+++ b/kotlin/src/main/cpp/src/bindings/bindings_renderer.cpp
@@ -112,6 +112,21 @@ extern "C"
jniWrapper->getRendererOnWorkerThread()->align(fit, alignment, targetBounds, sourceBounds);
}
+ JNIEXPORT void JNICALL
+ Java_app_rive_runtime_kotlin_renderers_Renderer_cppTransform(JNIEnv* env,
+ jobject thisObj,
+ jlong ref,
+ jfloat x,
+ jfloat sy,
+ jfloat sx,
+ jfloat y,
+ jfloat tx,
+ jfloat ty)
+ {
+ JNIRenderer* jniWrapper = reinterpret_cast(ref);
+ jniWrapper->getRendererOnWorkerThread()->transform(rive::Mat2D(x, sy, sx, y, tx, ty));
+ }
+
JNIEXPORT jint JNICALL
Java_app_rive_runtime_kotlin_renderers_Renderer_cppWidth(JNIEnv*, jobject, jlong rendererRef)
{
diff --git a/kotlin/src/main/java/app/rive/runtime/kotlin/renderers/Renderer.kt b/kotlin/src/main/java/app/rive/runtime/kotlin/renderers/Renderer.kt
index 683fde3d..b26519a5 100644
--- a/kotlin/src/main/java/app/rive/runtime/kotlin/renderers/Renderer.kt
+++ b/kotlin/src/main/java/app/rive/runtime/kotlin/renderers/Renderer.kt
@@ -43,6 +43,15 @@ abstract class Renderer(
targetBounds: RectF,
srcBounds: RectF
)
+ private external fun cppTransform(
+ cppPointer: Long,
+ x: Float,
+ sy: Float,
+ sx: Float,
+ y: Float,
+ tx: Float,
+ ty: Float
+ )
/** Instantiates JNIRenderer in C++ */
private external fun constructor(trace: Boolean, type: Int): Long
@@ -168,6 +177,20 @@ abstract class Renderer(
)
}
+ fun transform(x: Float, sy: Float, sx: Float, y: Float, tx: Float, ty: Float) {
+ cppTransform(cppPointer, x, sy, sx, y, tx, ty)
+ }
+
+ fun scale(sx: Float, sy: Float)
+ {
+ transform(sx, 0f, 0f, sy, 0f, 0f)
+ }
+
+ fun translate(dx: Float, dy: Float)
+ {
+ transform(1f, 0f, 0f, 1f, dx, dy)
+ }
+
@CallSuper
override fun doFrame(frameTimeNanos: Long) {
if (isPlaying) {