Adreno Vulkan Shim for 32/64-bit Wine Games on Termux
https://img.shields.io/badge/License-MIT-yellow.svg
在骁龙 835 (Adreno 540) + Termux + Box64 + Wine 环境下,让 32/64 位 Windows 游戏通过原生 Vulkan 硬件加速运行的 ICD Shim + WSI 层解决方案。
🎯 项目背景与解决的问题
在 Android 的 Termux 环境中通过 Box64 和 Wine 运行 32 位 Windows 游戏时,一个经典且棘手的问题是:
· Adreno 540 Vulkan HAL 在 vkMapMemory 中返回的 CPU 侧指针位于 高 4GB 地址空间(例如 0x7fdb95000,约 34GB)。 · Wine 的 WOW64 层 在转换 32 位指针时,无法将超过 4GB 的指针塞入 32 位寄存器,因此主动 unmap 并合成 VK_ERROR_OUT_OF_HOST_MEMORY,导致游戏白屏但有声音。
❌ 为何其他方案失败?
尝试方案 结果 BOX64_MMAP32=1 强制所有匿名映射到低 4GB,但会破坏 Adreno HAL 内部控制结构,导致 vkCreateDevice 返回 -1 mremap 重映射别名 KGSL VMA 带 VM_PFNMAP|VM_DONTEXPAND,内核拒绝(EFAULT) 同一 fd 二次 mmap KGSL 只允许一个用户态映射(EBUSY) VK_EXT_external_memory_host Adreno 540 驱动不支持该扩展
✅ 我们的解法:零拷贝 dmabuf 重映射
本方案通过 Vulkan ICD Shim 在驱动层介入:
- 自动注入 VK_KHR_external_memory_fd 等扩展,使显存可导出为 dmabuf。
- 拦截 vkAllocateMemory,为 HOST_VISIBLE 内存创建 dummy buffer 并标记为 DEDICATED_ONLY。
- 拦截 vkMapMemory,调用真实 HAL 建立 GPU VA,同时导出 dmabuf 并用标准 mmap 映射到 低 4GB 地址空间,将低位指针返回给 32 位 guest(Wine WOW64 可正常处理)。
- 零拷贝:dmabuf 与原始显存共享同一物理页,无数据复制。
- 完全绕过 Box64 的 mmap 钩子,不依赖 BOX64_MMAP32 环境变量。
✨ 效果展示
· Galgame(如 9-nine):在骁龙 835 上稳定 60 FPS。
· 性能损耗:仅 vkMapMemory 时增加一次 mmap 系统调用(微秒级),运行时零额外开销。
· 兼容性:已测试 9-nine、Mashiroiro Symphony SANA、Skyrail 等多款 32/64 位游戏;纯 D3D7 游戏(如 KiriKiri 的 Nagaruboshi)经 d3d7to9/ 补充方案亦可跑(见下)。
某些 Unity / D3D11 游戏(如 SPECIMEN: HIDE & SEEK)在 Adreno 540 上表现为:画面灰屏/黑屏 → 修好后可见但带贴图表面持续闪烁。
根因链条:
- Adreno 540 的 HAL 把
D32_SFLOAT_S8_UINT(130) 在vkGetPhysicalDeviceFormatProperties/ImageFormatProperties中报成零特性、彻底不支持(VK_ERROR_FORMAT_NOT_SUPPORTED),而D24_UNORM_S8_UINT(129) 是硬件真正支持的深度格式。 - DXVK 在启动期用不同 usage 探测深度格式,只要
SAMPLED这一次返回失败,就把该格式整体标记为不支持并缓存 → 之后每次RenderTexture.Create都直接失败(SPECIMEN 实测 5594 次),场景无法渲染。 - 仅「谎称 D32S8 可用」会让场景显示出来,但硬件实际不能正确渲染 D32S8 → 深度缓冲是坏的 → 带贴图表面 z-fighting 闪烁。
修复(实现于 icd/vulkan_adreno_icd.c):对 D32_SFLOAT_S8_UINT 做透明替换为 D24_UNORM_S8_UINT,且 vkCreateImage / vkCreateImageView / vkCreateRenderPass(2) 三处一致替换,避免格式不匹配报错。另含 VK_TEST_RAW=1 开关,可在不修补的情况下直连硬件验证。
证据(详见 docs/adreno-depth.md):
qfmt(RAW 查询):D32S8 的FormatProperties.optimal = 0x0、ImageFormatProperties 返回 -11;D24S8 正常。depthtest(功能渲染):D24S8 渲染中心像素为绿(深度正确);D32S8 直接令驱动段错误(不可用)。
注意:本问题已永久修好。老 Adreno 540 的其他能力缺口(BC 压缩、可选颜色格式等)若在未来别的游戏出现,用同样的「抓日志 → RAW 查询 → 等价替换」流程即可(见
docs/adreno-depth.md第四节)。
📁 项目结构
vulkan-adreno-shim/
├── README.md # 本文件
├── LICENSE # MIT 许可证
├── .gitignore
├── icd/ # ICD Shim 核心源码(完全原创)
│ ├── vulkan_adreno_icd.c # 主 shim 实现
│ ├── vulkan_adreno_icd.json # ICD 清单文件(Vulkan Loader 加载用)
│ └── vulkan_gpu.c # 可选:Android linker namespace 绕过
├── wsi/ # Vulkan WSI Layer(基于 Sky1-Linux 修改)
│ ├── vulkan-wsi-layer/ # 完整源码(已移除 .git)
│ │ ├── wsi/x11/surface.cpp # 修改:独立 XCB 连接
│ │ ├── wsi/x11/dri3_presenter.cpp # 修改:安全 DRI3 探测
│ │ └── wsi/x11/shm_presenter.cpp # AHB 零拷贝 + ring 生产者(fstat 尺寸推送)
│ └── ahb_shim/ # Xvnc fstat 重写器(AHB 零拷贝的消费者端)
│ ├── ahb_size_shim.c # LD_PRELOAD 进 Xvnc,hook fstat 回填 st_size
│ ├── ahb_ring_test.c # 端到端验证(dlopen shim + ring 流程)
│ └── README.md # 构建/部署说明(含启动顺序)
├── scripts/ # 启动脚本示例
│ ├── claunch-9nine-adreno.sh # 32 位 Galgame 启动
│ └── claunch-skyrail-adreno.sh # 64 位 Unity 游戏启动
├── tests/ # 独立测试程序
│ ├── t_extfd2.c # dmabuf 基础导出测试
│ ├── t_extfd4.c # 内存类型矩阵测试
│ ├── t_shimlow.c # 端到端 shim 模拟测试
│ ├── qfmt.c # 直连 HAL 查询深度格式原始能力(配合 VK_TEST_RAW=1)
│ ├── depthtest.c # 真实深度测试渲染,验证 D32S8/D24S8 是否可用
│ ├── depthtest.vert/.frag # 上述测试的着色器(已编译为 .spv)
│ └── README.md # 测试编译/运行说明
├── docs/
│ ├── adreno32.md # 完整技术文档(含根因分析、失败路径、实现细节)
│ ├── adreno-depth.md # 深度格式 D32S8→D24S8 修复完整记录与未来指导
│ └── d3d7to9.md # 补充:D3D7/DirectDraw 游戏经 D3D7to9→DXVK 在 Adreno 跑通
├── d3d7to9/ # 补充:D3D7→D3D9 翻译层(elishacloud/DXWrapper)部署件套
│ ├── ddraw.dll # D3D7to9 本体(32-bit)
│ ├── dxwrapper.dll # DXWrapper 引擎(32-bit,被 ddraw.dll 加载)
│ ├── dxwrapper.ini # 配置(Dd7to9=1,RealDllPath 指向 ddraw_wine.dll)
│ └── README.md # 快速部署说明
└── archive/ # 非核心测试/日志/备份(压缩存档)
└── extra_files.7z
🚀 快速开始
- 环境准备
· Termux(从 F-Droid 或 GitHub 安装,不要用 Google Play 版本) · 已安装 box64、wine(支持 WOW64 的版本,如 Proton 11)、DXVK-Sarek 或主线 DXVK 1.10.x。 · X 服务器(如 TigerVNC)运行在 :1 显示编号。 · 已编译并安装好 Sky1 WSI Layer(vulkan-wsi-layer)为隐式层,位于 /data/data/com.termux/files/usr/share/vulkan/implicit_layer.d/。
- 编译 ICD Shim
cd ~/vulkan-adreno-shim/icd
gcc -shared -fPIC -O2 -Wall -o vulkan_adreno_icd.so vulkan_adreno_icd.c -ldl2.5(可选但推荐)编译 AHB 零拷贝所需的 Xvnc shim
AHB 零拷贝路径把 Android Hardware Buffer 的 fd 直接交给 Xvnc 的 MIT-SHM,
但 Xvnc 因 st_size==0 会拒绝。这个 shim 必须 LD_PRELOAD 进 Xvnc 进程,
且 Xvnc 要先于游戏启动(ring 由 shim 创建):
cd ~/vulkan-adreno-shim/wsi/ahb_shim
cmake -B build -S . && cmake --build build
# 启动 Xvnc 时注入:
LD_PRELOAD=$PWD/build/libahb_size_shim.so Xvnc :1 -geometry 1280x720 ...未注入时 WSI 层自动回退到 memfd + CPU 拷贝(功能正常,非零拷贝)。
详见 wsi/ahb_shim/README.md。
- 配置 ICD 路径
在启动脚本中设置环境变量:
export VK_ICD_FILENAMES=/path/to/vulkan_adreno_icd.json- 启动游戏(以 9-nine 为例)
cd ~/vulkan-adreno-shim/scripts
./claunch-9nine-adreno.sh该脚本会:
· 设置 BOX64_MMAP32=0(必须!) · 指向本 shim 作为 ICD · 启动音频、设置显示、清理残留进程 · 运行游戏并保存日志
🧪 验证是否生效
查看日志文件(默认 ~/.build/adreno-9nine.log),应出现类似:
[VK_ICD] 低位 dmabuf 映射已启用
[VK_ICD] alloc 已接管: type=2 请求=16384KB 实分=16389KB
[VK_ICD] vkMapMemory -> 低位 dmabuf 0x34000000
info: Presenter: Actual swap chain properties: Format: ... Buffer size: 960x540
若出现 Mapping memory failed 错误,请确认:
· BOX64_MMAP32 是否为 0 · ICD 路径是否正确 · WSI 层是否已正确安装
📖 进阶文档
· docs/adreno32.md – 包含完整的根因分析、所有失败路径记录、shim 实现细节、调试技巧。 · docs/d3d7to9.md – 补充:纯 D3D7 / DirectDraw 7 游戏(如 KiriKiri Galgame)为何在 Adreno 上起不来、如何用 D3D7to9 在 prefix 层翻译成 D3D9 复用 DXVK 路径跑通(Shim 无需改动)。 · d3d7to9/ – 上述方案的即用部署件套(D3D7to9 + DXWrapper 引擎 + 配置)。 · tests/ – 独立的测试程序可用于验证 dmabuf 导出、内存类型兼容性、shim 端到端行为。
先告诉你结果
你可以在 Termux 里跑 Windows 游戏,用上硬件加速,画面流畅得像原生系统一样。
这件事听起来像某种技术预言,但它现在已经是现实了——一份完全开源的代码,就能做到这件事。
而这份代码只干了一件事:把两套原本没打算互相配合的软件,硬生生捏在了一起。
它解决了什么问题?
Android 上的硬件驱动,本来就是为手机 App 设计的,没考虑过 Termux 这种“非标准”环境。
· 驱动文件放在了 Android 禁止普通进程访问的地方 · 驱动接口只有手机系统才认得出来 · 驱动返回的内存地址,32 位程序根本读不懂
所以如果你想在 Termux 里跑个 Windows 游戏,那些官方渠道全都会告诉你:这条路不通。
但“不通”不代表做不到,只是没人愿意做而已。
我选择了一条没人走的路
我写了一个 shim——一个夹在系统加载器和硬件驱动之间的小模块。
它做的事情很直接:
· 打通 Android 对硬件库的隔离 · 让系统加载器“以为”这是一块标准的 GPU 驱动 · 修正驱动返回的地址,让 32 位程序也能读写
不修改系统,不刷模块,不 root。
只是给原本不兼容的两端,搭了一座桥。
这意味什么?
这意味着“兼容性”不一定要等官方施舍。
当一个场景太小众、没人愿意投入资源去适配的时候,你不需要从头造一个驱动,只需要理解两端各自在说什么,然后把它们翻译成对方听得懂的话。
这就是 shim 做的事。它不重写底层,不给系统动手术,它只是在夹缝里精准地补上那几个缺失的接口。
这个项目可以被用在哪里?
如果你希望在 Termux 上跑 Windows 程序、想在非标准环境里调用硬件加速、或者只是想知道“桥接”这件事到底能做到什么程度,那你可能会需要它。
它不是开箱即用的傻瓜式工具,但它是一套已经跑通的、完整的技术方案。
走不通的路,不一定真的是死路。
🤝 贡献
我们欢迎任何形式的贡献!如果你发现 Bug 或有改进建议,请提交 Issue 或 Pull Request。
⚖️ 许可证
· ICD Shim (icd/ 及 vulkan_gpu.c):采用 MIT License。 · WSI Layer (wsi/):基于 Sky1-Linux/vulkan-wsi-layer,同样采用 MIT 许可证,保留原始版权声明。 · 文档和测试程序:同样采用 MIT 许可证。
🙏 致谢
· Sky1-Linux/vulkan-wsi-layer – 提供了 WSI 层的稳定实现基础。 · Box64 – 强大的 x86_64 模拟器。 · Wine – 让 Windows 应用在 Linux 上运行。 · DXVK 及其分支 DXVK-Sarek – 出色的 Direct3D→Vulkan 翻译层。
📞 联系与支持
如果你在使用过程中遇到任何问题,欢迎在 GitHub Issues 中提出。也欢迎通过邮件或社区讨论分享你的经验。