Skip to content

Plan pure Swift shell over SwiftUsd/OpenUSD #1

@elkraneo

Description

@elkraneo

Summary

Create a pure Swift shell over SwiftUsd so application and feature targets can use OpenUSD functionality without importing raw Swift/C++ interop types or enabling C++ interop themselves.

The goal is not to replace SwiftUsd. SwiftUsd remains the runtime binding and engine layer over OpenUSD. SwiftUsdShell becomes the stable pure Swift SDK surface: handles, values, DTOs, protocols, semantic requests, and results.

Productization Goal

The main goal is to productize OpenUSD as an SDK capability that is painless for host applications:

  • Hosts should not import OpenUSD, raw SwiftUsd/OpenUSD C++ interop modules, or USDInteropCxx.
  • Hosts should not need Swift/C++ interop enabled just to inspect, validate, or edit USD assets.
  • Hosts should avoid paying large build-time and invalidation costs from raw OpenUSD headers in app and feature targets.
  • Common workflows should be driven through pure Swift APIs and stable DTOs.

Architectural Clarification

The intended final layering is:

OpenUSD
  Native USD engine and C++ implementation.

SwiftUsd
  Swift runtime/binding layer over OpenUSD.
  Owns direct OpenUSD interaction wherever possible.
  Generic missing authoring/inspection primitives should be added here first.

SwiftUsdShell
  Pure Swift public SDK surface.
  No OpenUSD import.
  No Swift/C++ interop.
  Defines handles, paths, tokens, values, DTOs, protocols, semantic requests, and results.

First-party USD packages, such as USDTools
  Product/domain workflows over SwiftUsdShell APIs.
  Keep raw runtime details private and transitional.
  Public APIs must stay pure Swift and shell-facing.

Application and external consumers
  Import SwiftUsdShell and pure Swift domain APIs.
  Do not import OpenUSD, USDInteropCxx, or raw SwiftUsd/OpenUSD interop surfaces.

SwiftUsdShellRuntime should not become a renamed USDInterop. If a runtime implementation target exists, it should prefer SwiftUsd as its implementation dependency and only drop to raw OpenUSD for explicitly documented gaps that cannot yet be expressed through SwiftUsd. Those gaps should be candidates for moving down into SwiftUsd.

USDInterop Policy

USDInterop is transitional scaffolding, not the final SDK boundary.

  • Do not grow USDInterop as the long-term productized runtime layer.
  • Generic OpenUSD-safe operations currently in USDInterop should be classified and migrated:
    • runtime/binding primitives belong in SwiftUsd where possible;
    • pure Swift API contracts belong in SwiftUsdShell;
    • product workflow logic belongs in USDTools or another domain package.
  • New work should avoid adding broad new public concepts to USDInterop unless needed to unblock an immediate migration or bug.
  • Consumers must never be required to depend on USDInterop or USDInteropCxx for common workflows.

Problem

SwiftUsd already ships native OpenUSD libraries as binaries, but consumers still pay large compile-time and invalidation costs when they import the raw OpenUSD C++ API surface through Swift/C++ interop.

The cost leaks whenever public APIs expose imported C++ types such as:

  • UsdStage
  • UsdStageRefPtr
  • UsdPrim
  • UsdAttribute
  • UsdShadeShader
  • UsdShadeMaterial
  • SdfPath
  • TfToken
  • VtValue
  • Gf*
  • OpenUSD.*
  • pxrInternal_*

This prevents clean productization because downstream consumers must still understand the heavy C++ interop surface.

Non-Goals

  • Do not hand-write a .usda parser.
  • Do not replace SwiftUsd/OpenUSD.
  • Do not expose raw OpenUSD escape hatches in public APIs.
  • Do not make USDInterop the permanent SDK runtime layer.
  • Do not attempt to complete every OpenUSD schema before proving the boundary.
  • Do not binary-package the runtime before the public API is pure Swift.

