☰
如何快速验证 D3D12 转换管线:Madeira 的 DXIL 金丝雀着色器完整指南
2026/10/4 1:47:41 网站建设 项目流程

如何快速验证 D3D12 转换管线:Madeira 的 DXIL 金丝雀着色器完整指南

【免费下载链接】MadeiraRun x86-64 Windows PC games on jailed iOS via FEX-Emu + Wine + DXMT项目地址: https://gitcode.com/GitHub_Trending/mad/Madeira

Madeira是一个让 x86-64 Windows 游戏运行在越狱 iOS 上的项目,核心思路是 FEX-Emu + Wine + DXMT。其中最难的一环是把 Windows 的D3D12画到 Apple GPU 上——iOS 没有 D3D12,每个着色器必须先把DXIL中间格式编译成 Metal 库(Metal Shader Converter)。这条转换管线一旦出错,游戏就是黑屏。

Madeira 的答案很"金丝雀":不直接拿游戏开刀,而是先写一对极小、极刻意的 DXIL 金丝雀着色器,用 26~27 项自动化检查把"DXIL → 转换器 → Metal 库 → 渲染管线 → 像素读回"整条链路逐层验证,全部通过后才允许在其上搭建真正的 D3D12 实现。

为什么需要"预编译金丝雀":先证明管线,再谈游戏

在 iOS 上跑 D3D12 游戏的完整链路是:

游戏 DXBC/DXIL 着色器 → Metal Shader Converter 转换 → metallib 加载 → MTLRenderPipelineState → GPU 出图 → 像素读回验证

链路上任何一环失败都会静默地表现为"颜色不对"或"启动黑屏"。Madeira 的设计原则是:在写任何 COM 对象代码之前,先有一个门禁(gate)证明"转换出来的着色器能正确读回我们喂进去的颜色"。这就是 M1 里程碑的 msc_canary.mm 金丝雀测试,它的地位相当于矿井里的金丝雀——管线坏了,它先死,而且死法能精确指出坏在哪一环。

两个金丝雀着色器:故意最小的"探针"

金丝雀的 HLSL 源码只有几十行,刻意选成"能证明整条绑定路径的最小着色器":

  • 单常量缓冲金丝雀canary.hlsl:顶点着色器不读任何顶点缓冲,用SV_VertexID生成一个全屏三角形;像素着色器把b0根常量缓冲的颜色原样返回。像素值完全可预测,与光栅化规则无关。
  • 布局金丝雀canary_layout.hlsl:故意在根签名里放"4 个 32 位根常量 + 1 个根 CBV"两参布局——红、绿通道来自根常量,蓝通道来自 CBV。这样任何一侧的偏移错误都会让像素颜色出错,而不是错出一个"看起来合理"的颜色。

两份着色器编译成的 DXIL 固件 直接放在仓库里作为测试输入,绕开了"顶点取数"和"转换器"两个变量同时被测试的问题(M4 里程碑的立方体再单独证明真实顶点缓冲路径)。

🔑 金丝雀设计精髓:错误的绑定偏移/绑定点会产出"错误的颜色",而不是"可接受的颜色"——结果二元化,检查才能自动判定。

26 项检查:金丝雀如何逐层拆解转换管线

msc_canary.mm 的检查按"从加载到像素"的顺序分层推进,每一层独立判定:

层级检查内容证明什么
1️⃣ 转换器加载通过dlopen/dlsym解析 24 个入口符号iOS dylib 能在进程内加载,缺符号会点名报告而非 dyld 崩溃
2️⃣ 根签名驱动布局显式构造根签名(不复用编译器嵌入的 RTS0 块)真实运行时收到的根签名来自应用,参数缓冲布局必须随之正确生成
3️⃣ 转换产物DXIL 转出的 metallib 在目标设备加载并建成渲染管线转换器输出对设备"有效",而不只是字节合法
4️⃣ 像素读回两组常量状态必须精确读出(255,0,0,255)和(200,100,255,255)参数缓冲的偏移、绑定全部正确——验证而非假设
5️⃣ 错误边界无效 DXIL、截断字节流、不存在的入口名全部以有界、有名字的错误失败,而不是崩溃

