CrossOver 中的 OpenXR 能连接 OpenXR-OSX 吗?
所属系列
- macOS 上的 VR 系列 (5 共 5)
这篇笔记接着《通过 CrossOver 在 Apple silicon Mac 上使用 Quest PCVR》和《Virtual Desktop / CrossOver 调查记录》继续。
问题
我想把问题缩小到一个更具体的范围:
Elite Dangerous已经能在CrossOver中进入OpenXR路径OpenComposite已经能把游戏的OpenVR调用转成OpenXROpenXR-OSX现在宣称提供macOS上的OpenXR运行时
那么,很自然的下一问就是:
能否将 CrossOver 中的 Windows OpenXR 应用指向 Mac 主机上的 OpenXR-OSX?
简短回答
不能直接这样做。
容器里的 Elite -> OpenComposite -> Windows OpenXR loader -> ActiveRuntime 链条是真实存在的,但终点仍是 Windows 运行时边界。OpenXR-OSX 是 macOS 运行时,在主机侧提供运行时清单和一个 dylib。CrossOver 里的 Windows loader 不能像对待另一个普通运行时 JSON 那样直接使用它。
Elite 的结果究竟证明了什么
之前的 Elite Dangerous 测试仍然有价值。
它说明:
Elite能进入 VR 路径OpenComposite能截获该路径并转发到OpenXR- Windows 侧 loader 能找到
ActiveRuntime并尝试启动会话
这意味着应用侧并非完全走不通。容器中的 Windows OpenXR 应用能够推进到尝试与运行时通信的阶段。
但它没有证明,应用已经接近与主机原生 macOS 运行时通信。
桥接断在哪里
1. loader/运行时边界仍然是 Windows
在 Windows 上,OpenXR loader 从注册表读取活动运行时,打开运行时 JSON,再加载 library_path 指向的共享库。
这一点很关键,因为 Windows 进程预期这条路径的终点是 Windows 运行时二进制。
在我们的情况下:
- 容器可以把
ActiveRuntime指向 Windows JSON - 该 JSON 应当指向 Windows 运行时库
OpenXR-OSX生成的却是openxr_osx.json,指向 macOS 侧的libopenxr_osx.dylib
所以,还没涉及头显追踪或帧时序,Windows 进程与 macOS 运行时就已经对“运行时二进制究竟是什么”存在分歧。
为什么不只是清单文件的问题
即使能用某种技巧,让 Windows 侧接受主机侧运行时目标,也仍然没有解决运行时真正的工作。
OpenXR 运行时不只是追踪服务。它负责或深度参与:
- 系统发现
- 设备形态选择
- 会话创建
- 交换链创建
- 图形 API 绑定
- 帧时序
- 视图位姿
- 输入空间
- 帧提交
CrossOver 内的应用仍是 Windows 应用,因此它的图形需求通过 Windows 侧 API 和句柄表达。而主机侧 macOS 运行时,则围绕自己的运行时实现和图形互操作路径构建。
到这里,问题就不再是切换一个开关,而像是在设计新的兼容层。
真正的桥接需要什么
最小的可行架构,看上去更像自定义代理运行时,而非切换运行时。
1. 容器内的 Windows 运行时垫片
需要一个 Windows OpenXR 运行时 DLL,让 Windows loader 能正常发现并加载。
它的职责是:
- 满足 Windows loader 的要求
- 暴露预期的运行时协商入口点
- 实现足够的 OpenXR 运行时接口,让应用保持运行
- 把实际工作转发到别处
2. 容器与主机之间的 IPC
随后,这个垫片需要把运行时调用从 Windows 进程发送到 macOS 主机侧服务。
这意味着要为以下内容设计传输协议:
- 实例和会话生命周期
- 系统查询
- action 和空间状态
- 交换链协商
- 帧时序
- 帧提交
最合理的第一版可能是:
- 用
Unix domain socket处理本地控制和请求/响应流量 - 只有图像传输对普通 socket IPC 来说太重时,再引入共享内存
其他选择也存在,但不太适合作为默认方案:
localhost TCP容易观察和调试,但作为同机通信约定,没有本地 socket 那么紧密- 一开始就用共享内存,会过早优化,在桥接尚未证明任何东西之前,就让所有权和同步更加棘手
因此,基本形态会是:
- Windows 垫片 DLL 由
CrossOver内的OpenXRloader 加载 - 垫片连接主机守护进程,可能使用
/tmp/openxr-osx-bridge.sock这样的 socket 路径 - 运行时请求经过序列化后跨通道发送
- 主机守护进程返回转换后的结果,并维护主机侧对象状态
这也意味着原始运行时指针和句柄不应直接跨越边界。更稳妥的模型,是让桥接层定义自己的对象 ID:
- Windows 侧:伪造的
XrInstance、XrSession和XrSwapchain句柄,由垫片侧表格支撑 - 主机侧:对应的桥接 ID,映射到真实主机对象
这样协议更接近“带句柄间接层的 RPC”,而不是“共享实现内存,然后祈祷”。
3. 与 OpenXR-OSX 通信的主机侧运行时服务
macOS 侧需要某个组件接收这些调用,然后:
- 直接驱动
OpenXR-OSX,把它当作底层运行时后端 - 或自行重新实现足够的运行时行为,让 Windows 客户端以为自己在与普通运行时通信
4. 图形数据封送
这是最难看的一部分。
Windows VR 应用不只是请求位姿,然后就结束了。它还通过交换链提交渲染图像。桥接必须解决:帧如何离开 CrossOver 内转译后的 Windows 图形世界,再以 macOS 运行时能够呈现到头显路径上的形式到达。
我预计,项目到这里会很快变得痛苦。
这里的划分很重要:
- 控制平面:基于 socket 的 RPC 路径大概就够了
- 数据平面:提交的帧图像,可能迫使桥接改变形态
作为概念验证,帧路径甚至也可以先走同一个 IPC 通道,只为证明调用顺序和对象生命周期。如果推进到能提交真实帧,我预计下一轮重设计会围绕图像传输:
- 共享内存环形缓冲区
- 显式同步原语
- macOS 侧的主机原生呈现表面
到这里,项目就从“如何把 OpenXR 调用传过去”,变成“如何以尚可承受的延迟传输 VR 帧数据”。
共享普通内存,与共享一个真正能充当渲染目标的资源,也有重要区别。
在 CPU 内存层面,原理上原生 macOS 进程和 CrossOver/Wine 进程应该能映射同一片共享区域:
- POSIX 共享内存
mmap- 内存映射文件
因此,桥接可以在主机分配共享内存,两侧都映射它,把它当作基于复制的传输缓冲区。
但这不意味着就有了干净的零复制渲染路径。
Windows 应用仍然期待与 Windows 侧图形 API 关联的 OpenXR 交换链图像。即使两个进程都能看到同一片 CPU 可见内存,也不自动意味着:
- 应用能高效地向其中渲染
- 转译图形栈能在正确时机把已提交图像导出到那里
- 主机无需额外复制就能使用它
- 同步和所有权规则能准确对齐,满足 VR 时序
所以,IOSurface 这样的主机侧对象只能回答一部分问题。像素已经存在于 macOS 侧时,它们很有用;但更早的问题仍在:已提交的帧究竟如何先从 Windows/CrossOver 图形路径中出来?
因此,可能的区别是:
- 进程之间共享内存:可行
- 跨容器/主机边界共享零复制渲染资源:可行性低得多
如果这座桥最终存在,第一版很可能不得不使用基于复制的帧传输。这或许足以验证架构,但延迟预算能否满足实际 VR,仍是悬而未决的问题。
更新:复用现有的 D3D 到 Metal 转译接口
上面的图形数据封送部分,假定桥接必须从头设计帧传输。其实未必如此。对于每个通过 CrossOver 运行的游戏,它已经解决了“Windows D3D 纹理变成真实 Mac 原生 GPU 资源”的问题。代理运行时恰好可以接入这个位置,而不必再平行构建一套传输。
CrossOver 目前的三种转译层
- D3DMetal:Apple 的 Game Porting Toolkit,D3D11/12 直接到 Metal。闭源,无法修改,对这件事来说是死路。
- DXVK + MoltenVK:开源,D3D → Vulkan → Metal。在部分呈现路径中,MoltenVK 使用 IOSurface 支撑其图像。
- DXMT:较新、开源、原生使用 Metal 的 D3D11 实现,没有 Vulkan 中间跳转。三者中最有希望,因为既开放,又最贴近底层 Metal(不是故意玩双关)。
为什么这改变了问题的形态
使用 DXVK/DXMT 的应用,每次普通 Present() 调用都已经生成真实的主机侧 Metal 纹理。这正是原笔记遗漏的机制:转译层已经跨过了桥接所需要跨越的那个边界。
复杂之处在于,OpenXR 交换链不像普通 D3D 交换链那样由应用拥有。运行时创建图像,通过 xrEnumerateSwapchainImages 交给应用渲染。因此,处在真实 OpenXR 运行时位置的垫片 DLL 需要:
- 通过 DXMT/DXVK 向 D3D 设备请求交换链纹理,就像任何游戏请求渲染目标一样
- 进入转译层内部的纹理对象,取出底层
IOSurface引用
IOSurface 如何承担关键工作
IOSurface 是 macOS 原生机制,专为跨进程传递 GPU 内存设计。垫片一旦拿到 IOSurface ID,只需通过 IPC 发送这个 ID,不必发送像素。主机守护进程在自己这一侧调用 IOSurfaceLookup(),再把表面直接交给 OpenXR-OSX/Metal。
这是真正的数据平面零复制路径,而不是上一节所说的“共享内存环形缓冲区,再祈祷同步能对上”。
修订后的可能故障顺序
这并没有消除前面提到的 loader、会话和时序问题,只改变了帧传输的形态。桥接尝试仍可能遇到的障碍,大致重新排序如下:
- loader/运行时协商不匹配(不变)
xrGetSystem/设备形态不匹配(不变)xrCreateSession图形绑定不匹配(不变)- 让 DXMT(或 DXVK)按垫片而非应用的要求交出交换链纹理,替代原来的“交换链/图像传输”障碍
- 提取 IOSurface 句柄,并在转译层不发生冲突的情况下,合法地跨容器/主机边界共享
- 时序和合成器行为(不变)
- 延迟(不变,但可能优于基于复制的传输)
结论
它仍不是配置开关。但它把“发明帧传输”变成了“取出已经存在的句柄”,比原笔记里基于复制的最坏情形小了不少。前提是 DXMT 内部足够可访问,能从它创建的交换链纹理中取出 IOSurface。
可能的故障顺序
如果我真去构建这座桥,预计障碍会大致按以下顺序出现:
xrCreateInstance或 loader 协商不匹配xrGetSystem和设备形态报告不匹配- 图形绑定类型无法准确对齐,导致
xrCreateSession失败 - 交换链/图像传输问题
- 时序和合成器行为
- 即使技术栈勉强保持运行,延迟最终仍高到无法实际使用 VR
这个排序很重要,因为它说明这不只是“把 loader 做完”的项目。loader 发现机制工作之后,更深的问题仍在后面等着。
为什么这仍不像一条好的 Elite 路径
对于 Elite Dangerous,完整技术栈会是:
EliteOpenComposite- 容器里的 Windows
OpenXRloader - 自定义代理运行时垫片
- IPC 桥接
- 主机侧运行时适配器
OpenXR-OSX- Quest Android 客户端
还没谈动作到画面的延迟、控制器映射、音频和长时间会话稳定性,就已经有这么多活动部件。
之前的 Virtual Desktop 调查已经说明,试图借用 CrossOver 内不受支持的 Windows 运行时栈很脆弱。这个桥接想法避开了 Virtual Desktop Streamer,但又换成了另一类不受支持的系统工程。
现在什么比较现实
OpenXR-OSX 近期现实的用途,仍然像是:
- 原生
macOSOpenXR示例 - 为
macOS构建的原生Unity或GodotOpenXR应用 - 配合项目自己的 Quest 客户端,按其原本设计方式进行测试
这与“在 CrossOver 里运行 Windows PCVR 游戏,再接到主机运行时”很不一样。
结论
目前的 Elite 结果有趣之处在于,它证明 Windows 应用侧可以在 CrossOver 内到达 OpenXR 运行时边界。
但那个边界仍然不是我们需要的。
把 CrossOver 中的 Windows OpenXR 应用桥接到主机上的 OpenXR-OSX,需要自定义 Windows 运行时代理和主机侧适配器,图形及交换链传输很可能是最难的部分。这是一个运行时桥接项目,而不是配置练习。