Hard Rules

  1. SwiftUsdShell must never import OpenUSD, SwiftUsd raw interop modules, USDInterop, USDInteropCxx, or any C++ interop target.
  2. Public shell APIs must not mention raw SwiftUsd/OpenUSD types.
  3. Runtime implementation should use SwiftUsd first. Direct OpenUSD use is allowed only as private implementation debt for missing SwiftUsd capabilities.
  4. Any public API that exposes Usd*, Sdf*, Tf*, Vt*, Gf*, OpenUSD.*, or pxrInternal_* is a shelling failure unless the type is a pure Swift shell type.
  5. Migration must be measurable by removing raw USD imports or C++ interop requirements from at least one downstream consumer.
  6. USDInterop dependencies in first-party packages must be treated as migration debt, not final architecture.

Phase 0: Package Bootstrap

  • Establish SwiftUsdShell as a standalone package.
  • Add pure Swift core types:
    • USDStageHandle
    • USDPrimHandle
    • USDPath
    • USDToken
    • USDAssetPath
    • USDLoadPolicy
    • SwiftUsdShellError
  • Add tests proving core types are Hashable, Sendable, and Codable where appropriate.
  • Confirm SwiftUsdShell builds without Swift/C++ interop.

Exit criteria:

  • SwiftUsdShell has zero OpenUSD imports.
  • SwiftUsdShell has zero USDInterop or USDInteropCxx imports.
  • SwiftUsdShell has zero .interoperabilityMode(.Cxx).
  • A trivial consumer can import SwiftUsdShell without C++ interop.

Phase 1: Runtime Capability Inventory

Before creating a runtime target, inventory the real missing capabilities.

  • List shell workflows that require runtime implementation:
    • open stage from URL
    • register/resolve stage handles
    • validate prim existence
    • inspect prim and attribute summaries
    • inspect material sources
    • author material values and texture connections
    • save/export edit layers
  • For each runtime operation, decide the correct home:
    • SwiftUsd if it is a generic runtime/binding primitive;
    • SwiftUsdShell if it is a pure Swift API contract or DTO;
    • USDTools if it is domain workflow logic;
    • temporary USDInterop only when needed to bridge an existing gap.
  • Avoid creating a parallel runtime target that simply duplicates USDInterop.

Exit criteria:

  • Runtime gaps are explicitly classified.
  • Any direct OpenUSD or USDInterop dependency has a documented migration target.

Phase 2: Mid-Weight Pilot In USDToolsMaterials

Use USDToolsMaterials as the first serious proof. It is large enough to be meaningful and small enough to complete.

Initial focus: read-only material inspection and detection, not mutation.

  • Add SwiftUsdShell dependency to USDToolsMaterials.
  • Add shell-facing APIs for:
    • detectShaderSystems(in: USDStageHandle)
    • detectOrigin(in: USDStageHandle)
    • detectMaterialOutputs(in: USDStageHandle, materialPath:)
    • detectMaterialMode(in: USDStageHandle, materialPath:)
    • editableMaterialContract(in: USDStageHandle, materialPath:)
    • inspectMaterialSources(in: USDStageHandle, materialPath:)
  • Keep old raw APIs temporarily for compatibility.
  • Make raw OpenUSD typealiases internal where possible.
  • If immediate internalization breaks callers, deprecate public raw APIs and record blockers.
  • Add tests around shell-facing material detection.

Exit criteria:

  • USDToolsMaterials has a complete read-only material inspection API that does not expose raw USD types.
  • At least one downstream consumer can call material inspection through shell handles.
  • Remaining raw public APIs are documented as migration debt.

Phase 3: Migrate One Real Consumer

Use Preflight as the internal proving ground, but do not make this package Preflight-specific.

  • Pick one consumer path, likely material inspection in PreflightUSDInterop.
  • Replace raw UsdStageRefPtr material calls with shell handle calls.
  • Confirm behavior remains equivalent.
  • Check whether the migrated consumer can remove direct OpenUSD, USDInterop, or C++ interop dependency for that path.

Exit criteria:

  • One real product path uses SwiftUsdShell APIs instead of raw SwiftUsd/OpenUSD APIs.
  • The migration reveals concrete remaining raw USD dependencies instead of hiding them.

Phase 4: Expand Shell Core Types

Add pure Swift representations as needed by real APIs:

  • USDValue
  • numeric/vector/matrix values
  • USDTimeCode
  • USDAttributeSummary
  • USDRelationshipSummary
  • USDPrimSummary
  • USDMaterialSummary
  • USDConnection
  • diagnostics and warnings

Exit criteria:

  • Common read-only inspection results can be represented without VtValue, TfToken, or SdfPath leaking.

