为什么fastDoom这么快

Fabien Sanglard 新闻

摘要

关于fastDOOM移植版相比原始Doom可执行文件实现显著性能提升的详细技术分析,涵盖了Doom源代码传承历史及具体优化技巧。

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/05/16 03:35

# 为什么 fastDOOM 这么快 来源:https://fabiensanglard.net/fastdoom/index.html 2025 年 3 月 4 日 为什么 fastDOOM 这么快 --- 2024 年冬天,我修复了一台 IBM PS/1 486-DX2 66Mhz "Mini-Tower" 型号 2168 的电脑。那是我少年时期一直想要却买不起的机器。言语无法形容我在修复这台机器时的喜悦。 一拿到能启动的系统,我立即对想运行的软件进行了基准测试。 `` C:\DOOM>doom.exe -timedemo demo1 timed 1710 gametics in 2783 realtics `` DOOM 不会直接给出帧率,需要简单计算一下。本例中,1710/2783*35 = **21.5** fps。对于 1993 年 12 月能(合理)买到的最佳机器(广告页 (https://fabiensanglard.net/fastdoom/pentium_ad_pcmag_nov_1993.jpg)、规格 (https://fabiensanglard.net/fastdoom/ASTRA01.png)、芯片组 (https://fabiensanglard.net/fastdoom/ASTRA02.png)、显卡 (https://fabiensanglard.net/fastdoom/ASTRA06.png)、硬盘1 (https://fabiensanglard.net/fastdoom/ASTRA03.png)、硬盘2 (https://fabiensanglard.net/fastdoom/ASTRA04.png)、SpeedSys (https://fabiensanglard.net/fastdoom/speedsys.png))而言,这个表现相当体面。 我原本打算忍受着头痛继续玩,直到听说了 fastDOOM。我通常不太喜欢移植版,因为往往会添加一堆不协调的功能(梦幻般的 Chocolate DOOM 除外),但出于好奇我还是试了试。 `` C:\DOOM>fdoom.exe -timedemo demo1 Timed 1710 gametics in 1988 realtics. FPS: 30.1 `` 在不砍任何功能的前提下,快了 30%[\[1\]](https://fabiensanglard.net/fastdoom/index.html#footnote_1)!在 doom2 的 demo1 这样要求高的地图上,提升更加明显,从 **16.8** fps 跃升至 **24.9** fps,快了 48%! 我没想到 DOOM 还有这么大的优化空间。毕竟一年内发布,优化时间确实有限。我得弄清楚这个魔法是怎么实现的。 历史背景 --- 深入 fastDOOM 之前,先了解代码的来源。DOOM 最初是在 NeXT 工作站上开发的。游戏结构为了便于移植,大部分代码集中在核心中,周围是执行 I/O 的小子系统。 来源:《Game Engine Black Book: DOOM》开发期间,id Software 编写了 DOS 的 I/O 代码。这些代码成为 DOOM 的商业发行版。但该版本在 1997 年无法开源,因为它依赖一个名为 DMX 的专有音频库。 最终开源的是 Linux 版本,由 Bernd Kreimeier 在编写引擎解读书籍项目时清理过。 DOS 版 DOOM 是通过组合 Linux 核心、Heretic 的 I/O 和 APODMX(Apogee 音频封装)来模拟 DMX 重建的。由于 Heretic 使用视频模式 13h,而 DOOM 使用模式 Y,图形 I/O(`i_ibm.c`)是通过反汇编 `DOOM.EXE` 逆向工程得到的。社区就这样获得了 PCDOOM v2[\[2\]](https://fabiensanglard.net/fastdoom/index.html#footnote_2)。 fastDOOM 的起点就是 PCDOOM v2。 `` ┌───────────────┐ │ NeXTStep DOOM │ └─────┬────┬────┘ │ │ │ │ │ │ ┌────────────┐ │ │ ┌──────┐ ┌─────────┐ │ Linux DOOM │◄─┘ └─►│ DOOM ├─────►│ Heretic │ └──────┬─────┘ └──────┘ └────┬────┘ │ ⁞ │ │ ▼ │ │ ┌──────────┐ │ └─────────────►│ PCDOOMv2 │◄────────┘ └─────┬────┘ ▼ ┌──────────┐ │ fastDOOM │ fastDoom 族谱 └──────────┘ ────────────────── `` 性能全景 --- Victor "Viti95" Nieto 写过版本说明,描述每个版本的性能改进,但他似乎更热衷于让 `FDOOM.EXE` 变得出色,而不是详细描述实现方法。 为了了解性能随时间的演变全貌,我下载了 fastDOOM 的所有 52 个版本、PCDOOMv2 以及原始 `DOOM.EXE`,然后写了一个 Go 程序生成一个 `RUN.BAT`,对所有这些版本运行 `-timedemo demo1`,并用 mTCP 的 `NETDRIVE` 挂载起来。 我选择对 `DOOM.WAD` 进行 timedemo,打开声音,屏幕大小设为 10(全屏带状态栏)。经过几个小时的霰弹枪和小鬼折磨后,我完整跑了五遍测试集,并用 `chart.js` 绘制了平均帧率图。 这张图首先可以排除的是:fastDOOM 的提升主要不来自现代编译器。`PCDOOMv2` 是用 OpenWatcom 2 构建的,但相比 `DOOM.EXE` 只有微小的提升。 Git 考古 --- 除了频繁发布,Viti95 还展示了出色的 Git 纪律:一个提交只做一件事,每个版本都打了标签。fastDOOM 的 Git 历史包含 3,042 个提交,这使得我们可以逐一测试每个特性的效果。 我又写了一个 Go 程序来构建每一个提交。这里就不详述处理众多构建系统变动(尤其是从 DOS 到 Linux 的切换)的繁琐细节了。一小时后,我得到了有史以来最丑陋的程序,以及 3,042 个 `DOOM.EXE`。令我欣慰的是,构建几乎从未失败过。 根据文件大小绘图显示,早期工作是通过清理和删除代码来精简的。有明显的下降点:bf0e983 (https://github.com/viti95/FastDoom/commit/bf0e983ed00f038b65a34f10fa626abb99c87fe6)(build 239,移除了声音录制)、5f38323 (https://github.com/viti95/FastDoom/commit/5f3832310b32c32895688b3112301f98e77119b8)(build 0340,删除了错误代码字符串)、8b9cac5 (https://github.com/viti95/FastDoom/commit/8b9cac591945fa93af84c0e85845b6a55bc76fe3)(build 1105,用 NASM 替换了 TASM)。 深入分析 --- 对所有构建进行 timedemo 会耗费极长时间(3042 × 1.5 / 60 / 24 × 3 轮 = 9 天),因此我专注于速度提升最大的版本。我又写了一个 Go 程序,生成一个 `.BAT` 文件,对 `v0.1`、`v0.6`、`v0.8`、`v0.9.2` 和 `v0.9.7` 中的所有提交运行 timedemo。我用 mTCP 挂载了 1.4 GiB 的 `FDOOM.EXE` 并运行。这花费了不少时间,因为包含 200 多个提交的版本运行时间需要 8 小时/轮。 fastDOOM v0.1 --- 该版本包含 220 个提交。 `` $ git log --reverse --oneline "0.1" | wc -l 220 `` 图表可点击并可鼠标悬停 v0.1 中最有价值的补丁无疑是 build 36 (e16bab8 (https://github.com/viti95/FastDoom/commit/e16bab87df86baaf2bdec5e17e4844fac9b110be))。“Crispy 优化”将状态栏百分比渲染变为无操作(如果数值未变化)。这避免了渲染到临时缓冲区再复制到屏幕,总计提升 2 fps。起初我不太相信,怀疑工具链有 bug。但在 PCDOOMv2 上单独应用这个补丁后,确认了巨大的速度提升。 接下来是 build 167 (a9359d5 (https://github.com/viti95/FastDoom/commit/a9359d599e339d7e8bcd730ea2bed7cc22fa2947)),通过宏内联了 FixedDiv。 接近尾声时,我们看到一系列优化,每个提升 0.5 fps。 Build 207 (9bd3f20 (https://github.com/viti95/FastDoom/commit/9bd3f207df1837f2b9cb87ca2e67fdc01fcc1a95)):一个 PSX DOOM 的优化,优化了 BSP 遍历方式。Build 212 (dc0f48e (https://github.com/viti95/FastDoom/commit/dc0f48e22d2804bf025d1b8efe4436311ad1d56d)) “内联 R_MakeSpans”,用于渲染水平面。 总体来说,这个版本删除了大量代码(50% 的提交是删除操作),这很可能有助于优化我机器上 486 的缓存行。 `` git log --reverse --oneline "0.1" | grep -i -E "remove|delete" | wc -l 100 `` 有趣的是,我的一个补丁 (https://github.com/viti95/FastDoom/commit/609c42df) 出现在了 fastDOOM 中。大概是在我写《Black Book》的时候?我完全不记得写过这个! fastDOOM v0.6 --- 该版本包含 33 个提交。 `` $ git log --reverse --oneline "0.5"^.."0.6" | wc -l 33 `` 图表可点击并可鼠标悬停 在许多小优化中(比如 GbaDOOM341 (https://github.com/viti95/FastDoom/commit/e745a5dc62c21de8926e5845b41fea89cd7f03ad)),有一些最有价值的。 Build 342 (22819fd (https://github.com/viti95/FastDoom/commit/b6aea6ab6c966d1d78edb5da915871e6a22819fd)):跳过渲染不需要的 visplane。Build 359 (40e0d4b (https://github.com/viti95/FastDoom/commit/3329d704e081d8a18e7fd3a9c4a239d8840e0d4b)):移除了一层玩家指针间接引用。Build 360 (ccd296f (https://github.com/viti95/FastDoom/commit/8e5e6355d232d3f4dfd9203dc6aa963adccd296f)):进一步移除间接引用。Build 369 (f29e665 (https://github.com/viti95/FastDoom/commit/dff38f9fa903f15b24fb13da712feeb09f29e665)):内联了屏幕空间线段分割器。 fastDOOM v0.8 --- 该版本包含 282 个提交。 `` $ git log --reverse --oneline "0.7"^.."0.8" | wc -l 282 `` 声音系统不太稳定,因此我必须在不开启声音的情况下进行 timedemo,然后标准化帧率。此外,v0.8 似乎专注于文本模式渲染器,因此在 Build 670 (a92c67f (https://github.com/viti95/FastDoom/commit/50848560577f8d008b5c7f5bb69d7595fa92c67f)) 和 Build 730 (c3f5f50 (https://github.com/viti95/FastDoom/commit/17095d801f6603f9ab8091fa60453d5b6c3f5f50)) 时出现了两次性能回退,Crispy 优化消失了。 图表可点击并可鼠标悬停 **最有价值的:** Build 792 (f279b7d (https://github.com/viti95/FastDoom/commit/b253754a80045e9809ccfb7bf0f0cf680f279b7d)):每个渲染器一个可执行文件(`FDOOM.EXE`、`FDOOM13H.EXE` 等)。Build 793 (1874ee8 (https://github.com/viti95/FastDoom/commit/5ddc4e1c967a737c271327cbd4290137a1874ee8)):关闭编译器的调试功能。Build 796 (6aae724 (https://github.com/viti95/FastDoom/commit/85a895802e750360e4251e7fd836906db6aae724)):恢复 Crispy 优化。Build 794 (1366ebf (https://github.com/viti95/FastDoom/commit/eefc599d6c740e890fffa96b5d68b03281366ebf)):尽可能编译更少的代码。 fastDOOM v0.9.2 --- 该版本包含 110 个提交。 `` $ git log --reverse --oneline "0.9.1"^.."0.9.2" | wc -l 110 `` 图表可点击并可鼠标悬停 **最有价值的:** Build 1639 (ae2a951 (https://github.com/viti95/FastDoom/commit/7d992d86be7e3b3492e7c032d76ea90a2ae2a951)):优化 skyflatnum 比较。Build 1645 (0730cdc (https://github.com/viti95/FastDoom/commit/9fb58c2ec8072c5de6e5f4bf8094b51b30730cdc)):为模式 Y 优化 R_DrawColumn。Build 1646 (17c9e83 (https://github.com/viti95/FastDoom/commit/c906b0cba8aac6e44698c7a942b77641217c9e83)):清理 R_DrawSpan 代码。 fastDOOM v0.9.7 --- 该版本包含 293 个提交。 `` $ git log --reverse --oneline "0.9.6"^.."0.9.7" | wc -l 294 `` 尽管多次运行基准测试,我仍无法降低这个版本的噪声。 图表可点击并可鼠标悬停 **最有价值的:** Build 1941 (0688235 (https://github.com/viti95/FastDoom/commit/c0657d1eb1ca2f6caebbda58c5fb20b150688235)):测试 x86 ASM 变更。Build 1943 (f326e73 (https://github.com/viti95/FastDoom/commit/cd506d432d63d0e0813b97c8d7c915c02f326e73)):添加 CPU 选择 + 针对 386SX 的 CR2 优化。Build 1944 (a836abb (https://github.com/viti95/FastDoom/commit/7682c40f69bd7db825670758b9a0fb881a836abb)):为 R_DrawSpan386SX 添加 ESP 优化。Build 2000 (3432590 (https://github.com/viti95/FastDoom/commit/38fbcdbd9ccc4c077daa8bd65138f2b403432590)):添加用 ASM 渲染浊影列的基础代码。Build 2031 (0edab46 (https://github.com/viti95/FastDoom/commit/95232f3d748978a27d142f1cbf7ef40710edab46)):每次循环移除一次 CMP 比较(Ken Silverman 的优化 (https://github.com/viti95/FastDoom/issues/143)?)。 模式 13h 与模式 Y --- fastDOOM 探索了许多加速方法,覆盖广泛的 CPU(386、486、Pentium、Cyrix)和视频总线(ISA、VLB、PCI)。在我的机器上效果不佳(性能更差)的一项优化是使用视频模式 13h 而非模式 Y。 在模式 13h 下,向 VGA 四个显存体传输数据的调度由硬件完成。对于 CPU 来说,显存就像一个单一的线性 320x200 帧缓冲。缺点是无法在显存中双缓冲,因此必须在内存中实现,这意味着字节会被写入两次:先写入内存中的帧缓冲,然后第二次发送到显存。此外,引擎必须等待 VSYNC。 `` 模式 13h ──────── RAM VRAM (VGA card) SCREEN ┌───────────────────┐ ┌───────────────────┐ ┌───────────────────┐ │ ┌───────────────┐ │ │ │ │ │ │ │ framebuffer 1 │ │ │ │ │ │ │ └───────────────┘ │ │ │ │ │ │ ┌───────────────┐ │ │ ┌───────────────┐ │ │ │ CPU ────►│ │ framebuffer 2 │ ├────► │ │framebuffer(fb)│ ├──────►│ │ │ └───────────────┘ │ │ └───────────────┘ │ │ │ │ ┌───────────────┐ │ │ │ │ │ │ │ framebuffer 3 │ │ │ │ │ │ │ └───────────────┘ │ │ │ │ │ └───────────────────┘ └───────────────────┘ └───────────────────┘ `` 模式 Y 允许程序员单独访问 VGA 显存体。这样可以在显存中实现三缓冲。而且,它的优点是可以直接写入显存,只写一次字节。开发者需要通过速度很慢的 `OUT` 指令手动选择目标体,但这也允许通过锁存器[\[3\]](https://fabiensanglard.net/fastdoom/index.html#footnote_3)同时写入两个 VGA 体来水平复制像素(从而免费实现低细节模式)。另一个缺点是由于需要从显存回读,绘制不可见的幽灵会慢得多。 `` 模式 Y ─────── VRAM (VGA card) SCREEN ┌───────────────────┐ ┌───────────────────┐ │ ┌───────────────┐ │ │ │ │ │fb1 | fb2 | fb3│ │ │ │ │ └───────────────┘ │ │ │ │ ┌───────────────┐ │ │ │ │ │fb1 | fb2 | fb3│ │ │ │ CPU ──────────────────────────────► │ └───────────────┘ ├──────►│ │ │ ┌───────────────┐ │ │ │ │ │fb1 | fb2 | fb3│ │ │ │ │ └───────────────┘ │ │ │ │ ┌───────────────┐ │ │ │ ``

相似文章

WinQuake 存在的原因及其工作原理

Fabien Sanglard

深入探讨创建 WinQuake(Quake 的 Windows 原生版本)的历史原因,以及它如何在 Windows 95 和 NT 上实现接近 DOS 版本的性能。