其中第 4 层最妙:布局金丝雀把根常量放在字节 0..15、根 CBV 的 64 位 GPU 地址放在字节 16,红/绿来自常量、蓝来自 CBV,"两侧任何一侧的布局错误都会显形且无歧义"。

缓存安全:预编译产物什么时候可以复用

金丝雀还顺带验证了着色器缓存的前提:

  • 相同输入重编译,产出字节一致的 metallib——这是把转换产物落盘复用的基础(但 README 特别强调:字节一致既非必要也非充分条件,关键是把所有相关输入都纳入缓存键)。
  • 缓存键敏感性:改最低部署版本、改根签名,输出必须变化或拒绝编译。
  • GPU 族级(GPU family)是最深的坑:同一着色器在 macOS 上 Apple9 与 Metal3 产出字节相同,换到 iOS 设备上却产出不同字节——"保留族级在缓存键里"这个看似多余的决定在另一个平台上救了正确性。

生产环境的缓存实现是 madeira_dxil_cache.h:缓存键覆盖字节码、入口名、根签名、目标 OS/GPU 族/最低版本等 DXIL 分支真正读取的全部输入,并额外存一个独立的 64 位校验哈希防止文件名碰撞误命中;它的行为由纯 C 主机测试 dxil_cache_test.c 在无设备、无 SDK 的情况下先行验证。

真机成绩单:从 macOS 到 iPhone 13 Pro

同一套金丝雀在不同环境全部通过,结果对照如下(来自 README.md):

环境结果首次转换耗时缓存重编译
macOS M4 Max✅ 26/261.0 ms0.4 ms
iOS 越狱 vphone VM✅ 26/262.2 ms0.3 ms
真机 iPhone 13 Pro(A15 GPU)✅ 27/2716.8 ms0.6 ms

跨目标测试更给出决定性结论:iOS 上编译、macOS 上加载的 metallib 哈希与 macOS 本地编译逐字节相同,"iOS 设备自己转换产物 → 远程 Mac 渲染"的回退方案因此不需要。

金丝雀还抓到一个真实缺陷:转换器不校验入口点名——用NoSuchEntryPoint编译能成功,唯一线索是反射返回空函数名,若不拦下,故障会延后到"缺少 MTLFunction"才爆炸。运行时自行加边界,正是金丝雀的价值。

金丝雀门禁之后:从 26/26 到 90/90

通过门禁后,项目才逐层往上盖楼,每一步都保持"检查数全绿":

  1. M1 in-app:金丝雀编译进libdxmt,在 Madeira 自己的签名和沙箱里dlopen运行,27/27 通过——顺带验证了"dylib 能用应用的 team 签名被 dyld 接受";
  2. M2:独立madeira_d3d12.dll(ARM64EC)实现 D3D12 COM ABI,41/41;
  3. M3:真实 GPU 缓冲拷贝与读回、无屏渲染,77/77;
  4. 运行时 DXIL 转换 + M5 纹理:fixture 模式彻底移除,应用着色器在管线创建时当场转换,90/90 通过。

新手 takeaway:这套方法的 3 个可复用技巧

  • 把"管线对不对"和"游戏对不对"分开验证:先造一个输出完全可预测的最小探针(全屏三角形 + 原样返回颜色),失败即定位到具体环节;
  • 错误必须"显形":探针的输出设计成"错了就是错色,而不是错出一个合理值",才能让自动化检查二元判定;
  • 在便宜的环境先跑:金丝雀先在 M4 Max 跑、再上 iOS VM、最后上真机 A15——三种故障(转换器本身 / 打包签名 / 启动路径)被刻意分开,"值得各自独立的失败"。

相关源码与文档:

  • 金丝雀着色器:canary.hlsl、canary_layout.hlsl
  • 金丝雀测试:msc_canary.mm
  • DXIL 转换缓存:madeira_dxil_cache.h
  • 模块里程碑与实测数据:README.md
  • 转换 SDK 许可说明:metal-shader-converter/README.md

【免费下载链接】MadeiraRun x86-64 Windows PC games on jailed iOS via FEX-Emu + Wine + DXMT项目地址: https://gitcode.com/GitHub_Trending/mad/Madeira

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询