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 送往显示器之前问题不会显现出来。
回想当初,作者把这段经历视作早期计算时代所必需的创造力的证明。面对极其苛刻的硬件限制,程序员不得不反复尝试、失败并迭代,才能找到既巧妙又有时不那么正统的解决方案。这个故事也提醒人们:在这样的技术挑战中,同样能收获乐趣,并鼓励他人把自己的经验记录下来,免得这些具有专业性与历史意义的见解随着时间流逝而被遗忘。
Back in the early 1980s, when 8-bit PC hardware was incredibly limited, creating smooth, playable video games was a grueling exercise in optimization. A friend of the author was developing a graphically intensive side-scroller that was nearly complete but suffered from unplayable frame rates. Despite the programmer having already applied standard optimizations like loop unrolling, the game remained too slow. The challenge was that the hardware lacked modern features like blitters or texture mappers, requiring programmers to manually move data byte by byte to draw images onto the screen.
The author, after studying the code, realized that the bottleneck was the routine responsible for copying background tiles to the screen buffer. The 6809 processor used in the machine offered specific, powerful addressing modes, but the cycle counts for the naive copying routine remained high. By further unrolling the loops and manually calculating offsets for memory access, they achieved a 60 percent speed increase. However, this was still insufficient to meet the performance requirements of the game, leading the author to seek even more unconventional, "crazy" solutions.
After consulting a 6809 microprocessor manual, the author discovered that the processor's stack instructions could be repurposed for rapid data movement. By commandeering both the hardware stack registers, S and U, the author developed a method to pull data from a tile and push it to the screen buffer using every available register—including the DP register—to move blocks of data in a single burst. This approach was remarkably efficient, but it introduced a side effect: the process drew the tiles upside down and with their halves swapped.
To overcome this, the pair realized that the mangling process was actually an involution. By pre-processing the tiles to reverse their internal structure before the game ran, the act of drawing the corrupted data would perfectly restore the images once they reached the screen. This solution provided a 70 percent speed improvement, effectively solving the performance issues. However, a final hurdle emerged when they implemented hardware-clocked interrupts for sound. Because the routine relied on commandeering the stack pointer, an interrupt occurring during a tile draw would overwrite the memory where the reference tiles were stored.
They addressed this final technical crisis by cleverly swapping the stack pointers, ensuring that any corruption caused by an interrupt would target the screen buffer rather than the original tile data. To prevent this temporary screen glitch from being visible to the player, they altered the drawing order to proceed from high memory to low memory. This ensured that any accidental corruption would be overwritten by the next tile being drawn before the buffer was sent to the display.
Looking back, the author highlights this experience as a testament to the ingenuity required during the early era of computing. Faced with severe hardware limitations, programmers had to constantly experiment, fail, and iterate to find elegant, if sometimes unorthodox, solutions. The story serves as a reminder of the joy found in such technical struggles and an encouragement for others to document their own experiences before these specialized, historic insights are lost to time.
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 的精神——对效率的不懈追求,以及对代码如何与硅片发生交互的深刻理解——在图形与高性能计算等专业领域里仍然存在。参与者普遍认同:即便现代工具与编译器已经让许多具体的汇编技巧不再重要,掌握硬件这一核心挑战依然关键,只是这种稀缺性正在逐渐加剧。 • Developing early VGA hardware involved intense, high-stakes competition where clever, improvised solutions—such as implementing a minimal "one-deep" FIFO—could outperform complex, expensive engineering efforts from rivals.
• Advanced optimization often relies on finding "jargon-free" ways to manipulate hardware, exemplified by early MS-DOS programmers who replaced slow string-matching loops with bit-masking tables and CPU-native instructions like XLAT to achieve massive performance gains.
• The necessity of working within extremely constrained environments, such as AWS CloudFront functions with limited compute budgets, still drives modern developers to employ fundamental computer science techniques like binary search over bit-represented data to bypass platform limitations.
• Historical coding environments, while lacking the abstraction layers and package managers of today, fostered a deep, intuitive connection to hardware, leading to "magical" results that remain foundational to modern software concepts.
• Graphics programming continues to offer opportunities for low-level experimentation, as developers porting engines to consoles or optimizing for weak hardware often find themselves working directly against graphics memory, mirroring the "close to the metal" experience of the 8-bit era.
• Clever tricks like using the 6502 stack pointer as a tool to manipulate object counters on the Atari 2600 demonstrate that performance breakthroughs often arise from bending, rather than breaking, the intended use of a processor's architecture.
• The scarcity of information before the internet forced programmers into isolated "islands" of knowledge, relying on word-of-mouth, regional computer clubs, and hobbyist magazines to share discoveries, creating a unique culture of oral tradition and intense collaboration.
• Modern compiler optimizations and hardware complexity have made many 8-bit-era assembly tricks obsolete, yet the underlying philosophy of "ruling the hardware"—understanding memory layouts, aliasing, and instruction behavior—remains relevant for performance-critical systems.
• The shift from "orality" and isolated discovery to the modern era of abundant, easily accessible content has inverted the challenge from finding information to sifting through "slop" to extract high-value insights.
• Working on niche, low-level projects offers a distinct sense of adventure and discovery, providing a modern link to the legendary "Real Programmers" who manipulated bits directly to achieve the impossible.
The discussion reflects a deep sense of nostalgia for the era of bare-metal programming, where limited hardware forced developers into a state of creative desperation. These constraints acted as a catalyst for innovation, leading to architectural hacks that exploited hardware in ways never intended by manufacturers. While modern development is heavily abstracted, the conversation suggests that the spirit of these "hacks"—the relentless pursuit of efficiency and the intimate understanding of how code interacts with silicon—persists in specialized domains like graphics and high-performance computing. Participants generally agree that while modern tools and compilers have rendered many specific assembly tricks irrelevant, the core challenge of mastering hardware remains a vital, albeit increasingly rare, skill.