Hotdry.

Article

Quake 1997源码在现代工具链复现:汇编兼容性与DirectX迁移的工程实践

复现1997年Quake编译环境需处理63个手写汇编函数、16位/32位兼容及工具链依赖,本文提供可落地的参数配置与检查清单。

2026-05-29systems

引言:复古编译的工程挑战

1999 年 id Software 开源 Quake 引擎时附带的 readme.txt 中,John Carmack 特别指出:"Masm is also required to build the assembly language files. It is possible to change a #define and build with only C code, but the software rendering versions lose almost half its speed." 这句话揭示了一个关键事实 ——Quake 的性能高度依赖 Michael Abrash 手写的手工优化汇编代码。

近三十年后,Fabien Sanglard 在 2026 年完成了一个极具价值的工程考古项目:在 VirtualBox 虚拟机中完整复现 1997 年的 Quake 编译环境。这项工作不仅是对游戏史的致敬,更为现代开发者处理遗留代码兼容性提供了可复现的参考路径。

工具链考古:从 VC++4 到 VC++6 的迁移

Quake 的 Win32 版本最初使用 Visual C++ 4.X 在 Windows NT 上开发,这是 1996 年中期可用的最新 Microsoft IDE。到 1999 年源码发布时,项目已迁移至 Visual C++ 6。现代复现需要严格遵循以下依赖链:

基础安装序列:

  1. Windows NT 4.0 或 Windows 98SE(支持 SMP 需重装系统以识别双 CPU)
  2. Visual C++ 6.0 标准版或专业版
  3. Visual Studio 6.0 Service Pack 5(vc6sp5.exe)
  4. MDAC 2.5(位于 sp5 解压目录的 mdac_typ.exe)
  5. VC++6 Processor Pack(vcpp5.exe)—— 提供 ml.exe 汇编器

一个关键的陷阱是工作区文件格式。VC++6 使用.dsw(工作区)和.dsp(项目)文件,而现代 Visual Studio 使用.sln 和.vcxproj。更隐蔽的问题是换行符敏感性 —— 从 GitHub 通过 FTP 获取的源码会破坏.dsw 文件,导致 VC++6 打开后显示 "无关联文件" 且不提供错误提示。必须使用原始 q1source.zip 中的文件,通过拖拽或 Quick 'n Easy FTP Server 传输以保持换行符完整性。

汇编兼容性:63 个优化函数的编译难题

Quake 源码包含 21 个.s 文件,共 63 个手写汇编函数。这些代码使软件渲染版本帧率从 22.7fps 提升至 42.2fps,几乎翻倍。现代复现的核心挑战在于:

语法差异: 汇编文件使用 AT&T 语法(GNU 汇编器风格),而非 Microsoft 的 Intel 语法。ml.exe(Microsoft Macro Assembler)需要 Processor Pack 才能正确识别这些文件。

架构特定优化: Abrash 的代码深度针对 Pentium FPU 流水线设计,包括:

  • 双流水线并行:利用 Pentium 的 U/V 整数流水线与 FPU 流水线同时执行
  • FXCH 零周期指令:将 FPU 栈转为寄存器数组,避免 487 时代的 4 周期交换开销
  • FDIV 重叠:在 39 周期的浮点除法执行期间,让整数单元绘制当前 8 像素跨度
  • 自修改代码:R_DrawSurfaceBlock8_mip 系列函数在运行时将 colormap 基址硬编码到指令流,避免寄存器占用和额外 ADD 指令

关键函数性能贡献:

  • D_DrawSpans8:+12.6 fps(墙面渲染)
  • R_DrawSurfaceBlock8_mip*:+4.2 fps(光照贴图烘焙)
  • D_Polyset*:+2.2 fps(模型渲染)

16 位 / 32 位边界:DOS 遗留与现代 Windows

Quake 源码同时承载 DOS 和 Win32 路径,产生以下兼容性问题:

内存模型冲突: 原始代码包含 DOS 实模式支持(VGA_UpdatePlanarScreen/VGA_UpdateLinearScreen),这些 16 位特定函数在现代 32 位 Windows 环境中需要条件编译排除。

FPU 精度控制: sys_dosa.s 和 sys_wina.s 中的 Sys_LowFPPrecision/Sys_HighFPPrecision 直接操作 FPU 控制字,这在现代操作系统中可能触发权限异常。

非 Intel CPU 支持: 将 id386 宏设为 0 可禁用所有汇编优化,但需要添加 nointel.c 到项目以提供 C 语言回退实现。这会产生与原始发布版本性能特征完全不同的二进制文件。

DirectX 版本迁移路径

原始 Quake 存在三个渲染后端:

  • quake.exe:DOS 版本,使用模式 X VGA
  • winquake.exe:Win32 软件渲染,使用 GDI
  • glquake.exe:OpenGL 加速版本

现代复现通常聚焦于 winquake.exe,因为它代表了 1997 年的典型 Windows 游戏架构。若需 DirectX 支持,社区维护的 D3DQuake 项目提供了 Direct3D 7.0 移植参考,但这已超出原始 1997 年代码范畴。

可落地的复现参数与检查清单

虚拟机配置:

  • 平台:VirtualBox 6.x/7.x
  • 系统:Windows NT 4.0 Workstation 或 Windows 98SE
  • 内存:64-128MB(符合 1997 年高端配置)
  • 显示:1280x1024(VC++6 安装界面在高分屏显示异常,建议 800x600)

编译验证步骤:

  1. 获取 q1source.zip(来自 Quake Official Archive)
  2. 使用 WinRar 2.50 解压(保持 8.3 文件名兼容性)
  3. 打开 WinQuake.dsw,执行 "Rebuild All"
  4. 若汇编失败,检查 ml.exe 是否存在于 VC++6 的 bin 目录
  5. 复制 PmProXX.dll、WdirXX.dll 及 id1 数据目录到输出目录
  6. 运行winquake.exe -wavonly +d_subdiv16 0 +timedemo demo1验证帧率

现代工具链替代方案: 对于需要在现代 Windows 上编译的场景,可考虑:

  • 使用 Clang 或 GCC 配合 GNU as 替代 ml.exe(需转换 AT&T 语法)
  • 用 SDL2 替换 Win32 特定 API 调用
  • 保留汇编优化需内联汇编或单独编译为目标文件后链接

结语

复现 1997 年 Quake 编译环境不仅是对游戏史的考古,更是对软件工程可复现性的压力测试。从换行符敏感的.dsw 文件到 Pentium 特定的 FPU 流水线优化,每个环节都展示了遗留代码兼容性的复杂维度。对于维护 20 年以上历史代码库的团队,Fabien Sanglard 的这项工作提供了宝贵的参考框架:完整的依赖文档、版本锁定的工具链、以及可验证的输出基准。


资料来源:

  • Fabien Sanglard, "Let's compile Quake like it's 1997!", 2026
  • Fabien Sanglard, "How Michael Abrash doubled Quake framerate", 2026
  • id Software, Quake source code (q1source.zip), 1999

systems

内容声明:本文无广告投放、无付费植入。

如有事实性问题,欢迎发送勘误至 i@hotdrydog.com