Release Notes — v2.56.0
What's new
Dynamic mesh node
VRODynamicMeshNode lets you update geometry every frame without recreating the node or triggering GPU reallocation. This unlocks a class of apps that wasn't practical before: procedural terrain, marching cubes in AR, CPU skinning of custom formats, and output from external simulation engines. The vertex buffers (positions, normals, UVs, colors) are updated in place using an orphan + sub-data path on OpenGL, keeping frame time flat even at 60 fps.
Virtual game controller
Two new native components — ViroVirtualJoystick and ViroVirtualButton — bring on-screen game controls to AR apps with sub-2 ms input latency. Touch events are handled entirely at the native layer and written to a shared state registry that your C++ game loop reads every frame, with no JS bridge round-trips in the hot path. Both components also fire JS callbacks (onStickChange, onPressIn, onPressOut) so you can drive UI reactions from the same input.
<ViroVirtualJoystick
controllerId="p1"
stickSide="left"
radius={60}
onStickChange={(e) => move(e.nativeEvent.x, e.nativeEvent.y)}
/>
<ViroVirtualButton controllerId="p1" button="A" size={52} />PCM audio streaming
StreamingAudioManager opens a real-time audio path where you push raw PCM samples as they are produced, rather than loading a complete file. The API is the same on iOS and Android:
StreamingAudioManager.create('voice');
StreamingAudioManager.beginStreaming('voice', 24000, 1);
StreamingAudioManager.play('voice');
// from your audio producer loop:
StreamingAudioManager.pushSamples('voice', base64Float32PCM);Useful for TTS output, procedural sound synthesis, physics-driven audio, and any embedded engine that generates audio samples at runtime.
AR World Mesh — public subscriber API
The AR world mesh is now a general-purpose multi-consumer provider. Any part of your app can subscribe to receive the full mesh geometry (vertices, indices, confidence) along with a source tag that tells you whether the data came from LiDAR, the monocular depth model, or ARKit plane anchors.
This release also adds:
- Plane fallback — devices without LiDAR or the Depth API now get a mesh built from detected AR planes, so the feature degrades gracefully instead of producing nothing.
- Per-consumer decimation — each subscriber can cap its triangle budget independently, so a physics engine and a nav-mesh consumer can coexist without fighting over resolution.
- Async physics — Bullet BVH construction runs on a background thread, preventing the ARKit frame drops that appeared when processing large meshes on the render thread.
- Full Android support — ARCore depth mode is now activated automatically when world mesh is enabled, and plane mesh generation is implemented end-to-end on Android.
Game loop
ViroGameLoop and the useGameLoop / useFixedUpdate hooks give you a proper per-frame callback with both variable-dt and deterministic fixed-step modes. Fixed-step mode is useful for physics engines and networked simulations that require ticks at an exact frequency.
<ViroGameLoop
onUpdate={({ dt, elapsed }) => { /* variable, every frame */ }}
fixedHz={30}
onFixedUpdate={({ dt }) => { /* deterministic, 30 times/s */ }}
/>ViroGameLoopUtils provides setPosition, setRotation, and setScale that write directly to the native node, bypassing React's reconciler for zero-overhead transforms from inside the loop.
Improvements
Monocular depth — new metric model, better occlusion
The monocular depth estimator has been upgraded to Depth Anything V2 (metric, indoor), a model trained on indoor scenes that outputs depth in meters. Compared to the previous model, occlusion triggers more reliably and at the correct distances for typical indoor AR use.
The pipeline also received several quality improvements: temporal confidence synthesis to avoid upgrading hit-tests on unstable depth pixels, in-place GPU texture updates that eliminate a per-frame allocation, correct handling of all four device orientations, and adaptive thermal throttling to prevent overheating during extended sessions.
Two new props on ViroARSceneNavigator let you tune the estimator at runtime:
| Prop | Default | Description |
|---|---|---|
monocularDepthScale |
1.0 |
Multiplies all depth values before occlusion. Lower values make occlusion trigger sooner. |
monocularDepthTargetFPS |
5 |
Inference rate. Raise for smoother occlusion; lower to reduce thermal load. |
Bug fixes
-
SIGABRT on Android 14+ (API 34) — a hard crash in the ARCore anchor handling path has been resolved. The crash occurred when
NewStringUTFreceived a non-Modified-UTF-8 anchor ID, which became common on Android 14. -
Quest Store submission rejected due to forced landscape orientation — the Viro Android plugin was unconditionally setting landscape orientation on
MainActivity, which caused Quest Store validation failures for non-Quest apps and overrode the orientation set inapp.json. The override now applies only to Quest apps.
Upgrade notes
No breaking changes. All new components and hooks are additive. StreamingAudioManager, ViroVirtualJoystick, ViroVirtualButton, ViroGameLoop, and the useGameLoop family are available as named exports from @reactvision/react-viro.
The setUpdateCallback on VROARWorldMesh is retained but deprecated in favor of the new subscribe / unsubscribe API.