
1. 从“Madeira”这个名字说起一个跨平台兼容层的野心第一次看到“Madeira”这个项目名我脑子里蹦出来的不是葡萄牙那座海岛而是它背后那串关键词FEX-Emu、Wine、DXMT、iOS、x86-64。把这几个词摆在一起稍微有点系统层经验的人都能嗅到一股熟悉的味道——这又是一个试图在非 x86 平台上跑 x86 程序的兼容层项目而且目标平台很可能指向了 Apple Silicon 的 Mac 甚至 iOS 设备。为什么这么说FEX-Emu 是目前在 ARM64 平台上做 x86-64 用户态指令翻译最活跃的开源项目之一它的定位和当年的 Box64、qemu-user 有重叠但在性能优化和系统调用转发上走得更激进。Wine 负责把 Windows 的 PE 可执行文件映射到 POSIX 系统调用上DXMT 则是把 Direct3D 翻译成 Metal 的中间层。这三者叠起来就是一条完整的“Windows 游戏/应用在 ARM Mac 上跑起来”的技术栈。Madeira 要做的我判断是把这三层打包成一个可分发、可配置的整合方案降低普通用户的使用门槛。因为单独折腾 FEX-Emu Wine DXMT 的组合光是编译参数和运行时环境变量就够劝退一大片人。这个项目的价值不在于发明了新轮子而在于把已有的轮子装到一辆能开的车上。这篇文章我会从技术栈拆解、环境搭建、实际跑通流程、常见故障排查几个角度把这个项目的核心逻辑讲透。适合对跨平台兼容层感兴趣、手上有 ARM 设备想跑 Windows 程序、或者单纯想理解 FEX-Emu 和 Wine 协作机制的读者。不管你是刚接触这块的新手还是已经折腾过 Box64 的老玩家应该都能从里面找到一些之前没注意到的细节。2. FEX-Emu 在整条链路里到底干了什么2.1 x86-64 到 ARM64 的指令翻译不是“模拟”那么简单很多人一听到“在 ARM 上跑 x86 程序”第一反应就是“模拟器”然后脑子里浮现出 QEMU 那种全系统模拟的笨重形象。FEX-Emu 走的是另一条路它做的是用户态指令翻译不模拟整个硬件环境而是把 x86-64 的机器码在运行时翻译成 ARM64 指令然后直接在当前系统上执行。这个区别很关键。全系统模拟需要模拟 CPU、内存控制器、外设开销巨大用户态翻译只需要处理指令集差异和系统调用转发性能损耗小得多。FEX-Emu 的核心是一个 JIT 编译器它在程序运行时动态地把 x86-64 的基本块翻译成 ARM64 指令翻译结果会被缓存起来下次执行同一段代码就不用重新翻译。但这里有个容易被忽略的点x86 和 ARM 的内存模型不一样。x86 是强内存模型TSOARM 是弱内存模型。这意味着在 x86 上能正常工作的多线程代码直接翻译到 ARM 上可能会因为内存访问顺序变化而出问题。FEX-Emu 需要在翻译过程中插入内存屏障指令来保证语义一致这也是它性能开销的主要来源之一。2.2 系统调用转发Wine 和 FEX-Emu 的交接面FEX-Emu 翻译的是用户态指令但程序总要和操作系统打交道——读写文件、分配内存、创建线程。这些系统调用不能直接翻译需要转发给宿主系统的内核。在 Linux 上FEX-Emu 通过 syscall 转发层把 x86-64 的 syscall 映射到 ARM64 的 syscall。Wine 在这里的角色就更微妙了。Wine 本身是一个 Windows API 的实现层它把 Windows 的 PE 文件加载起来把 Windows API 调用翻译成 POSIX 调用。当 Wine 跑在 FEX-Emu 上面时就变成了三层结构Windows 程序 → WineWindows API 到 POSIX→ FEX-Emux86-64 到 ARM64→ 宿主内核。这个链条里最容易出问题的地方是线程本地存储TLS和异常处理。Windows 的 SEH 异常模型和 Linux 的信号机制差异很大Wine 需要做一层转换而 FEX-Emu 又要在翻译后的代码里正确传递信号。任何一层出问题表现都是程序莫名其妙崩溃或者卡死。2.3 为什么选 FEX-Emu 而不是 Box64Box64 也是 ARM 上跑 x86-64 的热门方案而且社区活跃度很高。Madeira 选择 FEX-Emu 而不是 Box64我推测有几个原因。FEX-Emu 对 x86-64 指令集的支持更完整特别是 AVX 和 AVX2 指令。很多现代游戏和生产力软件都依赖这些指令Box64 在这方面的支持相对滞后。另外 FEX-Emu 的 JIT 架构在设计上更接近现代编译器优化空间更大。还有一个实际因素FEX-Emu 在 Apple Silicon 上的适配做得比较早和 Asahi Linux 社区的协作也比较紧密。当然 Box64 也有自己的优势比如对 32 位 x86 的支持更好配置更简单。选哪个取决于你要跑的具体程序。如果目标程序用了大量 AVX2 指令FEX-Emu 基本是唯一选择如果只是跑一些老旧的 32 位程序Box64 可能更省事。3. Wine 和 DXMT 的配合图形栈是怎么打通的3.1 Wine 的图形抽象层从 Win32 到 MetalWine 处理图形的方式经历过几代演变。早期直接用 X11 或 OpenGL 做后端后来引入了 Vulkan 后端现在最活跃的方向是通过 DXVK、VKD3D 这类翻译层把 Direct3D 调用转成 Vulkan。但在 macOS 上Vulkan 的支持一直是个问题MoltenVK 虽然能把 Vulkan 映射到 Metal但多了一层转换性能和兼容性都有损耗。DXMT 的思路不一样它直接把 Direct3D 翻译成 Metal跳过了 Vulkan 这个中间层。这意味着在 macOS 上图形调用的路径更短D3D → DXMT → Metal → GPU。理论上延迟更低兼容性问题也更少因为不需要经过 MoltenVK 的二次翻译。但 DXMT 的成熟度目前还不如 DXVK。DXVK 经过多年迭代对 D3D9、D3D10、D3D11 的支持已经相当完善DXMT 还在追赶阶段。Madeira 选择 DXMT 而不是 DXVK MoltenVK 的组合应该是在 macOS 这个特定平台上做的权衡。3.2 图形栈的初始化顺序很讲究在实际跑通 Wine DXMT 的过程中初始化顺序是个容易被忽视但很关键的问题。Wine 启动时会加载图形驱动如果 DXMT 的 Metal 后端没有正确初始化Wine 会回退到软件渲染或者直接报错。正确的顺序大致是这样的先确保 Metal 设备可用在 macOS 上这通常不是问题然后 Wine 加载 DXMT 的 DLLDXMT 在初始化时创建 Metal 设备和命令队列最后 Windows 程序创建 D3D 设备时DXMT 把请求映射到 Metal 的对应对象上。这里有个坑如果程序在创建 D3D 设备之前就查询了显示适配器信息而 DXMT 还没完成初始化返回的信息可能是错的。表现就是程序启动后分辨率不对或者直接提示“不支持的显卡”。解决办法是在 Wine 的注册表里预先配置好显卡信息让 DXMT 在启动早期就完成初始化。3.3 着色器编译卡顿的缓解思路用 DXMT 跑游戏第一次遇到新场景时经常会有明显的卡顿这是着色器编译导致的。Metal 的着色器编译是在运行时进行的当游戏渲染一个新效果时DXMT 需要把 D3D 的着色器字节码翻译成 Metal 着色器然后交给 Metal 编译器编译这个过程可能耗时几百毫秒甚至更长。缓解办法有几个方向。一是预编译着色器缓存DXMT 支持把编译好的 Metal 着色器缓存到磁盘下次遇到同样的着色器就直接加载。二是调整 Metal 的编译选项比如使用更快的编译模式牺牲一些运行时性能换取编译速度。三是在游戏设置里降低画质减少需要编译的着色器数量。实测下来第一次跑某个游戏时卡顿最明显跑过一遍之后缓存建立起来第二次就流畅很多。所以如果你打算长期用这套方案跑某个游戏第一次的卡顿是值得忍受的。4. 在 Apple Silicon 上从零搭起这套环境4.1 系统准备和依赖安装假设你用的是一台 M 系列芯片的 Mac系统版本建议在 macOS 13 以上因为 Metal 3 的一些特性需要较新的系统。首先需要安装 Xcode Command Line Tools这是编译 FEX-Emu 和 Wine 的基础。xcode-select --install然后通过 Homebrew 安装必要的依赖库。FEX-Emu 需要 CMake、Ninja、LLVM 等构建工具Wine 还需要一些图形和音频库。brew install cmake ninja llvm sdl2 gnutls freetype这里有个细节Homebrew 安装的 LLVM 版本可能和 FEX-Emu 要求的版本不一致。FEX-Emu 对 LLVM 版本比较敏感太新或太旧都可能导致编译失败。建议先查一下 FEX-Emu 的文档确认它当前支持的 LLVM 版本范围必要时用brew install llvm16这样的方式安装指定版本。4.2 编译 FEX-Emu 的关键参数FEX-Emu 的编译配置直接影响运行性能。以下是我实测下来比较稳的一套参数git clone https://github.com/FEX-Emu/FEX.git cd FEX git submodule update --init --recursive mkdir build cd build cmake .. -DCMAKE_BUILD_TYPERelease \ -DCMAKE_C_COMPILERclang \ -DCMAKE_CXX_COMPILERclang \ -DENABLE_LTOON \ -DBUILD_TESTSOFF ninjaENABLE_LTOON开启链接时优化能提升最终二进制的性能但编译时间会明显增加。BUILD_TESTSOFF跳过测试构建节省时间。如果你只是自己用不需要跑测试套件。编译完成后FEX-Emu 的可执行文件在build/Bin/目录下。需要把它加到 PATH 里或者创建一个符号链接到/usr/local/bin。4.3 Wine 的编译和 DXMT 的集成Wine 的编译比 FEX-Emu 更耗时而且依赖更多。如果你不想自己编译可以用 CrossOver 或者 Whisky 这类已经打包好的方案但 Madeira 的定位应该是自己掌控整个工具链所以我还是说一下从源码编译的流程。git clone https://github.com/wine-mirror/wine.git cd wine ./configure --enable-win64 --disable-win16 \ --with-metal --without-x make -j$(sysctl -n hw.ncpu)--with-metal启用 Metal 后端支持--without-x在 macOS 上不需要 X11。编译完成后make install会把 Wine 安装到/usr/local下。DXMT 的集成稍微麻烦一些它需要作为 Wine 的一个 DLL 被加载。通常的做法是把 DXMT 编译出的d3d11.dll、dxgi.dll等文件放到 Wine 的lib/wine/x86_64-windows/目录下然后在 Wine 的注册表里配置 DLL 覆盖让 Wine 优先加载 DXMT 的版本而不是内置版本。4.4 环境变量配置和启动脚本FEX-Emu 和 Wine 都需要一些环境变量才能正常工作。以下是我常用的配置export FEX_ROOTFS/path/to/fex/rootfs export FEX_APP_CONFIG/path/to/fex/config export WINEPREFIX$HOME/.wine-madeira export WINEDLLOVERRIDESd3d11,dxgin,b export DXVK_LOG_LEVELnoneWINEDLLOVERRIDES里的n,b表示优先使用原生nativeDLL如果找不到再用内置builtin的。这样 DXMT 的 DLL 会被优先加载。启动 Windows 程序时用 FEX-Emu 来加载 Wine再由 Wine 加载程序FEXBash -c wine /path/to/program.exeFEXBash是 FEX-Emu 提供的一个包装脚本它会设置好必要的环境变量然后启动一个 bash shell在这个 shell 里执行的 x86-64 程序都会被 FEX-Emu 翻译执行。5. 实际跑起来之后才会遇到的坑5.1 中文乱码字体和编码的双重问题Wine 跑 Windows 程序时中文显示乱码是个老问题在 Madeira 这套组合里同样存在。乱码的根源通常有两个一是缺少中文字体二是编码映射不对。字体问题比较好解决把 Windows 的字体文件比如simsun.ttc、msyh.ttf复制到 Wine 的字体目录下cp /path/to/windows/fonts/*.ttf $WINEPREFIX/drive_c/windows/Fonts/然后在 Wine 的注册表里配置字体替换把Tahoma、MS Sans Serif等默认字体映射到中文字体上。可以用wine regedit打开注册表编辑器在HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\FontSubstitutes下添加映射项。编码问题更隐蔽一些。有些程序用的是 GBK 编码而 Wine 默认按 UTF-8 处理就会显示乱码。这种情况需要在 Wine 的 locale 设置里指定正确的编码或者在程序内部设置里调整。实测下来大部分现代程序用 UTF-8 都没问题只有一些老程序需要特殊处理。5.2 音频输出断续和延迟Wine 在 macOS 上的音频后端默认用 CoreAudio但 FEX-Emu 的翻译层可能会引入额外的延迟导致音频断续或者和画面不同步。调整的方向有几个。一是增大音频缓冲区在 Wine 的音频设置里把缓冲区大小从默认的 50ms 调到 100ms 或更大代价是延迟增加但稳定性提升。二是检查采样率设置确保 Wine 的采样率和系统输出设备的采样率一致避免重采样引入的额外开销。三是如果程序支持切换到 ALSA 或 PulseAudio 后端试试有时候反而比 CoreAudio 更稳。5.3 程序启动崩溃的排查链路遇到程序启动就崩溃的情况不要急着重装按下面的顺序排查通常能定位到问题。第一步看 Wine 的调试输出。用WINEDEBUGall启动程序会打印大量日志从中找到err:或warn:开头的行这些通常是问题的线索。第二步确认 FEX-Emu 是否正常加载。如果 FEX-Emu 本身有问题Wine 可能根本启动不了。可以先用 FEX-Emu 跑一个简单的 x86-64 程序比如uname的 x86-64 版本验证翻译层是否工作。第三步检查 DLL 依赖。用WINEDEBUGloaddll看程序加载了哪些 DLL有没有加载失败的。如果某个关键 DLL 加载失败程序可能在初始化阶段就崩溃了。第四步检查图形驱动。如果程序在创建窗口或 D3D 设备时崩溃很可能是 DXMT 的问题。可以临时把WINEDLLOVERRIDES里的 DXMT 覆盖去掉用 Wine 内置的 D3D 实现跑一下看是否能启动。如果能启动但画面不对说明问题在 DXMT如果还是崩溃问题可能在更底层。5.4 性能调优的几个实际手段跑起来之后性能调优是下一步。FEX-Emu 和 DXMT 都提供了一些调优选项。FEX-Emu 这边可以调整 JIT 缓存大小和翻译块的大小。增大缓存能减少重复翻译但占用更多内存。在配置文件中可以设置JITCacheSize和BlockSize参数具体值需要根据你的内存大小和程序特点来调。DXMT 这边可以调整 Metal 的命令缓冲区提交策略。默认是每帧提交一次如果程序帧率不稳定可以改成自适应提交让 DXMT 根据 GPU 负载动态调整。另外开启 Metal 的framebufferOnly选项能减少不必要的纹理拷贝提升渲染效率。还有一个容易被忽视的点macOS 的电源管理。在跑这些翻译层的时候CPU 和 GPU 的负载都比较高如果系统进入低功耗模式性能会明显下降。可以在“系统设置 → 电池”里把电源模式调到“高性能”或者在终端里用caffeinate命令防止系统休眠。6. 这套方案适合谁不适合谁6.1 适合的场景如果你手上有 M 系列芯片的 Mac想跑一些 Windows 独占的老游戏或者行业软件又不想装虚拟机或者双系统Madeira 这套方案是值得尝试的。特别是那些对性能要求不高、但只有 Windows 版本的程序用 FEX-Emu Wine DXMT 跑起来体验通常比虚拟机好因为不需要分配固定的内存和 CPU 核心资源利用更灵活。另一个适合的场景是开发和调试。如果你在开发跨平台软件需要验证 Windows 版本在 ARM 环境下的行为这套方案能提供一个相对轻量的测试环境不用专门准备一台 Windows 机器。6.2 不适合的场景对性能要求极高的 3A 游戏这套方案目前还撑不起来。FEX-Emu 的指令翻译开销、DXMT 的图形翻译开销叠加起来帧率损失可能达到 30% 到 50%而且稳定性也不如原生。如果你主要玩这类游戏还是建议用 Windows 台式机或者游戏主机。另外依赖内核级驱动的程序也跑不了。比如一些反作弊系统、虚拟化软件、底层硬件访问工具它们需要加载 Windows 内核驱动而 Wine 不实现内核驱动模型FEX-Emu 也不翻译内核态代码。这类程序在 Madeira 上基本无解。6.3 和虚拟机方案的对比虚拟机方案比如 Parallels Desktop在 macOS 上跑 Windows 的体验已经相当成熟图形性能通过 Metal 加速也不错。Madeira 相比虚拟机的优势在于资源占用更小不需要为虚拟机分配固定的内存和磁盘空间启动也更快。劣势在于兼容性和稳定性虚拟机跑的是完整的 Windows 系统兼容性基本没有问题而 Madeira 依赖 Wine 的 API 实现总有一些程序跑不起来。选择哪个取决于你的具体需求。如果只是偶尔跑一两个 Windows 程序Madeira 更轻量如果需要完整的 Windows 环境虚拟机更省心。7. 后续可以继续折腾的方向这套环境搭起来之后还有一些可以继续优化的方向。一个是尝试不同的 Wine 版本Wine 的开发分支更新很快新版本可能修复了你遇到的问题也可能引入新的问题多试几个版本找到最稳的那个。另一个是调整 FEX-Emu 的配置针对具体程序做优化比如关闭某些不必要的高级指令翻译减少开销。DXMT 本身也在快速迭代关注它的更新日志新版本可能带来性能提升或兼容性改进。如果遇到 DXMT 的 bug可以在它的仓库里提 issue附上详细的日志和复现步骤开发者响应通常比较快。还有一个思路是把这套环境容器化用 Docker 或者类似的工具打包起来方便在不同机器之间迁移。不过 macOS 上的容器化支持不如 Linux 完善图形直通也是个问题这条路目前还比较难走通。我个人在实际操作中的体会是这套方案的价值不在于替代 Windows而在于给 ARM Mac 用户多一个选择。有些程序就是偶尔用一下为了它装虚拟机或者买 Windows 机器不划算用 Madeira 跑起来能凑合用这就够了。折腾的过程中也能学到不少系统层的知识对理解操作系统和兼容层的工作原理很有帮助。