Repository navigation
[Feature] Preview the scene on an Apple Vision Pro, drawn by the Mac - #153
Conversation
…velopment bundle A bare executable, as `swift run` and Xcode start the editor, has no bundle identifier and no LaunchServices record. The Apple Vision Pro preview's device picker traps without the identifier and refuses to connect without the record. - `Sources/UntoldEditor/Info.plist` is embedded in the executable (`-sectcreate __TEXT __info_plist`, from the manifest's linker settings): the identifier of the packaged app, `com.untoldengine.studio`. The launch line logs it. - The settings move with the identity: `EditorSettingsDomain` copies what the old domain, `UntoldEditor`, holds into the new one, once. - `scripts/dev-app.sh [--run] [debug|release]` and `make run-app` wrap the build in `Untold Engine Studio.app` inside the build folder: the executable copied, the resource bundles and the exporter scripts linked, the bundle registered. `ComponentSDK` and `EditorEnginePackage` follow its `BuildProducts` link to the modules and the `Package.resolved`.
macOS 26 lets a Mac app open a `RemoteImmersiveSpace` on a Vision Pro nearby and draw it with `CompositorLayer`, as a visionOS app would. The editor is a SwiftUI `App` around its AppKit delegate, window and menus, and declares that space. View > Preview on Apple Vision Pro, or the Preview pill beside the build target, asks for it; the headset's wearer accepts; the scene shows in full immersion, drawn by the editor's own renderer through the engine's public XR API, on a thread of its own, frame by frame as the headset asks. - The headset rides the editor's camera. W A S D fly it level, Q E up and down, the pointer turns it as a second head, hidden and held while the preview runs, on top of the wearer's own head. Esc ends the preview. A card over the viewport says so. - The Mac's viewport mirrors the left eye; the panels edit the live scene; clicks select nothing and the overlays are off. Play, a scene load and the headset closing the space end it. - Steering and drawing take turns under a lock, the camera's pose is read from its position and rotation, and the engine's own per-frame fly and its head write stand down meanwhile. - The two frameworks are weak-linked: the editor still starts on macOS 14. - Tests on a fake frame source: the placement, the flight, the loop rendering into textures, the mirror under Metal's validation, the session's states. Plan and decisions: EditorVisionProPreview.md in the engine's proposal drafts.
|
Really nice work on this one — the frame-source abstraction ( A few things worth a look before merge: 1. 2. 3. Nothing here feels blocking to me — #1 is the one I'd actually want addressed (or at least logged) before this ships, the other two are more "leave a trail of breadcrumbs for future us." Great PR overall, thanks for the thorough plan doc and test coverage! |
… the stop, undrawn drawables are cleared, two eyes at most From Harold's review of untoldengine#153. - `stopLoopAndRestore` went on after its wait and re-made the renderer's stereo targets under a loop that might still draw into them. A timeout is logged now, the session stays in `ending`, and the editor is put back when the loop's thread ends. The wait is `loopStopTimeout`, which tests shorten. - Every drawable the compositor hands over gets the head's anchor, and a drawable beyond the first is cleared before it is presented, so that it shows nothing rather than what its textures held. - The loop draws the first two eyes, the engine's stereo, and says once when a frame brings more, instead of drawing a third eye over the second. Tests: a loop held inside a frame outlives the stop and the editor is put back when it is released; a three-eye frame draws two and leaves the third untouched.
|
Thanks Harold, all three were right. Fixed in d238e1e.
|
…er, intro gallery skipped and the Explore asset panel off (untoldengine#154, untoldengine#155)
* [Bugfix] Keep the editor's menu bar when SwiftUI installs its own Since #153 the editor is a SwiftUI App, for the space the Apple Vision Pro preview shows, with its menus still built by the AppKit delegate. SwiftUI installs a menu bar of its own from its default commands; on macOS 26 it does so after the delegate has set the editor's, and the bar then reads Edit, View, Window and Help: no File menu, none of the editor's View items, and the app menu named after the Info.plist. On macOS 27 the editor's menu survived, which is why the change was not seen before the merge. EditorMainMenuKeeper installs the editor's menu and watches the application's main menu; whenever another menu replaces it, the editor's is put back on the next turn of the run loop (logged once). Five tests on the real NSApplication, one of them showing that without the keeper another menu stays. * [Bugfix] Menu bar: move the editor's menus into the bar SwiftUI installs Putting the editor's menu back by KVO did not hold on macOS 26: SwiftUI keeps the bar it installed. The keeper no longer replaces that NSMenu; it moves the editor's menus into whatever bar is installed, takes the bar's own items out, drops the delegate that fills it in, and keeps the Window menu the editor's. It looks again when the main menu changes and after every event the application handles (NSApplication.didUpdate, didBecomeActive, NSWindow.didBecomeKey), so it does not depend on the setter being observable. Nine tests, on a bar installed before, one installed later, one refilled in place without a setter, one that fills itself in through its delegate. --------- Co-authored-by: Javier Segura <javier@miolabs.com>
Part of #131 (stage 1.7c of the editor UI redesign).
What
macOS 26 renders immersive content for an Apple Vision Pro nearby ("Mac spatial rendering"): the editor opens a
RemoteImmersiveSpaceand draws it withCompositorLayer, as a visionOS app would, from its own renderer through the engine's public XR API. Nothing is installed on the headset.View > Preview on Apple Vision Pro, or the Preview pill beside the build target, asks for it; the headset's wearer accepts; the scene shows in full immersion. Esc, the menu, the pill or the label's Stop end it; so do Play, a scene load, and the headset closing the space.
How it behaves
renderXRper eye, the HZB, present.Running it
A bare executable, as
swift runand Xcode start the editor, has no bundle identifier and no LaunchServices record. Apple's device picker traps without the first and refuses to connect without the second. So, in the first commit:Sources/UntoldEditor/Info.plistis embedded in the executable (the manifest's linker settings,-sectcreate __TEXT __info_plist; anunsafeFlags, allowed in a root package and in Xcode's local packages). The identifier is the packaged app's,com.untoldengine.studio; the settings follow it (EditorSettingsDomaincopies the oldUntoldEditordomain once).scripts/dev-app.sh --run(ormake run-app) builds, wraps the result inUntold Engine Studio.appinside the build folder (the executable copied, the resource bundles and the exporter scripts linked, the bundle registered), and starts it with its output in the terminal.ComponentSDKandEditorEnginePackagefollow itsBuildProductslink. The packaged app fromcreate_app_bundle.shworks as it is. Xcode's Run is still a bare executable: for the headset, start the editor with the script (README).Requirements: macOS 26 on Apple silicon, a Vision Pro on visionOS 26, both on the same Apple Account with Wi‑Fi and Bluetooth on, nearby. The editor keeps macOS 14 as its minimum: the two frameworks are weak-linked, and the menu item and the pill are hidden where the Mac cannot preview.
Design notes
The plan note with the verified facts, the stages and decisions D1–D11 is
docs/proposals/EditorVisionProPreview.mdin the engine's proposal drafts. The ones a reviewer will meet in the code:UntoldEngineXRfor macOS is a later engine PR, so that any game gets "Play on Vision Pro" from its Mac build.VisionProPreviewState.cameraLock): the engine keeps the camera's view matrix in the camera andrenderXRsets it per eye, so a move in the middle of an eye draws part of it from the camera's own view.runFrameruns it on the XR path too, and its fly goes where the camera looks..fullimmersion only: Apple's visionOS 26 release notes say.progressivecrashes the Mac app.Tests
1234 pass. New: the placement math, the flight, the loop drawing both eyes into textures from a fake frame source with the pixels read back, the mirror under Metal's validation (
MTL_DEBUG_LAYER=1 swift test), the session's states with a live fake source on a thread, the settings domain, the SDK and engine-package link, the pointer capture's lifecycle. SwiftFormat clean under the 0.60.1 rules.Review
Harold's three points (d238e1e): a stop whose wait runs out is logged and the editor is put back when the loop's thread ends, not before; a drawable beyond the first is cleared before it is presented; the loop draws two eyes at most and says once when a frame brings more.
Tried
On an M4 Max with a Vision Pro on visionOS 26: open, look around, fly, turn, stop; five rounds of fixes from the tries are recorded in the plan note.
Not in this PR
Reconnecting after a dropped stream, the headset's frame rate in the status bar, gaze and pinch from the headset, the window and full-screen preview destinations the pill is ready for, and the engine fold-in: stages B to D of the plan note.