From 3abe24883ff22c87c2360b1d16e86299f25cc1e9 Mon Sep 17 00:00:00 2001
From: Adam <67035612+damzobridge@users.noreply.github.com>
Date: Thu, 11 Jun 2026 15:10:32 -0700
Subject: [PATCH] feat: add RenderTextureImageSource docs
---
game-runtimes/unity/data-binding.mdx | 238 ++++++++++++++++++++++++++-
snippets/experimental-feature.mdx | 19 +++
2 files changed, 255 insertions(+), 2 deletions(-)
create mode 100644 snippets/experimental-feature.mdx
diff --git a/game-runtimes/unity/data-binding.mdx b/game-runtimes/unity/data-binding.mdx
index 6eea7e16..1cb7f6b1 100644
--- a/game-runtimes/unity/data-binding.mdx
+++ b/game-runtimes/unity/data-binding.mdx
@@ -18,7 +18,7 @@ import Lists from "/snippets/runtimes/data-binding/lists.mdx"
import Artboards from "/snippets/runtimes/data-binding/artboards.mdx"
import Enums from "/snippets/runtimes/data-binding/enums.mdx"
-
+import { ExperimentalFeature } from "/snippets/experimental-feature.mdx";
import { YouTube } from "/snippets/youtube.mdx";
import { Demos } from "/snippets/demos.jsx";
@@ -461,9 +461,243 @@ private void OnDestroy()
}
}
```
-
For a demo of image data binding in Unity, see the **Image Data Binding** scene in the [Rive Unity Examples repository](https://github.com/rive-app/rive-unity-examples).
+### Render Textures
+
+
+The examples above use `ImageOutOfBandAsset` to bind static raster images loaded from disk. Use `RenderTextureImageSource` when the image comes from a live Unity `RenderTexture` instead, such as `VideoPlayer` output, a camera render target, or custom GPU content you change each frame.
+
+`RenderTextureImageSource` wraps the texture as a native Rive image and binds it to a view model image property through `SetFromRenderTextureImageSource`. Once bound, the runtime keeps the visuals up to date for you.
+
+The example below binds a `VideoPlayer` render target to a view model image property named `"video"`. Wait until the Rive widget is loaded before binding, then call `SetFromRenderTextureImageSource` once. The runtime handles per-frame updates after that.
+
+```csharp
+#if RIVE_USING_EXPERIMENTAL
+using System.Collections;
+using UnityEngine;
+using UnityEngine.Video;
+using Rive;
+using Rive.Components;
+
+public class VideoImageBinding : MonoBehaviour
+{
+ [SerializeField] private RiveWidget riveWidget;
+ [SerializeField] private RenderTexture videoTexture;
+ [SerializeField] private string viewModelImagePath = "video";
+ [SerializeField] private VideoPlayer videoPlayer;
+
+ [Tooltip("How the video is adapted before Rive samples it.")]
+ [SerializeField]
+ private RenderTextureImageSource.TextureProcessingMode processingMode =
+ RenderTextureImageSource.TextureProcessingMode.Auto;
+
+ private RenderTextureImageSource renderTextureSource;
+
+ private void Start()
+ {
+ if (videoTexture == null || riveWidget == null || videoPlayer == null)
+ {
+ Debug.LogWarning("Assign videoTexture, riveWidget, and videoPlayer in the Inspector.");
+ return;
+ }
+
+ videoPlayer.renderMode = VideoRenderMode.RenderTexture;
+ videoPlayer.targetTexture = videoTexture;
+ videoPlayer.prepareCompleted += OnVideoPrepared;
+ videoPlayer.Prepare();
+ }
+
+ private void OnVideoPrepared(VideoPlayer source)
+ {
+ StartCoroutine(BindAndPlay());
+ }
+
+ private IEnumerator BindAndPlay()
+ {
+ while (riveWidget.Status != WidgetStatus.Loaded ||
+ riveWidget.StateMachine == null)
+ {
+ yield return null;
+ }
+
+ renderTextureSource = new RenderTextureImageSource(videoTexture, processingMode);
+
+ ViewModelInstance viewModelInstance = riveWidget.StateMachine.ViewModelInstance;
+ if (viewModelInstance == null)
+ {
+ Debug.LogWarning("No ViewModelInstance on the widget.");
+ yield break;
+ }
+
+ ViewModelInstanceImageProperty imageProperty =
+ viewModelInstance.GetImageProperty(viewModelImagePath);
+ if (imageProperty == null)
+ {
+ Debug.LogWarning($"Image property '{viewModelImagePath}' not found.");
+ yield break;
+ }
+
+ imageProperty.SetFromRenderTextureImageSource(renderTextureSource);
+ videoPlayer.Play();
+ }
+
+ private void OnDestroy()
+ {
+ if (videoPlayer != null)
+ {
+ videoPlayer.prepareCompleted -= OnVideoPrepared;
+ videoPlayer.Stop();
+ videoPlayer.targetTexture = null;
+ }
+
+ renderTextureSource?.Dispose();
+ }
+}
+#endif
+```
+
+#### When to use it
+
+| Approach | Use when |
+| --- | --- |
+| `ImageOutOfBandAsset` | You have a static image file (PNG, JPG, WebP, etc.) loaded at runtime |
+| `RenderTextureImageSource` | You already have a Unity `RenderTexture` whose contents change over time |
+
+Both APIs bind to the same view model image property type. A property can only be driven by one source at a time. Assigning an `ImageOutOfBandAsset` through `Value` automatically unbinds an active render-texture source, and vice versa.
+
+#### Requirements and limitations
+
+The source must be a stable, user-allocated 2D `RenderTexture`. Do not use transient RenderGraph resources. Their backing memory can be reused and produce stale samples or crashes.
+
+Supported source formats:
+
+* Single-sample, non-MSAA
+* 2D only (not cube, array, or 3D)
+* Created and kept alive for as long as the binding is active
+
+Supported graphics backends:
+
+| Backend | Supported |
+| --- | --- |
+| Metal | Yes |
+| Direct3D 11 | Yes |
+| Direct3D 12 | Yes |
+| Vulkan | Yes |
+| OpenGL / WebGL | No |
+
+On unsupported backends, binding safe-fails: the property stays empty and an error is logged.
+
+Rive composites through an 8-bit internal render target, so HDR source values above 1.0 are clamped at the Rive layer.
+
+#### Texture processing
+
+Unity backends differ in whether render texture texels are stored top-down or bottom-up. In Linear color space projects, Unity may also hand Rive gamma-encoded values that get decoded twice when the panel composites.
+
+To avoid requiring every project to handle these fixes manually, the default `TextureProcessingMode.Auto` blits through an owned intermediate render texture and applies a flip and/or gamma re-encode only when the active backend or project settings actually need it.
+
+| Mode | Behavior |
+| --- | --- |
+| `Auto` | Apply orientation and color fixes when needed. Default. |
+| `Orientation` | Flip upside-down textures on backends that store texels top-down. Leave color alone. |
+| `Color` | Re-encode to gamma in Linear projects so colors composite correctly. Leave orientation alone. |
+| `None` | Bind the source render texture directly with no intermediate blit. Use this when your texture is already correctly oriented and encoded. |
+
+```csharp
+// Default: let the runtime decide what processing is needed
+var source = new RenderTextureImageSource(renderTexture);
+
+// Opt out when you already produce a correctly oriented, correctly encoded texture
+var directSource = new RenderTextureImageSource(
+ renderTexture,
+ RenderTextureImageSource.TextureProcessingMode.None);
+```
+
+#### Refresh behavior
+
+`RenderTextureImageSource` controls how often the bound image property is updated from the source texture.
+
+| Mode | Behavior |
+| --- | --- |
+| `PerFrame` | Rebuild and re-push every frame. Default. Use for live sources such as video or camera output. |
+| `Manual` | Update only when you call `Refresh()`. Use for snapshots, baked textures, or content that isn't updated very often. |
+
+```csharp
+// Live source (default)
+var liveSource = new RenderTextureImageSource(
+ renderTexture,
+ refreshMode: RenderTextureImageSource.RefreshMode.PerFrame);
+
+// Snapshot / on-demand source
+var snapshotSource = new RenderTextureImageSource(
+ renderTexture,
+ refreshMode: RenderTextureImageSource.RefreshMode.Manual);
+
+// After writing new content into the render texture:
+snapshotSource.Refresh();
+```
+
+#### Clearing and switching images
+
+To clear a render-texture-backed image:
+
+```csharp
+imageProperty.SetFromRenderTextureImageSource(null);
+```
+
+To switch back to a regular image asset:
+
+```csharp
+imageAsset.Load();
+imageProperty.Value = imageAsset;
+```
+
+Assigning `Value` automatically detaches any active `RenderTextureImageSource` binding on that property.
+
+#### Lifecycle and teardown
+
+While a `RenderTextureImageSource` is bound to at least one image property, the runtime keeps it alive and updating.
+
+When you are done with a source, call `Dispose()` to stop updates and release any intermediate GPU resources owned by Rive.
+
+##### Recommended teardown order
+
+`RenderTextureImageSource` does not own your `RenderTexture`. When shutting down, use this order:
+
+1. **Stop producers.** Stop or disconnect anything still writing into the texture (`VideoPlayer`, camera, custom blit loop, etc.).
+2. **Unbind and dispose the image source.** Call `Dispose()` on the `RenderTextureImageSource`, or call `SetFromRenderTextureImageSource(null)` and then `Dispose()`.
+3. **Release your render texture (if you created it).** If you allocated the `RenderTexture` at runtime, call `Release()` and then `Destroy()` on it.
+
+```csharp
+private void OnDestroy()
+{
+ // 1. Stop writing into the texture
+ if (videoPlayer != null)
+ {
+ videoPlayer.Stop();
+ videoPlayer.targetTexture = null;
+ }
+
+ // 2. Unbind and dispose the Rive image source
+ renderTextureSource?.Dispose();
+ renderTextureSource = null;
+
+ // 3. Only if you created the RenderTexture at runtime
+ if (ownsRenderTexture && renderTexture != null)
+ {
+ renderTexture.Release();
+ Destroy(renderTexture);
+ }
+}
+```
+
+
+ If your `RenderTexture` is a project asset assigned in the Inspector (as in the video example above), you only need steps 1 and 2. Do not call `Destroy()` on shared or asset-backed render textures.
+
+
+Destroying or releasing the source texture before step 2 is tolerated (the bound property clears on the next tick), but disposing the image source first is the safer path. It avoids Rive attempting to wrap a texture that is already being released.
+
+
```csharp
diff --git a/snippets/experimental-feature.mdx b/snippets/experimental-feature.mdx
new file mode 100644
index 00000000..dcc93752
--- /dev/null
+++ b/snippets/experimental-feature.mdx
@@ -0,0 +1,19 @@
+export const ExperimentalFeature = ({ defineSymbol = "RIVE_USING_EXPERIMENTAL", children }) => {
+ return (
+
+ Experimental Feature
+
+ This feature is not enabled out of the box. Add {defineSymbol} under Project Settings → Player → Scripting Define Symbols to use it.
+
+ These APIs are still in development and may change before they become stable.
+
+ Unity applies scripting defines per platform. If you ship to multiple targets (Standalone, iOS, Android, and so on), add the define on each platform tab where you need this feature.
+ {children && (
+ <>
+
+ {children}
+ >
+ )}
+
+ );
+};