Phase 5: Material Mutation Shell

After read-only material inspection works, migrate material edits.

  • Shell-facing semantic edit requests.
  • Shell-facing prepared edit results.
  • Shell-facing mutation results.
  • Authoring APIs must not expose UsdShade*, Sdf*, Tf*, VtValue, or raw OpenUSD types.
  • Generic authoring primitives needed by mutation should be proposed for SwiftUsd first, not permanently added to USDInterop.
  • Existing USDInterop-backed mutation helpers are transitional and should have a migration path.

Exit criteria:

  • Material inspection and material edit planning/mutation can be driven through pure Swift public APIs.
  • Preflight can perform common material edits without importing raw OpenUSD or USDInterop in app/feature targets.
  • Remaining USDInterop use is private, documented, and targeted for removal or movement into SwiftUsd.

Phase 6: Broader USDTools Shelling

Apply the pattern to other first-party packages in priority order:

  1. USDToolsMaterials
  2. USDToolsInspection
  3. USDToolsTransformSupport
  4. USDAnalysis
  5. USDToolsSession
  6. USDToolsSurgery
  7. AppleUSDSchemasUSD

For each package:

  • Inventory public raw USD signatures.
  • Add shell equivalents.
  • Migrate one consumer.
  • Make raw APIs internal or deprecated.
  • Record remaining USDInterop dependencies as migration debt.
  • Add tests.

Exit criteria:

  • First-party public APIs no longer require downstream consumers to import raw OpenUSD, USDInterop, or C++ interop targets for common workflows.

Phase 7: Code Generation

Once the manual shell shape is validated, introduce code generation.

Potential inputs:

  • SwiftUsd symbols and APIs
  • OpenUSD schema information, where needed
  • Apple SwiftUsd-ast-answerer ideas
  • symbol graphs where useful

Generate:

  • pure Swift shell wrappers
  • SwiftUsd-backed bridge methods where appropriate
  • schema wrappers
  • token sets
  • enum/value mappings

Exit criteria:

  • Repetitive schema/value wrapper work is generated, not hand-maintained.
  • Generated public APIs remain pure Swift.

Phase 8: Binary Or Service Productization

Only after public APIs are pure Swift:

  • Evaluate binary XCFramework runtime packaging.
  • Evaluate XPC/helper service runtime packaging.
  • Measure consumer build impact.
  • Document local development override strategy.

Exit criteria:

  • Consumers can use meaningful USD functionality without rebuilding/reimporting SwiftUsd in most app/feature targets.

Phase 9: External Consumer Proof With Bismuth

Use Bismuth as an external proof after internal shelling works.

  • Create BismuthUSD optional module.
  • Consume SwiftUsdShell APIs.
  • Convert shell mesh/material output to Bismuth Entity, MeshResource, and material types.
  • Avoid raw SwiftUsd/OpenUSD dependency in Bismuth core.

Exit criteria:

  • Bismuth can import static USD scene data through shell APIs without taking a direct raw SwiftUsd/OpenUSD dependency in its core renderer/DSL.

Completion Definition

The whole SwiftUsd shelling effort is complete when:

  • SwiftUsdShell is a standalone pure Swift package.
  • SwiftUsd remains the preferred runtime binding layer over OpenUSD.
  • Public shell APIs cover the main read/write workflows needed by first-party packages.
  • First-party packages expose pure Swift APIs for common workflows.
  • Raw SwiftUsd/OpenUSD and USDInterop types are no longer part of app-facing public APIs.
  • At least one real app/feature consumer uses shell APIs without direct raw OpenUSD, USDInterop, or C++ interop imports.
  • Build impact has been measured before and after.
  • Binary or service packaging has been evaluated after the API boundary is clean.
  • Bismuth or another external consumer can use the shell without becoming tied to raw SwiftUsd/OpenUSD.

Initial Implementation Note

The first concrete pilot should be USDToolsMaterials, specifically the read-only material inspection surface. This is intentionally more meaningful than a trivial stage-cache handle proof, but still smaller than shelling all of USDTools at once.

For mutation work, prefer adding missing generic runtime primitives to SwiftUsd, then representing them through SwiftUsdShell pure Swift contracts. Use USDInterop only as temporary compatibility scaffolding while migrating existing USDTools behavior.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions