尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Madeira 兼容层实战:FEX-Emu + Wine + DXMT 运行 Windows 应用与游戏

Madeira 兼容层实战:FEX-Emu + Wine + DXMT 运行 Windows 应用与游戏 1. 项目缘起为什么我要折腾 Madeira第一次看到“Madeira”这个词很多人会以为是葡萄牙那个产葡萄酒的岛屿。但在我们这行尤其是最近在 FEX-Emu、Wine、DXMT 这几个圈子里泡着的人看到 Madeira 的第一反应往往是又有人在折腾跨平台运行 Windows 应用和游戏的新方案了。我最初接触到这个方向是因为手头有一台 ARM 架构的轻薄本性能不差续航也顶但偏偏有几个老旧的 Windows 软件和几款 DX 游戏跑不起来。原生 ARM 版本没有虚拟机方案又太重于是我就顺着 FEX-Emu 和 Wine 的线索一路摸到了 Madeira 这个项目。简单来说Madeira 是一套围绕 FEX-Emu 和 Wine 构建的兼容层方案目标是在非 x86 平台上运行 x86-64 的 Windows 应用程序和游戏。它把 FEX-Emu 的指令翻译能力、Wine 的 Windows API 实现、以及 DXMT 的 DirectX 转译能力串在一起形成一个相对完整的运行链路。你如果只是想在 Linux 上跑个记事本那用不着这么复杂但如果你想跑的是依赖 DirectX 11/12 的游戏或者某些对指令集有要求的专业软件那这套组合就值得认真研究一下。我写这篇东西不是要给你一份官方文档的复述而是把我自己从零开始搭环境、踩坑、调参、最后跑通的过程完整记录下来。适合谁看如果你手上有 ARM 设备或者非 x86 的 Linux 环境想跑 Windows 程序或者你对 FEX-Emu、Wine、DXMT 这套技术栈感兴趣想搞清楚它们之间怎么配合再或者你只是单纯好奇“为什么有人要费这么大劲做兼容层”那这篇内容应该能给你一些实在的参考。2. 整体架构拆解FEX-Emu、Wine、DXMT 各自扮演什么角色2.1 三层结构的分工逻辑Madeira 的核心思路并不神秘它本质上是一个分层兼容方案。最底层是 FEX-Emu负责把 x86-64 指令翻译成宿主平台能执行的指令。中间层是 Wine负责把 Windows 的系统调用和 API 映射到 Linux 这边。最上层是 DXMT专门处理 DirectX 的调用转译把 D3D 的请求转到 Vulkan 或者 Metal 上。为什么要这么分因为这三件事的技术难度和耦合度完全不同。指令翻译是 CPU 层面的事Wine 是操作系统 API 层面的事DXMT 是图形 API 层面的事。如果把它们揉在一起做成一个单体方案维护成本会高到离谱。分开之后每一层可以独立迭代FEX-Emu 更新指令翻译效率Wine 更新 API 覆盖度DXMT 更新图形转译质量互不干扰。我一开始也想过能不能只用 Wine 加上 QEMU 用户态模拟试过之后发现QEMU 的用户态模拟在跑图形密集型应用时性能损耗太大而且和 Wine 的配合经常出各种奇怪的兼容问题。FEX-Emu 在这方面做得更专注它本身就是为游戏场景优化的对 x86-64 指令的翻译效率比通用模拟器高不少。2.2 为什么选 FEX-Emu 而不是其他方案市面上做 x86 翻译的方案不止 FEX-Emu 一家。Box64 也是一个常见选择尤其在 ARM 设备上。我两个都试过最后选 FEX-Emu 的原因有几个。第一FEX-Emu 对 x86-64 的支持更完整尤其是 AVX 指令集的处理Box64 在某些场景下会直接崩掉。第二FEX-Emu 的社区在游戏兼容性上投入更多很多游戏的配置文件可以直接拿来用。第三FEX-Emu 和 Wine 的集成已经有比较成熟的方案不需要自己从头写胶水层。当然FEX-Emu 也不是没有缺点。它的配置项比较多新手第一次看配置文件容易懵。而且不同版本的 FEX-Emu 对同一款游戏的兼容性可能不一样有时候升级反而会引入回归问题。我的建议是如果你决定用 FEX-Emu先把版本固定下来跑通了再考虑要不要升级。2.3 Wine 和 DXMT 的配合方式Wine 本身已经能处理不少 Windows 程序的运行但它的 DirectX 实现一直是个短板。WineD3D 能把 D3D 调用转到 OpenGL但 OpenGL 在现代游戏上的性能和兼容性都不太理想。DXMT 的出现就是为了解决这个问题它把 D3D 调用转到 Vulkan绕开了 OpenGL 这个中间层。DXMT 和 Wine 的配合方式简单说就是替换掉 Wine 自带的 D3D 实现。你在 Wine 的配置里指定使用 DXMT 的 DLL然后 DXMT 接管所有的 D3D 调用。这个过程需要确保 DXMT 的版本和 Wine 的版本匹配否则会出现 DLL 加载失败或者函数找不到的问题。我第一次配的时候就是版本没对上折腾了半天才发现是 DXMT 的构建版本太旧。3. 环境准备从零搭建 Madeira 运行环境3.1 系统选择和基础依赖我用的宿主系统是 Ubuntu 22.04ARM64 架构。选这个不是因为它是唯一选择而是因为它的软件源比较全编译工具链也成熟。如果你用的是 Fedora 或者 Arch大部分步骤是类似的只是包管理器的命令不一样。基础依赖这块有几样东西是必须的。编译工具链包括 gcc、g、cmake、ninja这些用来编译 FEX-Emu 和 DXMT。图形相关的库包括 libvulkan-dev、libgl-dev、libegl-dev这些是 DXMT 运行的基础。还有一堆 Wine 需要的依赖比如 libfreetype、libgnutls、libasound 等等。我建议你直接照着 Wine 官方文档里的依赖列表装一遍省得后面缺东西再回头补。注意不同发行版的包名可能不一样比如 libvulkan-dev 在 Fedora 上叫 vulkan-loader-devel。装之前先确认一下你的发行版对应的包名。3.2 FEX-Emu 的编译和安装FEX-Emu 的编译过程不算复杂但有几个坑要注意。首先它依赖一个叫 Catch2 的测试框架如果你不需要跑测试可以在 cmake 的时候把 BUILD_TESTS 关掉能省不少编译时间。其次FEX-Emu 默认会编译一个叫 FEXBash 的东西这是一个包装过的 shell用来在 FEX-Emu 环境下执行 x86 程序。这个工具后面会经常用到。编译命令大概是这样git clone https://github.com/FEX-Emu/FEX.git cd FEX git submodule update --init --recursive mkdir build cd build cmake -DCMAKE_BUILD_TYPERelease -DBUILD_TESTSOFF .. make -j$(nproc) sudo make install编译完之后你需要把 FEX-Emu 的 rootfs 也准备好。这个 rootfs 里包含了 x86-64 的基础库和 Wine 的 x86 版本。FEX-Emu 提供了一个脚本来自动下载和配置但国内网络环境下可能会比较慢建议提前准备好合适的下载方式。3.3 Wine 的编译选项和配置Wine 的编译比 FEX-Emu 要耗时得多而且配置选项直接影响后面的兼容性。我建议在编译 Wine 的时候把 DXMT 的支持打开这样后面配 DXMT 会省事一些。具体的编译参数包括 --enable-win64 和 --with-vulkan前者是 64 位支持后者是 Vulkan 后端。编译 Wine 的时候还有一个坑就是它默认会编译 32 位和 64 位两个版本。如果你只需要跑 64 位程序可以用 --enable-win64 加上 --disable-wine32 来跳过 32 位编译能省将近一半的时间。但如果你要跑一些老游戏那 32 位支持还是得留着。Wine 装好之后第一件事是运行 winecfg 来初始化配置目录。这个命令会创建 ~/.wine 目录里面包含了注册表和驱动配置。如果你后面要换 DXMT就是在这个目录里替换 DLL 文件。3.4 DXMT 的获取和部署DXMT 的获取方式有两种一种是直接下载预编译的二进制包另一种是自己从源码编译。我建议先用预编译包跑通流程确认没问题之后再考虑自己编译。预编译包一般包含 d3d11.dll、d3d12.dll、dxgi.dll 这几个核心文件你需要把它们复制到 Wine 的 system32 目录里。复制之前先把原来的 DLL 备份一下。Wine 自带的 d3d11.dll 和 dxgi.dll 虽然性能不如 DXMT但至少能跑。万一 DXMT 出问题你还能切回来。复制之后需要在 Wine 的注册表里把 DLL 的加载顺序改成原生优先否则 Wine 还是会加载自带的版本。# 备份原始 DLL cd ~/.wine/drive_c/windows/system32 cp d3d11.dll d3d11.dll.bak cp dxgi.dll dxgi.dll.bak # 复制 DXMT 的 DLL cp /path/to/dxmt/d3d11.dll . cp /path/to/dxmt/dxgi.dll . # 设置 DLL 加载顺序 WINEPREFIX~/.wine wine reg add HKCU\Software\Wine\DllOverrides /v d3d11 /d native /f WINEPREFIX~/.wine wine reg add HKCU\Software\Wine\DllOverrides /v dxgi /d native /f4. 实操过程跑通第一个 Windows 程序4.1 用 FEXBash 启动 WineFEX-Emu 装好之后你会得到一个叫 FEXBash 的工具。这个工具的作用是启动一个 shell在这个 shell 里执行的所有 x86-64 程序都会被 FEX-Emu 自动翻译。所以启动 Wine 的正确方式是在 FEXBash 里运行 wine 命令而不是直接在宿主系统里运行。FEXBash # 进入 FEXBash 之后 wine notepad.exe如果 notepad 能正常弹出来说明 FEX-Emu 和 Wine 的基本配合没问题。如果报错说找不到 x86-64 的库那多半是 rootfs 没配好需要检查 FEX-Emu 的 rootfs 路径设置。我第一次跑的时候notepad 是出来了但字体全是乱码。这个问题后面会详细说这里先记一笔。4.2 安装和运行一个简单的 Windows 程序跑通 notepad 之后下一步是装一个稍微复杂点的程序。我选的是一个老版本的 7-Zip因为它对系统依赖少而且有图形界面方便观察。安装过程就是在 FEXBash 里运行安装程序然后一路下一步。安装完之后程序会出现在 Wine 的菜单里。但这里有个坑Wine 的菜单在 FEX-Emu 环境下可能不会自动刷新。你需要手动运行 winecfg然后在“桌面集成”里重新生成菜单。或者更简单的方法直接找到安装目录下的 exe 文件用 wine 命令启动。4.3 配置 DXMT 并测试 DirectX 程序跑通普通程序之后就可以上 DirectX 程序了。我选的是一个 DX11 的独立游戏配置要求不高适合用来验证 DXMT 的基本功能。启动之前先确认 DXMT 的 DLL 已经正确替换并且注册表里的加载顺序也设好了。启动游戏的时候建议在终端里加上 WINEDEBUG 环境变量来观察日志WINEDEBUGd3d11,dxgi wine game.exe如果 DXMT 正常工作你会在日志里看到 DXMT 的初始化信息以及 D3D11 设备的创建过程。如果看到的是 WineD3D 的日志那说明 DXMT 没有被加载需要回头检查 DLL 替换和注册表设置。我测试的那个游戏第一次启动直接黑屏。看日志发现是 DXMT 在创建 swapchain 的时候失败了原因是宿主系统的 Vulkan 驱动版本太旧。更新驱动之后问题解决。所以如果你遇到类似的黑屏或者崩溃先检查 Vulkan 驱动。5. 常见问题与排查技巧实录5.1 Wine 乱码问题的根源和解决Wine 乱码是我遇到的最常见的问题没有之一。表现就是程序界面里的中文全部变成方块或者问号。这个问题的根源是 Wine 默认没有配置中文字体而且它的字体替换机制在没有合适字体的时候会直接显示乱码。解决方法有几个。最简单的是把宿主系统的中文字体复制到 Wine 的字体目录里然后在注册表里设置字体替换。具体操作是把 /usr/share/fonts 里的中文字体复制到 ~/.wine/drive_c/windows/Fonts然后运行 regedit在 HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\FontSubstitutes 里把 MS Shell Dlg 替换成你复制进去的字体名。还有一个更彻底的方法是用 winetricks 安装 corefonts 和 cjkfonts。winetricks 是一个脚本工具能自动下载和安装一些 Wine 需要的组件。不过在国内网络环境下winetricks 下载字体的过程可能会很慢建议手动下载字体文件然后放到对应的目录里。提示如果你用的是 FEX-Emu 环境winetricks 需要在 FEXBash 里运行否则它会尝试调用宿主系统的 Wine导致配置写错地方。5.2 DXMT 加载失败的排查思路DXMT 加载失败的表现通常是程序启动时报错说找不到 d3d11.dll或者直接崩溃。排查的时候先确认 DLL 文件确实在 system32 目录里而且文件名大小写正确。Linux 是区分大小写的D3D11.dll 和 d3d11.dll 是两个不同的文件。然后检查注册表里的 DllOverrides 设置。有时候你设了 native 优先但 Wine 还是会加载自带的版本原因是注册表的路径写错了。正确的路径是 HKCU\Software\Wine\DllOverrides不是 HKLM 下面的那个。还有一个容易被忽略的点是 DXMT 的版本和 Wine 的版本匹配问题。DXMT 的每个版本都是针对特定的 Wine 版本编译的如果你用的 Wine 版本和 DXMT 不匹配就会出现函数找不到或者结构体大小不一致的问题。我建议在 DXMT 的发布页面仔细看一下它支持的 Wine 版本范围。5.3 性能调优的几个关键参数跑通之后下一步就是调性能。FEX-Emu 有几个参数对性能影响比较大。第一个是 FEX_TSOENABLED这个参数控制是否启用 x86 的内存序模拟。关掉它能提升性能但可能会导致某些多线程程序出错。第二个是 FEX_VECTORTSOENABLED类似的东西针对向量指令。第三个是 FEX_ROOTFS指定 rootfs 的路径如果路径不对会导致频繁的 IO 等待。Wine 这边可以调整的参数包括 WINEDEBUG 的级别关掉不必要的调试输出能减少开销。还有 Wine 的 CSMT 选项这个在多线程游戏里能明显提升帧率。DXMT 这边可以调整的是 Vulkan 的队列数量和内存分配策略但这些参数需要根据具体的游戏来调没有通用的最优值。参数作用建议值FEX_TSOENABLED控制内存序模拟单线程程序关多线程程序开FEX_VECTORTSOENABLED控制向量内存序默认开性能不足时尝试关WINEDEBUG调试输出级别正常运行设为 -allDXMT_FRAME_LATENCY帧延迟控制根据显示器刷新率调整5.4 常见问题速查表问题现象可能原因解决方法程序启动黑屏Vulkan 驱动不兼容更新宿主系统 Vulkan 驱动界面中文乱码缺少中文字体复制字体并设置注册表替换DXMT 未加载DLL 替换或注册表错误检查 system32 目录和 DllOverrides程序崩溃无日志FEX-Emu 翻译错误尝试关闭 TSO 或更换 FEX 版本性能明显偏低调试输出过多设置 WINEDEBUG-all音频卡顿Wine 音频驱动不匹配切换 Wine 的音频后端为 pulse6. 一些个人体会和后续可折腾的方向这套方案跑通之后我最大的感受是兼容层这东西没有一劳永逸的配置。同一个程序在不同的 FEX-Emu 版本、不同的 Wine 版本、不同的 DXMT 版本下表现可能完全不一样。所以我的习惯是一旦某个组合跑通了就把版本号记下来不要轻易升级。如果非要升级先在一个独立的 prefix 里测试确认没问题再迁移。另外FEX-Emu 的 rootfs 其实可以自己定制。默认的 rootfs 里包含了很多用不到的东西如果你只跑特定几个程序可以精简一下能减少不少磁盘占用和启动时间。DXMT 这边社区里有人在尝试把 D3D12 的支持做得更完整如果你对图形管线比较熟可以关注一下这方面的进展。最后分享一个小技巧如果你在 FEXBash 里跑程序的时候遇到莫名其妙的错误可以试试用 strace 跟踪一下系统调用。虽然 FEX-Emu 会拦截一部分调用但 strace 还是能帮你定位到是哪个文件没找到或者哪个权限不对。这个办法帮我解决过好几次“程序无声无息退出”的问题。
返回列表