Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ The following table compares feature availability with the [previous Rive React
| Accessibility semantics | ⚠️ | Editor-authored semantics → VoiceOver (iOS; Android in progress) |
| Animation selection | ❌ | Animation playback not planned, use state machines |
| Renderer options | ❌ | Single renderer option available (Rive) |
| GPU Canvas (3D content) | ⚠️ | Opt-in via `RiveRuntime.setGPUCanvasEnabled()` (new runtime only) |

> **Note**: Several features in the table above (state machine inputs, text runs, and events) represent legacy approaches to runtime control. We recommend using data binding instead, as it provides a more maintainable way to control your Rive graphics (both at edit time and runtime).

Expand Down
14 changes: 0 additions & 14 deletions android/src/legacy/java/com/rive/RiveRenderBackendConfig.kt

This file was deleted.

23 changes: 23 additions & 0 deletions android/src/legacy/java/com/rive/RiveWorkerConfig.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.rive

import android.util.Log
import com.margelo.nitro.rive.AndroidRenderBackend

object RiveWorkerConfig {
private const val TAG = "RiveWorkerConfig"

fun setRenderBackend(backend: AndroidRenderBackend) {
if (backend != AndroidRenderBackend.OPENGL) {
Log.w(TAG, "setAndroidRenderBackend($backend) ignored: the legacy backend only supports OpenGL rendering.")
}
}

fun setGPUCanvasEnabled(enabled: Boolean) {
if (enabled) {
Log.w(TAG, "setGPUCanvasEnabled(true) ignored: the legacy backend does not support GPU Canvas.")
}
}

val isGPUCanvasEnabled: Boolean
get() = false
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import androidx.annotation.Keep
import com.facebook.proguard.annotations.DoNotStrip
import com.margelo.nitro.core.Promise
import com.rive.RiveInitializer
import com.rive.RiveRenderBackendConfig
import com.rive.RiveWorkerConfig
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

Expand All @@ -26,6 +26,13 @@ class HybridRiveRuntime : HybridRiveRuntimeSpec() {
get() = RiveInitializer.error

override fun setAndroidRenderBackend(backend: AndroidRenderBackend) {
RiveRenderBackendConfig.set(backend)
RiveWorkerConfig.setRenderBackend(backend)
}

override val isGPUCanvasEnabled: Boolean
get() = RiveWorkerConfig.isGPUCanvasEnabled

override fun setGPUCanvasEnabled(enabled: Boolean) {
RiveWorkerConfig.setGPUCanvasEnabled(enabled)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import app.rive.core.CommandQueue
import com.facebook.proguard.annotations.DoNotStrip
import com.margelo.nitro.core.ArrayBuffer
import com.margelo.nitro.core.Promise
import com.rive.RiveRenderBackendConfig
import com.rive.RiveWorkerConfig
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

Expand Down Expand Up @@ -88,13 +88,10 @@ class HybridRiveFileFactory : HybridRiveFileFactorySpec() {
app.rive.RiveLog.logger = RiveErrorLogger
Log.d(TAG, "RiveErrorLogger installed")
}
return sharedWorker ?: run {
val renderBackend = RiveRenderBackendConfig.resolveForWorker()
CommandQueue(renderBackend).also {
sharedWorker = it
Log.d(TAG, "Created CommandQueue (renderBackend=$renderBackend), refCount=${it.refCount}")
startPolling(it)
}
return sharedWorker ?: RiveWorkerConfig.createWorker().also {
sharedWorker = it
Log.d(TAG, "Created CommandQueue (${RiveWorkerConfig.current}), refCount=${it.refCount}")
startPolling(it)
}
}

Expand Down
31 changes: 31 additions & 0 deletions android/src/new/java/com/rive/DeferredRiveWorker.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.rive

import app.rive.RenderBackend
import app.rive.core.CommandQueue
import java.lang.reflect.InvocationTargetException

/**
* rive-android only exposes its deferred (GPU Canvas) worker through the
* Compose-only `rememberDeferredRiveWorker`; `CommandQueue.createDeferred` is
* Kotlin-internal. Reflection reaches it under either its mangled JVM name or
* a plain `createDeferred`.
*/
internal object DeferredRiveWorker {
private val factory by lazy {
val signature = arrayOf(RenderBackend::class.java, Boolean::class.javaPrimitiveType)
CommandQueue.Companion::class.java.declaredMethods.firstOrNull {
(it.name == "createDeferred" || it.name.startsWith("createDeferred$")) &&
it.parameterTypes.contentEquals(signature)
}
}

/** Null when this rive-android has no deferred worker entry point. */
fun createOrNull(renderBackend: RenderBackend): CommandQueue? {
val method = factory ?: return null
return try {
method.invoke(CommandQueue.Companion, renderBackend, false) as CommandQueue
} catch (e: InvocationTargetException) {
throw e.targetException
}
}
}
40 changes: 0 additions & 40 deletions android/src/new/java/com/rive/RiveRenderBackendConfig.kt

This file was deleted.

80 changes: 80 additions & 0 deletions android/src/new/java/com/rive/RiveWorkerConfig.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package com.rive

import android.util.Log
import app.rive.RenderBackend
import app.rive.core.CommandQueue
import com.margelo.nitro.rive.AndroidRenderBackend
import com.margelo.nitro.rive.RiveLog

/**
* Process-wide options for the new runtime's shared CommandQueue. They only
* take effect if set before the worker is created (i.e. before the first Rive
* file is loaded); once [createWorker] has succeeded, later calls are logged
* and ignored.
*/
object RiveWorkerConfig {
private const val TAG = "RiveWorkerConfig"

data class Resolved(val renderBackend: RenderBackend, val gpuCanvasEnabled: Boolean)

private var requestedBackend: RenderBackend = RenderBackend.OpenGL
private var requestedGPUCanvas = false
private var resolved: Resolved? = null

@Synchronized
fun setRenderBackend(backend: AndroidRenderBackend) {
resolved?.let {
Log.w(
TAG,
"setAndroidRenderBackend($backend) ignored: the shared render worker already exists " +
"(using ${it.renderBackend}). Call it before loading any Rive files."
)
return
}
requestedBackend = when (backend) {
AndroidRenderBackend.OPENGL -> RenderBackend.OpenGL
AndroidRenderBackend.VULKAN -> RenderBackend.Vulkan
}
}

@Synchronized
fun setGPUCanvasEnabled(enabled: Boolean) {
resolved?.let {
Log.w(
TAG,
"setGPUCanvasEnabled($enabled) ignored: the shared render worker already exists " +
"(GPU Canvas ${if (it.gpuCanvasEnabled) "enabled" else "disabled"}). " +
"Call it before loading any Rive files."
)
return
}
requestedGPUCanvas = enabled
}

val isGPUCanvasEnabled: Boolean
@Synchronized get() = resolved?.gpuCanvasEnabled ?: requestedGPUCanvas

/** The options the shared worker was built with; null until [createWorker] succeeds. */
val current: Resolved?
@Synchronized get() = resolved

/**
* Builds the shared worker from the requested options. GPU Canvas falls back
* to a regular worker when this rive-android has no deferred entry point.
*/
@Synchronized
fun createWorker(): CommandQueue {
val backend = requestedBackend
val deferred = if (requestedGPUCanvas) DeferredRiveWorker.createOrNull(backend) else null
if (requestedGPUCanvas && deferred == null) {
RiveLog.w(
TAG,
"GPU Canvas is not available in this rive-android version; rendering without it. " +
"3D content will not draw."
)
}
val worker = deferred ?: CommandQueue(backend)
resolved = Resolved(backend, gpuCanvasEnabled = deferred != null)
return worker
}
}
24 changes: 24 additions & 0 deletions docs/runtime-backends.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,27 @@ render worker is created (a later call logs a warning and is ignored).
Vulkan requires Android 10 (API 29) or newer; rive-android automatically
falls back to OpenGL when Vulkan is unavailable or fails to initialize. The
call is a no-op on iOS and on the legacy Android backend.

## GPU Canvas (new runtime only)

Rive's GPU Canvas renderer is required for 3D content and is disabled by
default. Opt in per process, before loading any Rive files:

```ts
import { RiveRuntime } from '@rive-app/react-native';

RiveRuntime.setGPUCanvasEnabled(true);
```

The choice is fixed once the shared render worker is created (a later call
logs a warning and is ignored); `RiveRuntime.isGPUCanvasEnabled()` reports the
setting in effect. On iOS this maps to
[`Worker(configuration: .init(enableGPUCanvas: true))`](https://rive.app/docs/runtimes/apple/gpu-canvas);
it needs rive-ios 6.25 or newer, and an older `RiveRuntimeIOSVersion` override
fails to compile. On Android it maps to rive-android's experimental deferred
worker, which upstream describes as temporary scaffolding on its way to becoming
the default; its entry point is not public, so the library resolves it by
reflection. If the rive-android in use has no deferred worker (e.g. an overridden
`Rive_RiveRuntimeAndroidVersion`), the worker is created without GPU Canvas, a
warning is logged through `RiveLog`, and `isGPUCanvasEnabled()` reports `false`
from then on. The call is a no-op on the legacy runtime.
11 changes: 11 additions & 0 deletions example/__tests__/viewmodel-properties.harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,17 @@ describe('ViewModel Properties', () => {
instance.booleanProperty('nonexistent')!.getValueAsync()
).rejects.toBeDefined();

// Nested misses: bad leaf under a real nested view model, and a bad
// parent segment. rive-android 11.10.0-11.12.0 aborted the process on
// these (rive-app/rive-android#470).
await expect(
instance.booleanProperty('pet/nonexistent')!.getValueAsync()
).rejects.toBeDefined();

await expect(
instance.booleanProperty('nonexistent/likes_popcorn')!.getValueAsync()
).rejects.toBeDefined();

await expect(
instance.colorProperty('nonexistent')!.getValueAsync()
).rejects.toBeDefined();
Expand Down
Binary file added example/assets/rive/ore.riv
Binary file not shown.
20 changes: 20 additions & 0 deletions example/assets/rive/ore.riv.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Generated by rive-gen-types — do not edit manually. @generated
/* eslint-disable */
// Source: ore.riv
import type { RiveFileSource } from '@rive-app/react-native';
declare const asset: RiveFileSource<{
schemaVersion: 1;
artboards: 'Artboard';
defaultArtboard: 'Artboard';
stateMachines: {
Artboard: 'State Machine 1';
};
enums: {};
viewModels: {
ViewModel1: {

};
};
referencedAssets: {};
}>;
export default asset;
Loading
Loading