ARM 设备运行 x86 Windows 程序:FEX-Emu + Wine + DXMT 三层翻译实战
1. 项目缘起从“Madeira”这个名字说起第一次看到“Madeira”这个词很多人第一反应是葡萄牙那个盛产葡萄酒的海岛。但在我这个常年折腾跨平台兼容层的人眼里它指向的是另一件事——一个围绕FEX-Emu、Wine、DXMT构建的 x86-64 应用运行方案目标场景很明确让原本为 Windows 或 x86 Linux 编译的程序在 ARM 设备尤其是 Apple Silicon 的 Mac、以及部分 ARM Linux 环境上跑起来并且尽量把图形性能拉到一个可用的水平。我接触这个方向最初是因为手头有一批老旧的 Windows 工具链和几个只在 x86 上编译过的游戏想在 M 系列芯片的 Mac 上直接用不想开虚拟机也不想远程连一台 x86 机器。虚拟机方案太重远程方案延迟又受不了。于是开始研究 FEX-Emu 加 Wine 的组合再叠上 DXMT 做 DirectX 到 Metal 的转译。这一套东西拼起来社区里有人给它起了个代号叫“Madeira”我猜大概是取“调和、融合”的意思——把不同架构、不同系统的东西调和到一起。这篇文章不打算写成官方文档的复述而是把我自己从零搭建、调试、踩坑、再优化的全过程拆开来讲。适合谁看如果你手里有 ARM 设备想跑 x86 的 Windows 程序或游戏又不想被虚拟机的性能损耗和资源占用拖累那这套思路值得你花时间研究。如果你只是偶尔用用某个 Windows 小工具那可能直接找个替代品更省事。但如果你像我一样对“让程序在非原生架构上跑起来”这件事有执念那下面的内容应该能帮你省下不少通宵调试的时间。核心关键词我先摆出来FEX-Emu负责 x86-64 到 ARM64 的指令翻译Wine负责 Windows API 到 POSIX 的转换DXMT负责 Direct3D 到 Metal 的图形转译。三者叠在一起才构成一个能跑图形程序的完整链路。缺了任何一个要么程序起不来要么起来了但画面卡成幻灯片。2. 整体架构拆解三层翻译到底在干什么2.1 为什么不是一层就够很多人会问Wine 不是已经能在 Linux 上跑 Windows 程序了吗为什么还要 FEX-Emu答案在于 CPU 指令集。Wine 解决的是“系统调用和 API 不一样”的问题它把 Windows 的 PE 文件加载起来把对 kernel32、user32 这些 DLL 的调用翻译成 Linux 能理解的系统调用。但 Wine 本身并不翻译 CPU 指令。如果你的程序是 x86-64 编译的而你的机器是 ARM64那 CPU 根本不认识那些指令Wine 再厉害也没用。所以 FEX-Emu 的角色就清楚了它在用户态把 x86-64 指令动态翻译成 ARM64 指令。注意是“动态翻译”不是静态重编译。程序运行时FEX-Emu 一边读 x86 指令一边生成对应的 ARM64 代码块缓存起来下次直接用。这种方式的优势是不需要改程序二进制拿来就能跑代价是首次执行有翻译开销但缓存命中之后性能会明显好转。DXMT 则是第三层。Wine 自带一个叫 WineD3D 的组件能把 Direct3D 调用转成 OpenGL。但在 Apple Silicon 的 Mac 上OpenGL 是弃儿Metal 才是亲儿子。DXMT 直接做 Direct3D 到 Metal 的转换绕开了 OpenGL 这个中间层延迟更低兼容性在现代游戏上也更好。三层各司其职像一条流水线FEX-Emu 管指令Wine 管 APIDXMT 管图形。2.2 三层之间的依赖关系与版本匹配这里有个很容易被忽略的点这三层的版本必须互相匹配。我试过用最新版的 FEX-Emu 配老版本的 Wine结果 Wine 启动时直接段错误。后来查日志才发现FEX-Emu 新版本改了一些内部 ABI而老 Wine 还在用旧的调用约定。所以我的建议是要么全部用稳定版要么全部用某个特定时间点的 git 版本不要混搭。具体来说FEX-Emu 的 RootFS 里通常已经打包了一套经过测试的 Wine 和 DXMT。如果你自己单独编译 Wine就要注意它的 PE 构建选项是否和 FEX-Emu 的预期一致。DXMT 这边它依赖 Metal 的某些特性macOS 版本太低会直接编译失败。我实测下来macOS 13 以上比较稳macOS 12 能跑但偶尔有渲染错误。组件作用关键依赖常见版本要求FEX-Emux86-64 到 ARM64 指令翻译ARM64 宿主Linux 或 macOS最新稳定版或特定 git commitWineWindows API 到 POSIX 转换FEX-Emu 提供的 x86-64 环境与 FEX-Emu 匹配的 PE 构建DXMTDirect3D 到 Metal 转译macOS Metal 框架Wine 的 D3D 接口与 Wine 版本对应的 DXMT 版本2.3 适用场景与不适用场景这套方案最适合的场景是单个 Windows 程序或游戏没有复杂的反作弊没有内核级驱动图形 API 是 Direct3D 9/10/11。比如老版本的 Photoshop、一些独立游戏、工业软件的单机版。我拿它跑过几个 DX9 时代的游戏帧数能到原生 x86 机器的六七成对于非竞技类完全够用。不适用的情况也很明确需要内核驱动的程序比如某些虚拟光驱、杀毒软件、依赖 AVX-512 等新指令集的程序FEX-Emu 对部分扩展指令支持有限、以及带反作弊的在线游戏反作弊会检测到异常环境。另外如果你的程序是纯计算密集型且对延迟极度敏感那即使能跑体验也不如直接找 ARM 原生替代品。3. 环境准备从零搭建 Madeira 运行环境3.1 宿主系统选择与基础依赖我主力环境是 Apple Silicon 的 Mac所以下面的步骤以 macOS 为主。Linux ARM 环境比如某些开发板或 ARM 服务器思路类似但包管理器和图形栈有差异。macOS 上首先要有 Homebrew这是装依赖的基础。然后需要 Xcode Command Line Tools因为编译 FEX-Emu 和 DXMT 都要用到 clang 和 make。基础依赖清单我列一下cmake、ninja、pkg-config、python3、git。这些用 Homebrew 一条命令就能装齐。另外FEX-Emu 在 macOS 上需要 Rosetta 2 吗不需要FEX-Emu 自己就是翻译层它不依赖 Rosetta。但如果你要用某些 x86 的构建工具来编译 FEX-Emu 本身那可能临时需要 Rosetta不过现在 FEX-Emu 已经能原生在 ARM64 上编译了所以这一步可以跳过。注意macOS 上编译 FEX-Emu 需要关闭 SIP 吗不需要。FEX-Emu 是用户态程序不涉及内核扩展SIP 开着也能跑。但如果你要调试某些底层行为可能需要临时关闭 SIP调试完记得开回来。3.2 FEX-Emu 的获取与 RootFS 配置FEX-Emu 官方提供了预编译的 RootFS 包里面包含了 FEX-Emu 本体、一套 Wine、以及 DXMT。这是最省事的路径。下载下来解压到一个目录比如~/madeira/rootfs。然后需要设置几个环境变量让系统知道去哪里找 FEX-Emu 的库和可执行文件。关键环境变量有三个FEX_ROOTFS指向 RootFS 目录FEX_APP_CONFIG指向配置文件PATH里要加入 FEX-Emu 的 bin 目录。我习惯写一个env.sh脚本每次用之前 source 一下。这样比改全局环境变量干净也方便切换不同版本的 RootFS。RootFS 里通常自带一个wine可执行文件但这个 wine 是 x86-64 的需要通过 FEX-Emu 来启动。所以实际调用方式是FEXLoader wine xxx.exe。FEXLoader 是 FEX-Emu 的入口程序它负责加载 x86-64 的 ELF 文件并开始翻译执行。3.3 Wine 与 DXMT 的版本核对RootFS 自带的 Wine 和 DXMT 一般是配套的但如果你想自己升级其中一个就要特别小心。我试过把 RootFS 里的 Wine 换成自己编译的 staging 版结果 DXMT 的 D3D 接口对不上游戏直接黑屏。后来查 DXMT 的文档才发现它依赖 Wine 的某些内部头文件版本差一点就可能编译出来的二进制不兼容。所以我的做法是先用 RootFS 自带的组合跑通确认基础功能没问题再考虑替换单个组件。替换时Wine 和 DXMT 最好一起换而且要从同一个来源获取。比如 DXMT 的 GitHub Release 页面通常会注明它适配的 Wine 版本范围照着那个范围选就不会错。4. 核心实操让一个 Windows 程序跑起来4.1 创建独立的 Wine PrefixWine 的 Prefix 就是一个目录里面模拟了 Windows 的 C 盘、注册表、系统目录。每个程序最好用独立的 Prefix避免不同程序之间的 DLL 冲突。创建命令是FEXLoader wineboot -u这会初始化一个默认 Prefix。如果你想指定路径先设置WINEPREFIX环境变量再执行。我一般会在~/madeira/prefixes/下给每个程序建一个子目录比如~/madeira/prefixes/game1。然后export WINEPREFIX~/madeira/prefixes/game1再跑wineboot。初始化完成后用winecfg可以打开配置界面调整 Windows 版本、DLL 覆盖等。注意winecfg也是 x86-64 程序同样要通过 FEXLoader 启动。实操心得初始化 Prefix 时如果卡住大概率是 FEX-Emu 在翻译某些指令时遇到了死循环。可以加FEX_TSO_ENABLED0试试这个选项关闭了 x86 的强内存序模拟能提升性能但某些程序会因此崩溃。如果崩溃就改回 1。4.2 安装程序与依赖组件安装 Windows 程序有两种方式直接运行安装包或者把已经安装好的目录拷贝过来。我推荐后者因为安装包里的安装程序本身可能依赖一些 FEX-Emu 支持不好的组件比如某些 InstallShield 的脚本引擎。拷贝已安装目录的好处是绕过了安装过程直接跑主程序。但拷贝过来的程序可能需要注册表项。这时候可以用wine regedit导入一个.reg文件把必要的键值写进去。我通常会在一台真实的 Windows 机器上装好程序然后用regedit导出相关的注册表分支再导入到 Wine Prefix 里。这一步比较繁琐但一次搞定之后就能反复用。依赖组件方面很多程序需要 Visual C 运行库。Wine 自带了一些但不全。可以用winetricks来装不过winetricks本身是 shell 脚本它调用的下载工具和安装程序可能也需要 FEX-Emu 来跑。我一般手动下载 vcrun 的安装包然后用FEXLoader wine vcrun_installer.exe来装。4.3 DXMT 的启用与图形配置DXMT 默认可能没有启用需要在 Wine 的注册表里设置。具体是HKEY_CURRENT_USER\Software\Wine\DllOverrides下把d3d9、d3d10core、d3d11、dxgi这几个键的值设为native这样 Wine 就会加载 DXMT 提供的 DLL而不是自带的 WineD3D。设置完之后可以用WINEDEBUGd3d来查看日志确认 DXMT 是否被加载。如果日志里出现DXMT字样说明生效了。然后跑一个简单的 D3D 测试程序比如dxdiag看看能不能正常渲染。如果黑屏或者报错检查 macOS 的 Metal 支持是否正常以及 DXMT 的版本是否和 Wine 匹配。配置项路径推荐值说明d3d9DllOverridesnative使用 DXMT 的 D3D9 实现d3d11DllOverridesnative使用 DXMT 的 D3D11 实现dxgiDllOverridesnative使用 DXMT 的 DXGI 实现MetalHUD环境变量1开启后可在屏幕上显示帧率4.4 性能调优的几个关键参数FEX-Emu 有几个环境变量对性能影响很大。FEX_TSO_ENABLED控制是否模拟 x86 的强内存序关掉能提升性能但可能不稳定。FEX_CORE可以指定使用多少个核心来翻译一般设为物理核心数。FEX_ROOTFS必须指向正确的 RootFS 路径否则会找不到库。Wine 这边WINEDEBUG设为-all可以关闭所有调试输出减少日志开销。WINEESYNC和WINEFSYNC如果支持的话可以开启能改善线程同步性能。DXMT 这边可以通过环境变量DXMT_HUD开启性能覆盖层实时看帧率和 GPU 占用。我实测下来一个 DX9 游戏在 M1 Mac 上默认配置大概 30 帧关掉 TSO 并开启 ESYNC 之后能到 45 帧左右。再进一步就要看游戏本身对 CPU 还是 GPU 更敏感了。如果是 GPU 瓶颈那 DXMT 的优化空间有限如果是 CPU 瓶颈FEX-Emu 的翻译效率就是关键。5. 常见问题与排查实录5.1 程序启动即崩溃或报错这是最常见的问题。第一步看日志用FEXLoader wine program.exe 21 | tee log.txt把输出保存下来。如果日志里有Unhandled instruction字样说明 FEX-Emu 遇到了不支持的 x86 指令。这时候可以查 FEX-Emu 的 issue 列表看是否有已知的指令支持问题。如果是 AVX 相关可以尝试设置FEX_AVX0来禁用 AVX 模拟让程序回退到 SSE。如果日志里是Failed to load DLL那多半是依赖库缺失。用wine的loaddll调试通道可以看到它尝试加载了哪些 DLL哪个失败了。然后手动把对应的 DLL 放到 Prefix 的system32目录下。注意 32 位和 64 位 DLL 要放对位置64 位放system3232 位放syswow64。5.2 图形渲染异常黑屏、花屏、闪烁黑屏最常见的原因是 DXMT 没有正确加载或者 Metal 设备创建失败。先确认DllOverrides设置正确然后看日志里有没有DXMT的初始化信息。如果 Metal 设备创建失败可能是 macOS 版本太低或者当前 GPU 不支持某些 Metal 特性。花屏和闪烁通常和垂直同步、帧缓冲格式有关。可以尝试在 DXMT 的配置里关闭垂直同步或者修改帧缓冲的格式。有些游戏对DXGI_FORMAT有特定要求DXMT 默认的格式可能不匹配。这时候需要查 DXMT 的文档看是否有对应的配置项。避坑技巧如果游戏在窗口模式下正常全屏模式下黑屏那多半是全屏切换的逻辑有问题。可以先用窗口模式跑然后用 macOS 的全屏功能绿灯按钮来模拟全屏。这样绕过了游戏自身的全屏切换代码往往能正常显示。5.3 音频问题与输入设备音频方面Wine 默认用 PulseAudio 或 CoreAudio。在 macOS 上CoreAudio 是原生支持一般不需要额外配置。如果没声音检查winecfg的音频选项卡确认输出设备选对了。有些程序需要特定的采样率可以在winecfg里调整。输入设备方面手柄和键盘鼠标一般都能直接识别。但某些游戏用的是 DirectInput而 Wine 的 DirectInput 实现可能不完整。这时候可以尝试用SDL的映射层或者用x360ce这类工具把 DirectInput 转成 XInput。我试过几个老游戏用x360ce之后手柄就正常了。5.4 性能突然下降的排查思路性能突然下降首先看是不是 FEX-Emu 的翻译缓存满了。FEX-Emu 会把翻译后的代码块缓存到磁盘如果缓存目录所在磁盘空间不足就会频繁淘汰缓存导致反复翻译。检查FEX_CACHE环境变量指向的目录确保有足够空间。其次看是不是 CPU 降频了。ARM 芯片在持续高负载下会降频尤其是 MacBook Air 这种无风扇设计。可以用powermetrics命令查看 CPU 频率和温度。如果是降频导致的那只能限制帧率或者降低画质来减少负载。最后看是不是内存不足。FEX-Emu 和 Wine 都会占用不少内存如果物理内存不够系统会频繁交换性能断崖式下跌。用vm_stat查看交换情况如果 swap 使用量很大就要考虑关闭其他程序或者增加内存。问题现象可能原因排查命令解决方法启动崩溃不支持的指令查看 FEX 日志禁用 AVX 或换版本黑屏DXMT 未加载WINEDEBUGd3d检查 DllOverrides花屏帧缓冲格式不匹配DXMT 日志调整格式配置性能下降缓存不足或降频powermetrics清理缓存或限制帧率无声音音频设备未选对winecfg切换输出设备6. 进阶玩法自定义构建与社区资源6.1 自己编译 FEX-Emu 的注意事项如果你需要最新的指令支持或者想针对特定程序做优化那就得自己编译 FEX-Emu。编译过程不算复杂但有几个坑要注意。第一确保 CMake 版本足够新老版本可能不识别某些选项。第二编译时开启-DCMAKE_BUILD_TYPERelease否则性能会差很多。第三如果编译过程中报错找不到某个头文件检查 Homebrew 的 include 路径是否在编译器的搜索路径里。编译完成后不要直接覆盖 RootFS 里的 FEX-Emu而是新建一个目录把编译产物放进去然后通过环境变量指向新目录。这样万一新版本有问题可以随时切回旧版本。我一般会保留两到三个版本用脚本快速切换。6.2 DXMT 的配置调优与社区补丁DXMT 的 GitHub 仓库里有一些社区提交的补丁针对特定游戏做了优化。比如某些游戏需要修改着色器编译选项或者调整纹理上传策略。这些补丁不一定合并到主分支但可以在 issue 或 pull request 里找到。应用补丁后需要重新编译 DXMT然后把生成的 DLL 替换到 Wine 的对应目录。DXMT 的配置文件通常在~/Library/Application Support/DXMT/下里面可以设置日志级别、HUD 显示、以及一些渲染选项。我建议先把日志级别设为info跑一遍程序看看有没有警告信息。根据警告来调整配置往往能解决一些莫名其妙的问题。6.3 社区资源与持续跟进这个领域变化很快FEX-Emu 和 DXMT 都在活跃开发中。我主要跟几个来源FEX-Emu 的 GitHub Releases 页面DXMT 的 GitHub 仓库以及一些专注于 ARM 游戏兼容的论坛。论坛里经常有人分享针对特定游戏的配置文件和启动参数这些实战经验比官方文档更有参考价值。另外RootFS 的维护者也会不定期更新把新的 Wine 和 DXMT 打包进去。如果你不想自己编译可以定期检查 RootFS 的更新直接下载新版覆盖。但覆盖之前记得备份旧的 Prefix因为新版本 Wine 可能会升级 Prefix 格式降级时可能不兼容。7. 我个人在实际操作中的几点体会这套方案我从最早的单跑 FEX-Emu到后来叠 Wine再到现在加上 DXMT前后折腾了大半年。最大的体会是不要追求一次到位。先让命令行程序跑起来再试图形程序再试游戏。每一步都确认稳定了再往下走。我见过太多人一上来就装大型游戏结果各种报错最后放弃。第二个体会是日志是你的朋友。FEX-Emu、Wine、DXMT 都有详细的日志输出关键是知道开哪个调试通道。FEX_DEBUG、WINEDEBUG、DXMT_LOG这三个环境变量组合起来基本能定位到问题出在哪一层。不要怕日志多用grep过滤关键字就行。第三个体会是性能预期要合理。ARM 翻译 x86 再转译图形性能损失是必然的。能跑到原生的一半到七成就算不错了。如果你的目标是 4K 高帧率那这套方案不适合你。但如果你只是想在一个轻便的设备上跑一些老程序那它确实能省下一台 x86 机器的钱和桌面空间。最后分享一个小技巧如果你有多个程序要跑可以写一个启动脚本根据程序名自动设置对应的WINEPREFIX、FEX_ROOTFS和 DXMT 配置。这样切换程序时不用手动改环境变量减少出错概率。脚本里还可以加上日志重定向每次启动都自动保存日志方便回溯问题。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →