【免费下载链接】pinyon-shift
A playable preview of Forza Horizon static recompilation
Pinyon Shift 把 Xbox 360 版《Forza Horizon》重新编译为 PC 原生程序,其核心是一套专为这款游戏打造的原生渲染器:它不再模拟 Xenos GPU,而是直接解读游戏发出的 PM4 命令流,在 Vulkan 上执行 EDRAM 表面管理、Resolve 与纹理解码,让游戏在 1x 到 4x 分辨率下以显示器刷新率运行。这篇文章带你完整看懂这条渲染链路。
为什么不用通用模拟,而要"游戏专用"渲染器
项目最初基于 ReXGlue(Xenia 衍生)的 Xenos 模拟渲染器,帧率只有约 28 fps。团队换了思路:先对游戏实际发给 GPU 的内容做一次完整"普查"(census)——在 17,580 帧脚本化路线上逐帧统计,而不是猜测。结果是:游戏只用极其有限的 GPU 特性集——
| 游戏使用的特性 | 数量 |
|---|---|
| 渲染目标配置(Render target configurations) | 29 种(后续路线 +11) |
| Resolve 种类 | 20 种(+10) |
| 纹理格式与维度组合 | 22 种 |
| 每个 draw 的颜色目标 | 固定 1 个(无 MRT) |
| 图元类型 | 三角带、列表、四边形、点、矩形 |
| 内存导出(memexport)draw | 0 |
| 前缓冲 | 恒为 1280×720、10:10:10:2 |
有限且可枚举的"帧契约",意味着可以写一个只处理这套配置、其余一律记录原因并拒绝的渲染器。这套契约记录在 NATIVE_FRAME_CONTRACT.md;某模式只有在整条路线上"跳过计数为零"时才算被覆盖。
PM4 命令流:解码、录制、提交三条流水线
游戏的 GPU 命令以 PM4 数据包的形式写入命令环。几乎全部 draw 都以PM4_INDIRECT_BUFFER形式到达——游戏提前建好了命令缓冲,每帧只是重放;实测每帧约 5,000 个 draw 中只有 42 个来自即时发射器,且 96% 的间接缓冲在相邻帧间字节级一致。
渲染器把这拆成多个职责清晰的线程:
| 线程 | 职责 |
|---|---|
| Guest render thread | 游戏自带的 D3D 运行时写 PM4 包 |
| GPU Commands | 把数据包解码进影子寄存器文件,处理WAIT_REG_MEM,加载微码时做哈希 |
| GPU Recorder | 持有真正的寄存器状态、缓存与执行器,为每个 draw 推导管线并录制延迟命令流 |
| GPU Submission | 每 1,024 个 draw 向 Vulkan 提交一次(vkQueueSubmit),并负责上屏 |
| Pipeline / Shader / Storage 工作线程 | 在热路径之外构建管线、翻译着色器、写缓存 |
顺序是难点:一个比赛帧约有 280 次事件写入、25–30 次 fence 写入、5 次中断和 1 次 swap。fence 写入是"记录"而非"等待",只有当录制线程消费完其之前的全部内容,游戏才能看到 fence。解码与录制拆分后,比赛场景从 17.67 ms 降到 16.46–16.75 ms 每帧。这条链路对应的宿主侧入口在 guest_output_renderer.cpp 和 graphics_hooks.cpp。
EDRAM 表面与转移(Transfer)
Xbox 360 的 Xenos GPU 先画进 10 MB 片上显存 EDRAM,再显式拷出到主存。FH1 原生执行器把 EDRAM 抽象成一张 2,048 个 tile 的所有者地图:
- 表面(Surface):每个渲染目标是宿主图像,键为 EDRAM 基 tile、pitch、MSAA 模式、深度/颜色与格式,高度延伸到同位置下一个表面;
- 所有者地图:记录每个 tile 最后被哪个表面写入,以及其 stencil 是否可能非零;
- 转移(Transfer):当某个 draw 占用别的表面拥有的 tile 时,旧内容必须搬走——颜色走像素 pass,深度走经 word buffer 的 compute pass。没有
VK_EXT_shader_stencil_export扩展的显卡(如 NVIDIA)上,深度转移要 9 个 pass。
一个巧妙的优化:对ALWAYS深度测试或不裁剪的 draw,执行器直接在CPU 上跑一遍顶点着色器来圈定覆盖范围,避免"一个 stencil draw 声称占用全部 2,048 个 tile"这类过度保守的转移。转移的瓶颈是 barrier 而非像素量。
Resolve:结果如何回到"游戏可见"的纹理
Xenos 的 resolve 是把 EDRAM 内容显式拷回主存的机制。在 FH1 原生执行器里,resolve 执行一个 compute 着色器,按游戏的分块纹理布局写入一份"游戏内存的宿主镜像",并标记该区间"由 GPU 写过"。随后:
- 与该区间重叠的纹理全部失效,下次采样时按请求格式重新解码——一个机制同时覆盖了部分 resolve、多格式别名同一地址、CPU 复用内存等所有情况;
- 深度 resolve 必须精确复现打包的 depth+stencil 位,因为后续 draw 会以
24_8/24_8_FLOAT读取它(可接受的差异只有 D24S8 舍入的 1 个 LSB 与 float24 低尾数位)。
快速路径叠在其上:resolve 直接写入"当前仍然有效"的纹理(raw-bits 视图),reload 时只重新分块 resolve 改动过的 32 行带。普查还发现唯一的 CPU 回读场景是车辆缩略图(渲染→resolve→CPU 压缩),因此对 256 KiB 以上的一次性 resolve 会在下一个 CPU 可观察命令前拷回游戏内存。20 种 resolve 的完整表格见 NATIVE_FRAME_CONTRACT.md。
纹理解码:从游戏内存到 Vulkan 采样
纹理缓存复用 Xenia 衍生的核心,跑在执行器的内存镜像之上:
- 键:来自采样常量——基地址、格式、尺寸、pitch、tiling、mip 范围、端序与 swizzle;
- 解码:compute 着色器做"反分块 + 字节交换",覆盖 DXT1/3/5、DXN、CTX1、DXT3A/5A、10:11:11 以及 16 位归一化与深度格式;不支持 BC 的设备可强制走通用 BC 解码路径;
- 失效:CPU 写用页写监视(page write-watch)发现,GPU 写由 resolve 标记;靠"每次分配的写入代际"(write generation)而非地址区分。这一细节曾让一条路线的顶点上传量从 41.6 GB 降到 0.42 GB;
- Gamma:主机的 gamma 纹理用分段线性曲线转换,默认在着色器内精确复现,也可切换更省但中间调偏暗的 sRGB 快速路径。
着色器与上屏:链路的最后两环
- 着色器:游戏的 Xenos 微码由 ShiftGlue 翻译器转成 SPIR-V。为这款游戏修过三个关键问题:HDR 采样的指数偏置读错字、全像素 quad 才执行的前向跳转、以及带导数 cube 采样改用隐式 LOD+偏置。翻译结果与管线描述跨次保存,设置阶段还会隐藏运行约两分钟的预热路线——空缓存时首帧超预算时间从 13.0 s 降到 5.7 s。着色器语料工具能从光盘的
.fxobj文件提取 3,349 个唯一程序,打包为可选的.pnsp格式;相关工具见 shader_capture.cpp 与 SHADER_PACK_FORMAT.md。 - 上屏:swap resolve 把前缓冲写入镜像,presenter 应用游戏 gamma 曲线与可选 FXAA,再用双线性、CAS 或 FSR 1 缩放;Windows 走 scRGB HDR,Android 走 HDR10 并随面板方向预旋转交换链。
- 分辨率:1x–4x 为整倍缩放(着色器翻译器把缩放烘焙进去),guest 可见的一切保持游戏尺寸,只有渲染缓冲随倍率放大。
延伸阅读与关键文件
| 主题 | 位置 |
|---|---|
| 帧契约(29 表面 / 20 resolve / 22 纹理布局) | NATIVE_FRAME_CONTRACT.md |
| 官方站点渲染器章节 | renderer.md |
| 每项优化的实测数字 | gpu-optimizations.md |
| Xenos 模拟路径的退役记录 | XENOS_RETIREMENT_BACKLOG.md |
| 早期研究笔记(含失败路线) | RESEARCH.md |
| 宿主侧渲染器入口 | guest_output_renderer.h |
Pinyon Shift 原生渲染器最有借鉴价值的经验是:用普查得到的有限契约替代通用模拟——先花两周数清楚游戏到底用什么,再写一个"只处理这些、其余全部拒绝并记录"的执行器。这套方法同样适用于任何静态重编译项目。
【免费下载链接】pinyon-shift
A playable preview of Forza Horizon static recompilation
相关推荐
ZeldaWWHDRecomp的Vulkan渲染器内幕:从Guest命令到原生渲染管线的完整解析
ZeldaWWHDRecomp的Vulkan渲染器内幕:从Guest命令到原生渲染管线的完整解析 ZeldaWWHDRecomp 是一款将《塞尔达传说:风之杖
Dejavu指纹生成原理深度解析:从频谱图到哈希指纹的完整流程
Dejavu指纹生成原理深度解析:从频谱图到哈希指纹的完整流程 Dejavu音频指纹识别系统是一个基于Python的强大音频识别工具,它通过独特的指纹生成算法将
音频处理终极指南:Prerender核心原理深度解析,从URL到HTML的完整渲染流程
终极指南:Prerender核心原理深度解析,从URL到HTML的完整渲染流程 Prerender是一个基于Node.js的服务,它使用Headless Chr
后端开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考