A great old-timey game-programming hack (2013)
278 points • 1 day ago • Article Link

早在 1980 年代初期,当 8-bit PC 硬件受到极其严重的限制时,制作流畅、可玩的电子游戏是一项极其耗费精力的优化工作。作者的一位朋友当时正在开发一款图形密集的横向卷轴游戏,游戏几乎已经完成,但却深受帧率过低而无法运行的问题困扰。尽管程序员已经应用了诸如循环展开(loop unrolling)等标准优化手段,但游戏速度依然缓慢。当时的挑战在于硬件缺乏诸如 blitters 或 texture mappers 等现代特性,这要求程序员必须手动逐字节地移动数据以将图像绘制到屏幕上。

作者在研究代码后意识到,瓶颈在于负责将 background tiles 复制到 screen buffer 的那段程序。机器使用的 6809 处理器提供了一些特定且强大的寻址模式,但朴素的复制例程的周期计数依旧很高。通过进一步展开循环,并手动计算内存访问的偏移量,他们实现了 60% 的速度提升。然而,这仍不足以满足游戏的性能要求,于是作者开始寻求更非传统、近乎"疯狂"的解决方案。

在查阅 6809 微处理器手册后,作者发现处理器的 stack 指令可以被改造成用于快速数据传输。通过征用硬件 stack 寄存器,即 S 和 U,作者开发出一种方法:利用包括 DP 寄存器在内的所有可用寄存器,以单次突发(single burst)的方式把数据从 tile 中取出并推送到 screen buffer 。这种方案效率极高,但也带来了副作用——tiles 会被绘制得上下颠倒,且左右两半被交换。

为了解决这一问题,两人意识到这种"搅乱"过程其实是一个 involution 。于是他们在游戏运行前就对 tiles 做了预处理,提前把内部结构反转。这样一来,等到把被破坏的数据绘制到屏幕后,图像在到达屏幕时会被自动完美地还原。该方案带来了 70% 的速度提升,基本有效地解决了性能问题。但当他们为声音加入 hardware-clocked interrupts 时,最后一道难题出现了。由于该例程需要征用 stack pointer,如果在绘制 tile 的过程中触发中断,就会覆盖保存 reference tiles 的内存。

他们通过巧妙地交换 stack 指针来化解这次最终的技术危机,确保中断造成的任何破坏都会落到 screen buffer 上,而不是原始的 tile data 。为了避免这种短暂的屏幕异常被玩家看到,他们调整了绘制顺序:从高内存往低内存绘制。这样,任何意外的损坏都会在下一张 tile 被绘制时被覆盖,从而保证在 buffer 送往显示器之前问题不会显现出来。

回想当初,作者把这段经历视作早期计算时代所必需的创造力的证明。面对极其苛刻的硬件限制,程序员不得不反复尝试、失败并迭代,才能找到既巧妙又有时不那么正统的解决方案。这个故事也提醒人们:在这样的技术挑战中,同样能收获乐趣,并鼓励他人把自己的经验记录下来,免得这些具有专业性与历史意义的见解随着时间流逝而被遗忘。

69 comments • Comments Link

- VGA 早期的硬件开发充满激烈竞争与高风险,越是处在这种环境里,越能看出那些聪明的即兴办法有多能打——例如用最小化的 "one-deep" FIFO,就能在复杂且昂贵的工程成果之外取得胜过对手的效果。

- 更高级的优化往往依赖寻找绕过常规思路、甚至可以说"无视术语"的硬件操纵方法。早期的 MS-DOS 程序员就是典型:他们用位掩码表和 CPU 原生指令(比如 XLAT)替代慢吞吞的字符串匹配循环,因而获得了巨大的性能提升。

- 在极其受限的环境里工作,依然会迫使开发者借助基础的计算机科学手段来绕过平台限制。比如计算预算有限的 AWS CloudFront 函数,就会让人用二分查找这类位表示数据的方案来做事。

- 那些历史悠久的编码环境虽然缺少今天常见的抽象层和包管理器,却培养出开发者与硬件之间深刻而直观的连接感,从而催生了至今仍被现代软件理念反复沿用的"神奇"成果。

- 图形编程依旧给低级实验留出了空间。因为把引擎移植到游戏机,或为了在弱硬件上跑得更快而做优化的人,往往会直接和图形内存打交道,这延续了 8-bit 时代那种"贴近硬件"的体验。

- 用 6502 的堆栈指针去当工具,顺手改作操控 Atari 2600 上对象计数器的手段,这类巧技说明:真正的性能突破通常来自变通,而不是去破坏处理器架构本应被使用的方式。

- 在互联网出现之前,信息极度匮乏,迫使程序员把自己困在孤立的知识"孤岛"里。他们靠口耳相传、依托本地电脑俱乐部以及爱好者杂志来共享发现,进而形成了一种独特的口述传统和深度协作的文化。

- 现代编译器优化与硬件复杂性,让不少 8-bit 时代的汇编技巧已经过时。但"统治硬件"——也就是理解内存布局、别名关系以及指令实际会做什么——这种底层哲学,依然对性能关键型系统具有现实价值。

- 从"口述"与孤立发现,走向信息充沛、获取便利的现代时代后,挑战也从"去找信息"变成了"从垃圾信息里筛出高价值的洞见"。

- 投身小众、低级的项目开发,能带来一种别具一格的冒险与发现感,让现代程序员更容易和那些"Real Programmers"之间建立起联系——他们以直接操控比特的方式实现曾被认为不可能的事情。

这场讨论流露出对 bare-metal 编程年代的深深怀念——当时受限的硬件把开发者逼进一种带创造力的绝望状态。这些限制反过来充当创新的催化剂,让人们能以厂商从未预想到的方式对硬件做出架构层面的 hacks 。尽管今天的开发越来越抽象,但对话指出:这些 hacks 的精神——对效率的不懈追求,以及对代码如何与硅片发生交互的深刻理解——在图形与高性能计算等专业领域里仍然存在。参与者普遍认同:即便现代工具与编译器已经让许多具体的汇编技巧不再重要,掌握硬件这一核心挑战依然关键,只是这种稀缺性正在逐渐加剧。