<!--
{
  "documentType" : "article",
  "framework" : "SpatialPreview",
  "identifier" : "/documentation/SpatialPreview/bridging-an-external-usd-runtime-to-spatial-preview",
  "metadataVersion" : "0.1.0",
  "role" : "article",
  "title" : "Bridging an external USD runtime to Spatial Preview"
}
-->

# Bridging an external USD runtime to Spatial Preview

Sync edits between an app with its own OpenUSD runtime and a Spatial Preview session using a shared layer as the exchange mechanism.

## Overview

Spatial Preview uses <doc://com.apple.documentation/documentation/USDKit> to manage a <doc://com.apple.documentation/documentation/USDKit/USDStage> on the Mac.
Apps that use USDKit to access USD data automatically sync their edits with Spatial Preview sessions.
When your app has its own USD runtime, the two apps don’t share memory or a stage cache.
Writing a USD file to disk doesn’t notify USDKit of changes, and edits made on
the connected Apple Vision Pro aren’t automatically visible to your app.

Bridge the two runtimes using a shared <doc://com.apple.documentation/documentation/USDKit/USDLayer> as an exchange mechanism. For
each direction, app to device and device to app, the pattern is the same:
write overrides into a thin edit layer, export it to disk, and use
<doc://com.apple.documentation/documentation/USDKit/USDLayer/copy(from:to:in:)> to propagate the changes
into USDKit’s stage. [`USDPreviewSession`](/documentation/SpatialPreview/USDPreviewSession) watches the <doc://com.apple.documentation/documentation/USDKit/USDStage> for mutations
and syncs only the changed data to the device.

### Set up the layer stack

Your USD layer stack on the Mac side consists of three files:

- Scene export: The full scene written by your external runtime and the root of the composition hierarchy that USDKit loads.
- Wrapper stage: A thin USDKit stage that sublayers the scene export and stays open for the duration of the session.
- Edit layer: A lightweight layer that holds only the overrides your app or the device produces for interchange, not the full scene geometry.

Create a [`USDPreviewSession`](/documentation/SpatialPreview/USDPreviewSession) to coordinate incremental
USD synchronization over that connection.

### Sync edits from your app to the device

When your external runtime detects a change, write the affected <doc://com.apple.documentation/documentation/USDKit/USDPrim> overrides
as over-prims into the app-side edit layer and export that layer to the chosen
exchange path on disk. Then call your bridge function to pull those edits into
the USDKit stage.

The bridge opens the exchange layer using
<doc://com.apple.documentation/documentation/USDKit/USDLayer/find(identifier:)> and walks its root prims. For each root prim,
check whether the destination edit layer already has a <doc://com.apple.documentation/documentation/USDKit/USDPrim> specification at that path using
<doc://com.apple.documentation/documentation/USDKit/USDLayer/prim(at:)>. Then copy the
data with <doc://com.apple.documentation/documentation/USDKit/USDLayer/copy(from:to:in:)>. Once every
changed prim is in the USDKit edit layer, [`USDPreviewSession`](/documentation/SpatialPreview/USDPreviewSession) detects the
mutation and syncs the delta to the device:

```swift
@MainActor
func syncEdits(editLayerPath: String) {
    guard let srcLayer = USDLayer.find(identifier: editLayerPath) else {
        return
    }
```

For each prim under the root in the source layer, ensure a spec exists in the destination edit layer before copying the prim data across:

```swift
    for rootPrim in srcLayer.pseudoRoot.nameChildren {
        let path = rootPrim.path

        editLayer.copy(from: srcLayer, to: path, in: path)
    }
}
```

Mark the function with [`@MainActor`](doc://com.apple.documentation/documentation/Swift/MainActor) because all USDKit edit operations must run on
the main actor. Dispatch to the main actor from any background thread before
calling this function.

### Sync device edits back to your app

When a user manipulates objects on Apple Vision Pro, Spatial Preview writes
those edits to the USDKit edit layer. In your bridge, read from the updated wrapper stage
on an app-specific polling interval or in response to a [`USDPreviewSession`](/documentation/SpatialPreview/USDPreviewSession) event.
Your app reads the updated layer file and applies its prim overrides to the
external USD runtime inside the app.

When you read edits from the Spatial Preview stage and apply them to the <doc://com.apple.documentation/documentation/USDKit/USDStage> in your app, don’t treat the edits as new changes to the USD data. Otherwise, your app echoes them back to the Spatial Preview session and creates an infinite feedback loop.

Batch and pass edits from an external OpenUSD runtime to the bridge layer at a reasonable interval (for example, 30 ms)
to prevent locking the [`@MainActor`](doc://com.apple.documentation/documentation/Swift/MainActor) thread.

For apps that can’t produce a minimal edit layer, export the complete scene and
call [USDStage.reload()](doc://com.apple.documentation/documentation/USDKit/USDStage/reload()) or [USDLayer.reload()](doc://com.apple.documentation/documentation/USDKit/USDLayer/reload()) on the corresponding USDKit object. USDKit diffs the
old and new stage internally; [`USDPreviewSession`](/documentation/SpatialPreview/USDPreviewSession) syncs only the deltas. This
approach requires less bridging code but exports the full scene on every change,
which can be slow for large scenes:

```swift
@MainActor
func reloadStage() {
    stage.reload()
}
```

[`USDPreviewSession`](/documentation/SpatialPreview/USDPreviewSession) exposes a `state` property of type [`SpatialPreviewSessionState`](/documentation/SpatialPreview/SpatialPreviewSessionState).
Read it before calling any sync operation to confirm the session is ready.

- [`SpatialPreviewSessionState.waiting`](/documentation/SpatialPreview/SpatialPreviewSessionState/waiting): No device is connected yet; sync calls have no effect.
- [`SpatialPreviewSessionState.connected`](/documentation/SpatialPreview/SpatialPreviewSessionState/connected): The connection is active; push edits.
- [`SpatialPreviewSessionState.interrupted`](/documentation/SpatialPreview/SpatialPreviewSessionState/interrupted): The session temporarily lost connectivity; queue pending edits and replay when state returns to [`SpatialPreviewSessionState.connected`](/documentation/SpatialPreview/SpatialPreviewSessionState/connected).
- [`SpatialPreviewSessionState.invalidated`](/documentation/SpatialPreview/SpatialPreviewSessionState/invalidated): The session ended permanently; release it and create a new [`DocumentPreviewSession`](/documentation/SpatialPreview/DocumentPreviewSession) to reconnect.

---

Copyright &copy; 2026 Apple Inc. All rights reserved. | [Terms of Use](https://www.apple.com/legal/internet-services/terms/site.html) | [Privacy Policy](https://www.apple.com/privacy/privacy-policy)