在复古计算领域,Apple II 的 Hi-Res 图像格式(8192 字节)代表了 8 位时代的图形显示极限。然而,这个看似简单的 8KB 内存布局隐藏着复杂的工程挑战:特殊的屏幕空洞结构、有限的处理器资源,以及磁盘存储空间的宝贵性。本文将深入探讨如何为这一特定硬件平台设计高效的压缩算法,特别是 LZ4FH(LZ4 for Hi-Res)算法的 8 位寄存器优化策略。
Apple II Hi-Res 内存布局的独特性
Apple II 的 Hi-Res 显示模式采用了一种非线性的内存映射方式。整个 8192 字节的显存被划分为 128 字节的块,每个块对应屏幕上的 3 行像素。但这里有一个关键设计:每 128 字节块中,只有前 120 字节包含实际的像素数据,剩余的 8 字节是 "屏幕空洞"(screen holes)。
这种设计源于硬件限制 ——Apple II 的视频生成电路需要特定的时序间隔。从软件角度看,这些空洞在屏幕上不可见,但它们占据了内存地址空间。正如 Stack Exchange 上的讨论所指出的,这些空洞 "被用于固件或扩展卡,不应被随意修改"。
对于压缩算法而言,这意味着我们需要处理两种不同类型的数据:
- 可见像素数据(120 字节 / 块)
- 屏幕空洞数据(8 字节 / 块)
传统 RLE 压缩的局限性
在 Apple II 的黄金时代,大多数图像压缩工具采用运行长度编码(RLE)。RLE 算法简单直接:将连续的相同字节替换为 "计数 + 值" 对。对于 8 位系统,RLE 具有明显优势:
- 实现简单,代码体积小
- 编码和解码速度快
- 内存占用少
然而,RLE 在处理复杂图像时效率低下。Apple II Hi-Res 图像虽然只有 560×192 分辨率,但游戏画面和艺术图像往往包含丰富的细节和渐变,这些都不适合 RLE 压缩。测试数据显示,RLE 对某些图像的压缩率可能低至 10-20%,远不能满足实际需求。
LZ4FH:为 8 位寄存器优化的 LZ4 变体
LZ4FH 算法是 LZ4 压缩格式的专门修改版,针对 Apple II 的 8 位架构进行了深度优化。核心思想是创建一个 "非对称编解码器":压缩过程可以在现代硬件上缓慢进行,但解压必须在 Apple II 上极快执行。
8 位寄存器优化策略
LZ4FH 的关键修改在于适应 8 位寄存器的限制。标准 LZ4 使用 16 位或 32 位的偏移量和长度字段,这在 8 位系统上处理效率低下。LZ4FH 的优化包括:
-
字节对齐的匹配操作:所有数据比较和复制操作都设计为 8 位友好,避免跨字节边界的复杂操作。
-
简化的控制流:减少条件分支,使用线性解码流程,这在 6502 处理器上特别重要,因为分支预测代价高昂。
-
内存访问模式优化:利用 Apple II 内存访问的特性,将频繁访问的数据保持在零页(zero page)内存中。
两种压缩模式的选择
fhpack 工具提供了两种压缩模式,类似于 LZ4 的 "快速" 和 "高压缩" 模式:
-
快速模式:使用贪婪解析算法,虽然不是特别快,但对于 8KB 数据来说可以接受。平均压缩时间在几秒内。
-
高压缩模式:使用最优解析算法,比快速模式慢 12 倍,但压缩率提高约 4%。对于图像库的批量处理,这种模式更有价值。
值得注意的是,压缩端的复杂算法不能在 6502 上实现。正如项目文档所述:"最优解析理论上可以在 128KB RAM 的机器上完成,但需要非常长的时间运行。" 这体现了非对称设计的合理性。
屏幕空洞处理的工程化参数
处理屏幕空洞是 Apple II 图像压缩特有的挑战。fhpack 提供了三种处理策略,每种都有其适用场景:
1. 保留模式(-h 标志)
参数:-h
效果:完全保留原始空洞数据
适用场景:需要精确数据还原的应用
压缩率:最低
2. 填零模式
效果:将所有空洞填充为零
适用场景:大多数图像,特别是空洞原本就为零的情况
压缩率:中等
3. 模式匹配填充
效果:用相邻数据的模式填充空洞
适用场景:空洞包含有意义但非必要的模式数据
压缩率:最高(在某些情况下)
实际测试显示,模式 2 和 3 的差异通常在 70-90 字节范围内。由于现代硬件上 fhpack 运行速度很快,工具默认会尝试两种方法并选择压缩率更高的结果。
性能参数与对比数据
基于约 70 个测试图像(主要来自游戏和早期贡献程序)的基准测试,我们可以得到以下关键性能数据:
压缩率对比
| 算法 | 总字节数 | 压缩率 |
|---|---|---|
| LZ4-HC | 248,473 | 37.4% |
| fhpack (LZ4FH) | 242,771 | 36.5% |
| LZW/II (ShrinkIt) | 232,201 | 34.9% |
解压速度参数
- 6502 处理器:约 5.6 FPS(消除所有开销的基准测试)
- 65816 处理器:约 12 FPS(使用批量数据复制指令)
磁盘加载时间对比
在 AppleWin 模拟器中使用 "真实" 磁盘访问速度:
- 未压缩图像:约 1.7 秒 / 图像(0.6 FPS)
- 压缩图像:约 1.4 秒 / 图像(0.7 FPS)
这意味着对于 5.25 英寸磁盘,加载压缩图像并解压通常比直接加载未压缩图像更快。
实际应用中的工程考量
内存布局约束
解压器需要两个参数:压缩数据的地址和输出缓冲区的地址。在当前实现中,输出缓冲区必须是 $2000 或 $4000(两个 Hi-Res 页面)。这些地址通过内存位置 $02FC 和 $02FE 传递。
文件格式兼容性
压缩图像使用 FOT($08)文件类型,auxtype 为 $8066(0x66 是 ASCII 'f')。这些文件可以在 CiderPress v4.0.1 及更高版本中查看。
代码体积优化
6502 版本的解压器代码非常紧凑,适合嵌入到各种应用程序中。65816 版本虽然更大,但利用了处理器的增强指令集,特别是数据移动指令,显著提高了性能。
技术局限性与替代方案
LZ4FH 的局限性
-
不可压缩数据的处理:对于完全随机的数据(如 test/nomatch),LZ4 无法压缩,但 fhpack 通过处理屏幕空洞仍能获得一定压缩率。
-
压缩端资源需求:高压缩模式需要现代硬件,无法在 Apple II 上运行。
替代算法:LZSS
LZSS(Lempel-Ziv-Storer-Szymanski)是另一个可行的选择,被 HardPressed 等工具使用。它将测试语料库压缩到 243,991 字节(36.7%),虽然通常不如 LZ4,但在某些情况下是可行的替代方案。
LZSS 的主要限制是最大匹配长度和偏移量较短,但对于 Hi-Res 图像来说这并不太重要。更大的问题是文字字节使用单独的标志位标识,而不是作为字节流处理,这对长文字串的性能有影响。
结论:复古计算的现代启示
Apple II Hi-Res 图像压缩的案例展示了如何将现代压缩算法适配到复古硬件上。LZ4FH 的成功关键在于:
-
非对称设计哲学:接受压缩和解压的不对称性,在现代硬件上完成繁重工作。
-
硬件特性利用:深度理解目标平台的架构限制(8 位寄存器、内存布局、指令集)。
-
领域特定优化:针对特定数据格式(屏幕空洞)设计专门的优化策略。
对于今天的开发者来说,这个案例的启示是:即使在资源受限的环境中,通过精心设计的算法和深入理解硬件特性,仍然可以实现令人印象深刻的性能提升。这种 "为特定平台深度优化" 的思维方式,在现代嵌入式系统和边缘计算中仍然具有重要价值。