最小 OpenXR-OSX MVP:从 macOS 在 Quest 上运行 hello_xr
所属系列
- macOS 上的 VR 系列 (4 共 5)
如果目标是证明 OpenXR-OSX 能从原生 macOS OpenXR 应用驱动 Quest 头显,这就是我会先尝试的最小测试。
这是那次成功运行的短视频:
范围
这不是 CrossOver、SteamVR 或 Elite Dangerous 的指南。
它关注一个更小的问题:
我能否运行原生 macOS OpenXR 示例,让它指向 OpenXR-OSX,并在 Quest 客户端上看到整条路径工作起来?
如果这一步失败,就没有必要把 Windows 端实验搞得更复杂。
简要步骤
最小但可信的 MVP 是这样的:
- 在 Mac 上构建
OpenXR-OSX运行时 - 用
XR_RUNTIME_JSON为从 shell 启动的应用注册运行时 - 从同一仓库构建并安装 Quest Android 客户端
- 构建原生 macOS
hello_xr - 从同一个 shell 运行
hello_xr,确保它能找到运行时
如果这条路径不通,我会先停下来调试,再去碰 CrossOver。
它应该证明什么
如果成功,这个测试应该能同时证明几件事:
OpenXR-OSX运行时可以构建并加载- 原生 macOS
OpenXR应用能发现运行时 - Quest 客户端能在局域网找到运行时
- 运行时能创建会话,并开始通过 Quest 路径呈现画面
它不能证明 Windows 游戏、OpenComposite 或 CrossOver 桥接设想可行。
前提条件
上游当前为所支持的路径列出了这些要求:
Apple Silicon MacmacOS 13+Xcode及命令行工具cmakeninjaJava 17Android SDKAndroid NDKadb- 处于开发者模式的
Quest
文档:
最小操作顺序
1. 构建 macOS 运行时
从本地 fork 开始:
cd ~/Source/Forks/fork-OpenXR-OSX
cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug
cmake --build build
ctest --test-dir build --output-on-failure
预期输出:
build/runtime/libopenxr_osx.dylibbuild/runtime/openxr_osx.jsonbuild/runtime/openxr_osx.toml
2. 为终端启动的应用注册运行时
首次测试,我会避开 GUI 注册,采用上游文档中的 shell 方式:
export XR_RUNTIME_JSON="$HOME/Source/Forks/fork-OpenXR-OSX/build/runtime/openxr_osx.json"
相比辅助脚本或 macOS 配套应用,这能让第一次运行更简单。
3. 构建并安装 Quest 客户端
cd ~/Source/Forks/fork-OpenXR-OSX/clients/android-openxr
./gradlew assembleDebug
adb install app/build/outputs/apk/debug/app-debug.apk
上游说明,clients/android-openxr/local.properties 必须指向本地 Android SDK。
4. 启动 Quest 客户端
在头显上:
- 打开已安装的 Android 客户端
- 保持头显唤醒
- 让 Mac 和 Quest 连接同一个局域网
adb 安装与启动命令示例:
adb install app/build/outputs/apk/debug/app-debug.apk
adb shell monkey -p com.openxrosx.client -c android.intent.category.LAUNCHER 1
如果连接了多个 adb 设备,明确指定头显:
adb -s <device-id> install app/build/outputs/apk/debug/app-debug.apk
adb -s <device-id> shell monkey -p com.openxrosx.client -c android.intent.category.LAUNCHER 1
按文档描述,Quest 客户端会在局域网中发现运行时、建立连接、接收编码后的视频帧,并回传头部和控制器数据。
5. 构建原生 macOS hello_xr
示例本身使用 Khronos 的 OpenXR-SDK-Source,hello_xr 就在其中。
文档给出的最小 macOS 项目生成流程:
git clone https://github.com/KhronosGroup/OpenXR-SDK-Source.git
cd OpenXR-SDK-Source
mkdir -p build/macos
cd build/macos
cmake -G Xcode ../..
然后从生成的 Xcode 项目中构建 hello_xr 示例目标。
Khronos 参考资料:
6. 从同一个 shell 运行 hello_xr
这一点很重要,因为示例启动时,shell 中仍需设置 XR_RUNTIME_JSON。
第一次应从步骤 2 使用的同一个终端会话启动,不要通过 Finder、Spotlight 或另一个 shell 窗口启动。
怎样才算成功
最低成功标准是:
hello_xr不会在发现运行时这一步立即失败- Quest 客户端建立连接,而不是停在待机或加载颜色画面
- 会话创建成功
- 头显运动能影响示例的运行,而不是还没呈现画面就失败
项目本身提醒,目前 Quest 界面仍然很简陋,所以即使“能用”,看起来也可能很粗糙。
实际跑通了什么
我确实用本地构建的 OpenXR-OSX 运行时和侧载的 Quest 客户端,执行了这次测试。
在 macOS 上有效的 hello_xr 调用是:
XR_RUNTIME_JSON=~/Source/Forks/fork-OpenXR-OSX/build/runtime/openxr_osx.json \
~/Source/Forks/OpenXR-SDK-Source/build/macos/src/tests/hello_xr/Debug/hello_xr \
-g Metal
-g Metal 很重要。不指定图形后端,hello_xr 只会打印使用说明,然后退出。
Quest 客户端最初显示文档描述的蓝色待机画面。hello_xr 启动真正的 OpenXR 会话后,头显连接到主机,从待机进入实际的 hello_xr 场景。
我在头显中看到的画面符合示例预期:青绿色背景,加上几个简单的 3D 立方体。这足以算作原生 macOS -> OpenXR-OSX -> Quest 路径成功。
这是那次成功运行的短视频:
主机端证据
成功运行时,最有用的主机日志是:
StreamingServer: Broadcasting on ...OpenXR OSX: Streaming server started, waiting for headset connection...OpenXR OSX: Client connected (Quest), receiving trackingStreamingServer: Client connected: Quest (... ) refresh=72HzVideoEncoder: Initialized H.265 encoder ... @ 72fps
这确认了完整链路:
- 原生
macOShello_xr OpenXR-OSX运行时协商- 创建
Metal会话 - 通过局域网发现 Quest
- 跟踪数据回传主机
- H.265 视频编码
- 视频帧进入头显串流路径的队列
实际运行也让我更清楚一个有用的架构细节:StreamingServer 是 OpenXR-OSX 运行时的一部分,不是独立应用,也不属于 hello_xr 本身。hello_xr 只是一个普通的 OpenXR 应用。它通过运行时启动真正的会话后,OpenXR-OSX 就会为 Quest 客户端启动自己的发现、跟踪和视频串流流程。
成功运行时的性能记录
这次成功运行在头显端协商到了 72Hz。
主机和 Quest 的实时日志中,有用的数据如下:
- 协商刷新率:
72Hz - 编码器目标:
72fps - Quest 端解码耗时:约
10-12ms - Quest 端合成器耗时:约
1ms - Quest 端从接收到提交的总耗时:约
11-13ms - 渲染姿态匹配率:通常为
98-100%
串流足够稳定,能持续较长时间显示示例场景,但并非毫无问题:
- 偶尔出现
NACK重传 - 偶尔请求关键帧
- 早期出现一次编码丢帧
- 较长时间运行中,自适应码率从初始
50Mbps逐步降到略高于30Mbps的范围
我不认为这些丢帧本身足以对运行时下定论。测试网络并未专门为低延迟无线串流做细致配置,因此,看到画面不时不稳定,以及部分重传和关键帧请求时,应把这个限制考虑进去。
90Hz 复测
后来,我修改了 Quest 客户端,让它使用 XR_FB_display_refresh_rate,支持时请求 90Hz,并记录协商结果。
复测成功了:
- Quest 客户端报告服务器为
90Hz - Quest 客户端发送了
ClientConnect ... (refresh=90Hz) - 主机记录
Client connected: Quest ... refresh=90Hz - 编码器以
90fps初始化
所以,90Hz 确实跑起来了,并不只是主机端的假设。
在 90Hz 下,日志仍有一些重传、关键帧请求和跳帧,但相同的网络限制依然存在:这不是严格受控的无线测试环境。因此,正确结论是 90Hz 得到了支持并且能工作,而不能认定串流质量的限制完全来自运行时。
所以,目前的结果还不是“生产级 PCVR”,但已经完全足以证明这套架构在自身设定的范围内可以工作,包括成功的 90Hz 路径。
过程中发现的实际陷阱
- 桌面版
hello_xr需要明确指定图形后端;这里使用-g Metal。 - 非交互式运行
hello_xr,可能会让它在打印Press any key to shutdown...后立即结束,因此测试连接最好使用真实的交互终端会话。 - Quest 客户端确实会记录有用的解码和网络统计,但最清楚的连接确认来自主机运行时日志。
- 无线
adb可以正常安装和启动,因此完成配对后,不必使用 USB。
出错时的排查顺序
如果出了问题,我会按这个顺序检查:
- 运行时构建产物确实存在
XR_RUNTIME_JSON指向正确的openxr_osx.json- Quest APK 确实安装成功
- 头显应用已经打开,头显保持唤醒
- Mac 和 Quest 在同一个网络中互相可达
- 然后才开始阅读运行时或示例日志
为什么继续桥接之前,我想先做这个
CrossOver 笔记已经表明,Windows 端能走到运行时边界,但桥接问题远比原生路径困难。
如果 OpenXR-OSX 连原生 macOS hello_xr 都无法接到 Quest 客户端上,那就去尝试把 Elite、OpenComposite 和自定义运行时适配层导向它,顺序就反了。
结论
这是合适的第一项测试,因为它把问题缩减到项目目前声称支持的最小架构:原生 macOS OpenXR 应用、OpenXR-OSX 运行时,以及 Quest Android 客户端。