Rendering a windowed game in stereo
At a glance
| Item | Summary |
|---|---|
| Purpose | Bring an iOS or iPadOS game to visionOS and enhance it. |
| App architecture | The app shell selects a stereo rendering path; HeadPositionProvider supplies view state, dedicated renderer types own Metal frame work, and RealityKit deferred-lighting components/systems integrate the game scene into a windowed stereo surface. |
| Main patterns | SwiftUI scene composition, UI–RealityKit bridge, Entity-component-system, Session owner boundary, Protocol capability boundary, Pluggable stereo renderer |
| Project style | Code-rich sample with 45 scanned C/Objective-C header, Metal, Swift file(s) and 5021 implementation line(s). |
Project structure
Source bundle/
├── RealityKit-Stereo-Rendering/RealityKitStereoRenderingApp.swift # MetalVRRPortalSampleApp, WindowView, OnDismiss
├── RealityKit-Stereo-Rendering/DeferredLighting/Renderer/Renderer.swift # Renderer, DrawableProviding
├── RealityKit-Stereo-Rendering/DeferredLighting/Renderer/SinglePassDeferredRenderer.swift # SinglePassDeferredRenderer
├── RealityKit-Stereo-Rendering/DeferredLighting/Renderer/TraditionalDeferredRenderer.swift # TraditionalDeferredRenderer
├── RealityKit-Stereo-Rendering/DeferredLighting/DeferredLightingView.swift # DeferredLightingComponent, DeferredLightingEntity, DeferredLightingSystem
├── RealityKit-Stereo-Rendering/DeferredLighting/Renderer/Utilities/RenderDestination.swift # RenderDestination
├── RealityKit-Stereo-Rendering/MetalVRREntity.swift # RateFactors, RateFactorProviding, RateIndicator
└── Packages/RealityKit-Assets/Package.realitycomposerpro/ProjectData/main.json # authored RealityKit content
Structure observations
- The runtime boundary is WindowGroup + volumetric window + ImmersiveSpace + SwiftUI/UIKit host; the tree is pruned to lifecycle, state, framework, rendering, and ECS files.
- The source is C/Objective-C header, Metal, Swift; role-named types and feature folders separate the major responsibilities.
- Authored
.reality,.rkassets, or Reality Composer Pro content is an implementation boundary; Swift loads or drives it rather than reproducing its entity graph.
Overall architecture
flowchart LR
MetalVRRPortalSampleApp_1["MetalVRRPortalSampleApp"]
WindowGroup___volumetric_window___ImmersiveSpace___SwiftUI_UIKit_host_2["WindowGroup + volumetric window + ImmersiveSpace + SwiftUI/UIKit host"]
DeferredLightingView_3["DeferredLightingView"]
Renderer_4["Renderer"]
RealityKit_entity_graph_5["RealityKit entity graph"]
head_pose___stereo_Metal_renderer_6["head pose + stereo Metal renderer"]
MetalVRRPortalSampleApp_1 --> WindowGroup___volumetric_window___ImmersiveSpace___SwiftUI_UIKit_host_2
WindowGroup___volumetric_window___ImmersiveSpace___SwiftUI_UIKit_host_2 --> DeferredLightingView_3
DeferredLightingView_3 --> Renderer_4
Renderer_4 --> RealityKit_entity_graph_5
RealityKit_entity_graph_5 --> head_pose___stereo_Metal_renderer_6
Reference code
RealityKit-Stereo-Rendering/RealityKitStereoRenderingApp.swift:13 — executable or app-lifecycle anchor.
struct MetalVRRPortalSampleApp: App {
// ...
}The diagram is a responsibility flow, not a claim that every adjacent node directly calls the next. It separates lifecycle, presentation, stable state, framework/session work, and the RealityKit entity graph.
Ownership and state
classDiagram
DeferredLightingView *-- MetalVRREntity : renderEntity
DeferredLightingView *-- Entity : root3DFrame
DeferredLightingView *-- Entity : root
DeferredLightingView *-- HeadPositionProvider : headTracker
Ownership evidence
RealityKit-Stereo-Rendering/DeferredLighting/DeferredLightingView.swift:282 — representative stored state or nearest verified lifecycle anchor.
struct DeferredLightingView: View {
// ...
@State var renderEntity: MetalVRREntity?
// ...
}| Owner | Object or state | Relationship | Mutation authority |
|---|---|---|---|
DeferredLightingView |
MetalVRREntity as renderEntity |
owns a SwiftUI-managed reference slot | The owning type coordinates writes |
DeferredLightingView |
Entity as root3DFrame |
creates and retains | The owning type coordinates writes |
DeferredLightingView |
Entity as root |
creates and retains | The owning type coordinates writes |
DeferredLightingView |
HeadPositionProvider as headTracker |
creates and retains | The owning type coordinates writes |
Ownership is intentionally narrow: environment, bindings, observed objects, weak links, and delegate callbacks do not prove lifetime ownership. Initialized stored services and wrapper-managed state do; entities added inside a RealityKit content closure belong to that entity graph.
Class and protocol design
| Type | Responsibility | Depends on or conforms to |
|---|---|---|
MetalVRRPortalSampleApp (RealityKit-Stereo-Rendering/RealityKitStereoRenderingApp.swift:13) |
Declares app lifecycle and top-level dependency lifetime. | App |
DeferredLightingView (RealityKit-Stereo-Rendering/DeferredLighting/DeferredLightingView.swift:280) |
Presents UI and forwards gestures or lifecycle events. | View |
Renderer (RealityKit-Stereo-Rendering/DeferredLighting/Renderer/Renderer.swift:13) |
Wraps a framework, input, rendering, or session capability. | NSObject |
DrawableProviding (RealityKit-Stereo-Rendering/DeferredLighting/Renderer/Renderer.swift:353) |
Declares a replaceable capability or collaboration contract. | Concrete framework collaborators |
RenderDestination (RealityKit-Stereo-Rendering/DeferredLighting/Renderer/Utilities/RenderDestination.swift:13) |
Declares a replaceable capability or collaboration contract. | Concrete framework collaborators |
RateFactorProviding (RealityKit-Stereo-Rendering/MetalVRREntity.swift:22) |
Declares a replaceable capability or collaboration contract. | Concrete framework collaborators |
DeferredLightingComponent (RealityKit-Stereo-Rendering/DeferredLighting/DeferredLightingView.swift:12) |
Stores RealityKit entity data. | TransientComponent |
DeferredLightingSystem (RealityKit-Stereo-Rendering/DeferredLighting/DeferredLightingView.swift:254) |
Updates entities matching a RealityKit component query. | System |
Verified protocol-oriented seams: ResolutionProbeGrid → RateFactorProviding, DeferredLightingEntity → DrawableProviding, DeferredLightingEntity → RenderDestination, MTKView → DrawableProviding, MTKView → RenderDestination. Framework conformances remain adapters, not proof of an app-wide protocol architecture.
Access control
| Symbol | Access | Verified effect | Likely rationale |
|---|---|---|---|
@Environment(\.openWindow) private var openWindow (RealityKit-Stereo-Rendering/DeferredLighting/DeferredLightingView.swift:285) |
private |
Use stays inside the declaration and same-file extensions permitted by Swift. | Inference: Hide lifecycle-sensitive state or an implementation step. |
fileprivate let buffer: MTLBuffer (RealityKit-Stereo-Rendering/DeferredLighting/Renderer/Utilities/BufferView.swift:14) |
fileprivate |
Use is limited to declarations in this source file. | Inference: Share with same-file helpers without making the symbol module-wide. |
public let assetsBundle = Bundle.module (Packages/RealityKit-Assets/Sources/RealityKit-Assets/RealityKit-Assets.swift:11) |
public |
The symbol is available to importing modules, subject to its containing type’s visibility. | Inference: Expose an intentional package or cross-target surface. |
No reviewed Swift access example uses private(set), open; declarations without a modifier are internal.
Objective-C/C header and implementation visibility is documented separately from Swift lexical access control.
Reference code
RealityKit-Stereo-Rendering/DeferredLighting/DeferredLightingView.swift:285 — representative visibility boundary.
struct DeferredLightingView: View {
// ...
@Environment(\.openWindow) private var openWindow
// ...
}Logic ownership and placement
| Logic | Owning type or file | Placement rationale |
|---|---|---|
| Application/command lifecycle | MetalVRRPortalSampleApp |
The executable boundary determines app, controller, scene, or command lifetime. |
| Presentation and user intent | DeferredLightingView |
The view/controller forwards input and owns only presentation-local work. |
| Framework/session/render coordination | Renderer |
The role-named collaborator isolates long-lived or per-frame work from presentation code. |
| Per-entity data and repeated behavior | RealityKit-Stereo-Rendering/DeferredLighting/DeferredLightingView.swift:254 |
Components carry data; the system queries and updates matching entities. |
| GPU and low-level resource work | RealityKit-Stereo-Rendering/DeferredLighting/Renderer/Renderer.swift:37 |
Buffer, texture, shader, or frame-resource mutation stays in a rendering/compute boundary. |
| Authored scene composition | Packages/RealityKit-Assets/Package.realitycomposerpro/ProjectData/main.json |
Reality Composer Pro owns hierarchy, materials, animation, or behavior that Swift loads and coordinates. |
Design patterns
| Pattern | Source evidence | Purpose or tradeoff |
|---|---|---|
| SwiftUI scene composition | RealityKit-Stereo-Rendering/RealityKitStereoRenderingApp.swift:13 |
Keeps windows, volumes, and immersive presentation visible at the app boundary. |
| UI–RealityKit bridge | RealityKit-Stereo-Rendering/DeferredLighting/DeferredLightingView.swift:327 |
Builds or hosts the RealityKit entity graph from SwiftUI or a platform view/controller lifecycle. |
| Entity-component-system | RealityKit-Stereo-Rendering/DeferredLighting/DeferredLightingView.swift:254 |
Stores per-entity data in components and advances repeated behavior in registered systems. |
| Session owner boundary | RealityKit-Stereo-Rendering/HeadTracker.swift:21 |
Keeps authorization, session lifetime, and framework callbacks in a stable model, manager, or controller. |
| Protocol capability boundary | RealityKit-Stereo-Rendering/ResolutionProbeGrid.swift:47 |
Defines a local capability with concrete conformers; this is the verified protocol-oriented seam. |
| Pluggable stereo renderer | RealityKit-Stereo-Rendering/RealityKitStereoRenderingApp.swift:39 |
Isolates renderer implementations behind a common frame lifecycle while head pose and ECS lighting remain shared inputs. |
Naming conventions
- Role suffixes are evidence, not decoration: App:
MetalVRRPortalSampleApp; Controller:ViewController; Provider:HeadPositionProvider; Renderer:Renderer,SinglePassDeferredRenderer,TraditionalDeferredRenderer; System:DeferredLightingSystem,ResolutionProbeSystem. - ECS names pair data and behavior: components
DeferredLightingComponent; systemsDeferredLightingSystem,ResolutionProbeSystem. - Protocols:
DrawableProviding,RenderDestination,RateFactorProviding. - Commands use verb-led methods:
beginFrame,beginDrawableCommands,endFrame,encodePass,encodeStage,encodeGBufferStage,encodeDirectionalLightingStage,encodeLightMaskStage. - Files and folders use primary-type or responsibility names such as
Views,Models,Rendering,Components,Systems,Packages, or feature names where those boundaries exist.
Architecture takeaways
- Treat
MetalVRRPortalSampleAppas the owner of executable or scene lifecycle, not automatically as the owner of every entity created later. - Keep view/controller input near presentation, but move sessions, reconstruction, capture, rendering, shared game state, or documents into stable owners when their lifetime exceeds one callback or render pass.
- Use components for per-entity data and systems for repeated simulation instead of centralizing every update in a view model or controller.
- Count authored Reality Composer Pro assets as implementation; the Swift shell is not the complete architecture.
Source map
| Source file | Relevant symbols |
|---|---|
RealityKit-Stereo-Rendering/RealityKitStereoRenderingApp.swift:13 |
MetalVRRPortalSampleApp, WindowView, OnDismiss |
RealityKit-Stereo-Rendering/DeferredLighting/Renderer/Renderer.swift:13 |
Renderer, DrawableProviding |
RealityKit-Stereo-Rendering/DeferredLighting/Renderer/SinglePassDeferredRenderer.swift:11 |
SinglePassDeferredRenderer |
RealityKit-Stereo-Rendering/DeferredLighting/Renderer/TraditionalDeferredRenderer.swift:14 |
TraditionalDeferredRenderer |
RealityKit-Stereo-Rendering/DeferredLighting/DeferredLightingView.swift:12 |
DeferredLightingComponent, DeferredLightingEntity, DeferredLightingSystem, DeferredLightingView |
RealityKit-Stereo-Rendering/DeferredLighting/Renderer/Utilities/RenderDestination.swift:13 |
RenderDestination |
RealityKit-Stereo-Rendering/MetalVRREntity.swift:16 |
RateFactors, RateFactorProviding, RateIndicator, RateMapIndicators, PercentageIndicator, RenderTarget |