ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

ARM64 上运行 Windows 程序:Wine、FEX-Emu 与 DXMT 兼容层实战

ARM64 上运行 Windows 程序:Wine、FEX-Emu 与 DXMT 兼容层实战 1. 从“Madeira”这个名字说起一个跨平台兼容层的真实需求第一次看到“Madeira”这个项目名加上关键词里那一串 Wine、FEX-Emu、DXMT、x86-64、iOS我脑子里第一反应是这大概率又是一个在 ARM 设备上跑 x86 程序的兼容层实验。Madeira 是葡萄牙的一个群岛以葡萄酒闻名而 Wine 恰好也是“葡萄酒”的意思——这个命名不是巧合它暗示了项目的核心方向在非 x86 平台上把 Windows 生态那套东西“酿”出来。先把背景讲清楚。Wine 本身不是一个模拟器它是一个兼容层通过把 Windows 的 API 调用翻译成 POSIX 调用让 Windows 程序能在类 Unix 系统上运行。它不虚拟化硬件所以性能损耗比完整虚拟机小得多。但 Wine 有个硬伤它假设底层 CPU 架构和 Windows 程序一致也就是 x86 或 x86-64。一旦你跑到 ARM 设备上比如 Apple Silicon 的 Mac、树莓派、或者 iOS 设备Wine 就没法直接翻译指令了因为指令集根本不同。这时候就需要第二层翻译。FEX-Emu 就是干这个的它是一个 x86-64 到 ARM64 的用户态模拟器专门用来在 ARM 设备上跑 x86-64 的 Linux 程序。把 FEX-Emu 和 Wine 叠在一起理论上就能在 ARM 设备上跑 Windows 程序。DXMT 则是另一块拼图它是 DirectX 到 Metal 的翻译层负责把 Windows 游戏里的 D3D 调用转成 Apple 平台的 Metal 调用让图形渲染能跑起来。所以 Madeira 这个项目本质上是在探索一条链路ARM64 设备 → FEX-Emu 翻译 x86-64 指令 → Wine 提供 Windows API → DXMT 处理图形 → 最终运行 Windows 应用或游戏。这条链路里每一层都有坑而 Madeira 要做的就是把这些坑填平或者至少把配置流程标准化。适合谁来参考这篇内容如果你是在 Apple Silicon Mac 上折腾 Windows 游戏、在 ARM Linux 设备上跑老 Windows 软件、或者单纯对跨架构兼容层感兴趣那这套思路值得跟一遍。如果你只是想找个“一键安装包”那可能会失望因为这类项目目前还处在需要手动调参的阶段。2. FEX-Emu 在整条链路里到底扮演什么角色2.1 为什么不能只用 Wine 跑 ARM 上的 Windows 程序很多人会问Wine 不是已经能跑 Windows 程序了吗为什么还要 FEX-Emu答案在于指令集。Windows 程序编译出来是 x86 或 x86-64 的机器码CPU 只认自己的指令集。ARM64 的 CPU 不认识 x86-64 的指令就像你拿一本中文书给只懂英文的人看他每个字母都认识但连起来不知道什么意思。Wine 做的是 API 翻译它把CreateFile这样的 Windows API 调用转成open这样的 POSIX 调用。但 API 翻译的前提是程序本身的机器码能在 CPU 上跑起来。在 x86 机器上这不是问题在 ARM 机器上就是死结。FEX-Emu 解决的就是这个死结它在运行时把 x86-64 指令一条条翻译成 ARM64 指令或者更准确地说是 JIT 编译成 ARM64 代码再执行。这里有个关键区别FEX-Emu 是用户态模拟器不是系统级模拟器。它不需要模拟整个操作系统只需要模拟 CPU 指令和少量系统调用。所以它的开销比 QEMU 全系统模拟小得多启动一个程序通常只需要几百毫秒的翻译开销之后就是接近原生的执行速度。2.2 FEX-Emu 的配置要点和常见误区FEX-Emu 的安装本身不复杂在大多数 ARM Linux 发行版上都有包。但配置才是真正的门槛。我踩过的坑主要集中在几个地方第一RootFS 的选择。FEX-Emu 需要一个 x86-64 的根文件系统来提供 x86 版本的库文件。很多人直接用宿主系统的 ARM 库结果程序启动就报cannot open shared object file。正确的做法是准备一个 x86-64 的 chroot 环境或者用 FEX-Emu 提供的 RootFS 工具自动生成。这个 RootFS 里要有 x86 版本的 glibc、libstdc、以及 Wine 依赖的那些库。第二环境变量的设置。FEX-Emu 通过FEX_ROOTFS指定根文件系统路径通过FEX_APP_CONFIG指定配置文件。配置文件里可以调 JIT 缓存大小、多线程编译开关、以及指令集特性开关。我实测下来把TSOEnabled打开能提升不少兼容性虽然会牺牲一点性能但很多老程序依赖 x86 的内存序语义关掉就容易出诡异问题。第三和 Wine 的配合方式。有两种做法一种是在 FEX-Emu 的 RootFS 里装 Wine然后通过 FEX-Emu 启动 Wine另一种是在宿主系统装 Wine让 Wine 自己去调用 FEX-Emu 翻译。前者更干净后者更省空间。我推荐前者因为版本管理更清晰出问题容易定位。注意FEX-Emu 的 JIT 缓存默认放在~/.cache/fex-emu如果你频繁测试不同程序这个目录会膨胀得很快。定期清理或者把它挂到 tmpfs 上能省不少磁盘 IO。3. DXMT把 DirectX 调用翻译成 Metal 的中间层3.1 DXMT 和 DXVK、WineD3D 的区别在 Apple 平台上跑 Windows 游戏图形翻译层有好几个选择。WineD3D 是 Wine 自带的把 D3D 转成 OpenGL但 OpenGL 在 macOS 上已经被标记为废弃性能和新特性支持都很差。DXVK 是把 D3D 转成 Vulkan然后通过 MoltenVK 再转成 Metal链路长开销大。DXMT 则是直接 D3D 到 Metal少了一层中间商理论上延迟更低、兼容性更好。DXMT 目前主要支持 D3D11 和部分 D3D12 特性对老游戏的 D3D9 支持还在完善中。它的优势在于直接利用 Metal 的现代特性比如 Metal 的 argument buffer、heap、以及 tile-based 渲染架构这些在翻译 D3D11 的 resource binding 和 render pass 时能省不少开销。3.2 在 Madeira 链路里配置 DXMT 的实操细节DXMT 的安装通常是把编译好的d3d11.dll、dxgi.dll、d3d10core.dll等文件放到 Wine 的system32目录下然后设置WINEDLLOVERRIDES让 Wine 优先加载这些原生 DLL。具体步骤从 DXMT 的发布页下载对应版本的二进制包注意要选和你的 Wine 版本匹配的构建。把 DLL 文件复制到 Wine prefix 的drive_c/windows/system32目录。在 Wine 的注册表里或者通过环境变量设置WINEDLLOVERRIDESd3d11n,b;dxgin,b意思是优先用原生 DLL失败再回退到内置。如果你的游戏需要 D3D12还要额外配置vkd3d-proton或者 DXMT 的 D3D12 后端但后者目前还不成熟。我实测下来DXMT 在 M1 和 M2 上的表现比 DXVKMoltenVK 好不少尤其是那些对帧率敏感的游戏。但它的兼容性列表还在增长中有些游戏会出现贴图错误或者着色器编译卡顿。遇到这种情况可以试试在 DXMT 的配置里打开DXMT_SHADER_CACHE把编译好的着色器缓存下来第二次启动就流畅了。提示DXMT 的日志通过DXMT_LOG_LEVEL控制调试时设成debug能看到每个 D3D 调用的翻译结果但日志量很大建议只在排查问题时开。4. 把 Wine、FEX-Emu、DXMT 串起来完整链路搭建4.1 环境准备从零开始的目录结构在动手之前先把目录结构规划好。我习惯这样组织~/madeira/ ├── fex-rootfs/ # FEX-Emu 的 x86-64 根文件系统 ├── wine-prefix/ # Wine 的 prefix 目录 ├── dxmt/ # DXMT 的 DLL 文件 ├── games/ # Windows 程序或游戏安装目录 └── logs/ # 日志输出目录FEX-Emu 的 RootFS 可以用fex-rootfs工具生成也可以从社区下载现成的。生成命令大致是fex-rootfs --distro ubuntu --arch x86_64 --output ~/madeira/fex-rootfs这个过程会下载一个基础的 Ubuntu x86-64 文件系统大概几百 MB。下载完成后你需要在这个 RootFS 里安装 Wine。因为 RootFS 是 x86-64 的所以可以直接用 apt 安装 x86 版本的 Winefex-rootfs --rootfs ~/madeira/fex-rootfs --shell # 进入 chroot 后 apt update apt install wine64 wine32 exit4.2 启动脚本的编写和参数调优每次手动敲一长串环境变量不现实写个启动脚本是必须的。我的脚本大概长这样#!/bin/bash export FEX_ROOTFS$HOME/madeira/fex-rootfs export FEX_APP_CONFIG$HOME/madeira/fex-config.json export WINEPREFIX$HOME/madeira/wine-prefix export WINEDLLOVERRIDESd3d11n,b;dxgin,b;d3d10coren,b export DXMT_LOG_LEVELwarn export DXMT_SHADER_CACHE$HOME/madeira/dxmt-cache fex-emu --rootfs $FEX_ROOTFS -- \ wine $HOME/madeira/games/your-game.exe $FEX 的配置文件fex-config.json里可以调这些参数参数作用推荐值TSOEnabled开启 x86 内存序模拟trueJITCacheSizeJIT 缓存大小256(MB)Multiblock多块编译优化trueSMCChecks自修改代码检测mtrackX87ReducedPrecisionx87 精度降低falseTSOEnabled我建议开着虽然会损失大概 10% 到 15% 的性能但能避免很多多线程程序的诡异崩溃。SMCChecks设成mtrack是因为有些老游戏会动态修改代码段不检测的话 JIT 出来的代码就过期了。4.3 首次运行时的常见报错和排查顺序第一次跑起来大概率不会顺利。我总结了一个排查顺序按这个顺序走能省很多时间FEX-Emu 本身能不能跑先用fex-emu --rootfs ... -- /bin/echo hello测试如果这步就失败说明 RootFS 或者 FEX 安装有问题。Wine 能不能在 FEX 里启动fex-emu --rootfs ... -- wine --version如果报库缺失说明 RootFS 里 Wine 的依赖没装全。Wine prefix 能不能初始化fex-emu --rootfs ... -- wineboot这步会创建 prefix 目录如果卡住或者报错通常是权限问题或者磁盘空间不足。DXMT 能不能加载跑一个简单的 D3D 测试程序看日志里有没有DXMT相关的输出。如果没有检查WINEDLLOVERRIDES是否生效。游戏本身能不能启动如果前面都过了游戏还起不来那就是游戏特定的兼容性问题需要单独调。这个顺序的核心逻辑是从底层往上层排查每一层都确认没问题再往上走。很多人一上来就调游戏参数结果底层 FEX 都没跑通白费功夫。5. 性能调优和兼容性打磨的实战经验5.1 帧率上不去的几个真实原因链路跑通之后下一个问题就是性能。我在 M1 Mac 上测试下来影响帧率的主要因素有这几个JIT 编译开销。FEX-Emu 第一次执行某段代码时需要翻译这会导致启动阶段和场景切换时卡顿。解决办法是开启 JIT 缓存让翻译结果持久化。FEX 的缓存默认是开的但如果你把JITCacheSize设得太小缓存频繁淘汰反而更慢。我建议至少给 256MB。图形翻译开销。DXMT 虽然比 DXVK 链路短但 D3D 到 Metal 的翻译仍然有开销。特别是 D3D11 的UpdateSubresource和Map/Unmap操作在 Metal 里需要额外的同步。如果游戏大量使用动态纹理更新帧率会明显下降。这种情况可以试试在 DXMT 配置里打开DXMT_FAST_MATH牺牲一点精度换速度。CPU 和 GPU 的负载不均衡。FEX-Emu 是 CPU 密集型的DXMT 是 GPU 密集型的。如果游戏逻辑复杂但图形简单瓶颈在 CPU反之在 GPU。用htop和 Metal System Trace 分别看 CPU 和 GPU 占用能快速定位瓶颈在哪。5.2 兼容性问题的分类处理思路兼容性问题大致分三类每类的处理方式不同第一类是启动即崩溃。这种通常是缺少 DLL 或者 API 不支持。用WINEDEBUGloaddll看加载了哪些 DLL缺哪个补哪个。如果是 API 不支持看 Wine 的版本是否够新或者找对应的 winetricks 脚本安装运行库。第二类是能启动但画面异常。黑屏、花屏、贴图错误这些多半是 DXMT 的翻译问题。先看 DXMT 日志里有没有Unsupported或者Error关键字然后去 DXMT 的 issue 列表里搜游戏名大概率有人遇到过。第三类是能玩但随机崩溃。这种最难查通常是内存序或者线程同步问题。先把TSOEnabled打开如果还崩试试把 FEX 的Multiblock关掉用单块编译模式虽然慢但更稳定。5.3 我踩过的最坑的一个问题Wine 乱码热词里有个“wine 乱码”这个我太有发言权了。Wine 在非中文 locale 下运行中文程序经常出现方块或者问号。根本原因是 Wine 默认的字体映射找不到合适的中文字体。解决办法有两个一是把 Windows 的中文字体比如simsun.ttc、msyh.ttf复制到 Wine prefix 的drive_c/windows/Fonts目录然后在注册表里把FontSubstitutes里的MS Shell Dlg映射到这些字体。二是直接在 Linux 侧安装中文字体然后通过 fontconfig 让 Wine 能找到。我通常两个都做双保险。具体命令# 复制字体 cp /usr/share/fonts/truetype/wqy/wqy-microhei.ttc \ ~/madeira/wine-prefix/drive_c/windows/Fonts/ # 注册表映射 fex-emu --rootfs ... -- wine reg add \ HKCU\\Software\\Wine\\Fonts\\Replacements \ /v MS Shell Dlg /d WenQuanYi Micro Hei /f这个坑之所以坑是因为它不影响程序逻辑只影响显示但看着满屏方块根本没法用。而且不同程序用的字体名不一样有时候要试好几轮才能找到正确的映射。6. 这套方案还能怎么扩展Madeira 这个项目目前还比较早期但它的思路可以延伸到很多场景。比如在 iOS 设备上虽然系统限制很多但通过类似的兼容层思路理论上也能跑一些简单的 Windows 程序——当然实际难度要大得多涉及签名、沙盒、以及 JIT 权限等问题。热词里提到的“ios 开发者模式”“ios 自动化”这些和兼容层本身关系不大但反映了大家对在封闭平台上跑开放生态的兴趣。另一个方向是把 FEX-Emu 和 Wine 的配置过程脚本化、容器化。我现在正在尝试用 Docker 把整个链路打包成一个镜像这样换设备的时候直接拉镜像就能跑不用重新配环境。难点在于 FEX-Emu 需要访问宿主的内核特性容器里跑需要额外的权限配置。还有一个值得关注的点是 DXMT 对 D3D12 的支持。目前 D3D12 游戏在 Apple 平台上基本没法玩如果 DXMT 能把 D3D12 到 Metal 的翻译做稳定那能解锁的游戏库会大很多。我试过几个 D3D12 游戏大部分在创建 pipeline state 的时候就失败了说明还有不少工作要做。最后分享一个我在调试过程中养成的习惯每次改配置之前先把当前的配置文件和启动脚本备份一份命名带上日期和改动内容。因为这类兼容层调试经常需要反复回退没有备份的话改着改着就忘了哪个参数是有效的。我现在的备份目录里已经有几十个版本的配置虽然看起来乱但关键时刻能救命。
返回列表