在 1990 年代初期的 PC 游戏开发中,Mode 13h(320×200,256 色)因其线性内存映射而广受欢迎 —— 每个像素对应显存中的一个字节,编程模型简单直观。然而,这种 "链式模式"(Chain-4)存在一个致命缺陷:256KB 显存中有 192KB 处于闲置状态,仅支持单页缓冲,无法实现硬件级双缓冲或平滑滚动。Michael Abrash 在 1991 年提出的 Mode X 通过关闭 Chain-4 位、切换至平面(Planar)组织方式,在兼容硬件上解锁了多页面缓冲与更高分辨率的可能,成为 DOS 时代软件光栅化的关键技术基础。
从链式到平面:内存组织的范式转换
标准 Mode 13h 的核心机制在于 Chain-4 位(Sequencer 寄存器 Index 4 的 Bit 3)。当该位为 1 时,VGA 硬件自动将地址的低两位用于选择四个位平面(Bit Plane)之一,每个地址仅映射到一个像素。这种设计牺牲了 75% 的显存空间,换取了编程便利性。
Mode X 的第一步是关闭 Chain-4,将 VGA 切换至 "非链式"(Unchained)状态。此时,每个字节地址同时映射到四个位平面上的四个相邻像素 ——Plane 0 存储像素 0、4、8...,Plane 1 存储像素 1、5、9...,以此类推。地址计算变为:
字节偏移 = (y × 行宽字节数) + (x / 4)
目标平面 = x & 3
其中行宽字节数为水平像素数除以 4。对于 320 像素宽度,每行占 80 字节。这种组织方式使得 320×200 分辨率下,单页仅需 16,000 字节,256KB 显存可容纳 16 个完整页面。
寄存器配置:初始化序列详解
进入 Mode X 的标准流程是先通过 BIOS 设置 Mode 13h,再修改特定 VGA 寄存器。以下是关键寄存器操作序列:
1. 关闭 Chain-4 模式 向 Sequencer 地址端口 0x3C4 写入索引 4,然后向数据端口 0x3C5 写入 0x06(Bit 3 清零)。这会解除地址与平面的自动绑定。
2. 关闭字模式与双字模式 CRTC(CRT Controller)寄存器 Index 0x17(Mode Control)写入 0xE3,关闭字模式;Index 0x14(Underline Location)写入 0x00,关闭双字模式。这两项确保显存访问以字节粒度进行。
3. 清屏与平面选择
通过 Sequencer Index 2(Write Plane Enable)写入 0x0F 启用全部四个平面,使用 memset 将 0xA000 段清零。此后绘制操作需根据目标像素的 X 坐标选择特定平面。
4. 扩展至 320×240 分辨率 Mode X 的经典配置是 320×240,这需要修改 CRT 时序寄存器:Miscellaneous Output Register(0x3C2)写入 0xE3 设置同步极性;CRTC Index 0x11 写入 0x2C 解除写保护;随后调整 Vertical Total(0x06)、Overflow(0x07)、Vertical Retrace Start/End(0x10/0x11)、Vertical Display Enable End(0x12)等寄存器,将垂直扫描线从 200 扩展至 240。此时像素宽高比为 1:1,圆形在屏幕上显示为完美正圆,而非椭圆。
硬件页面翻转:无撕裂渲染的关键
Mode X 的核心优势在于硬件级页面翻转(Page Flipping)。CRTC 寄存器 0x0C(Start Address High)和 0x0D(Start Address Low)共同决定显示器从显存的哪个偏移位置开始扫描。通过修改这两个寄存器,可以在垂直回扫期间切换可见页面,实现真正的双缓冲:
// 设置可见页面(page 为页号,从 0 开始)
void setVisiblePage(int page) {
unsigned offset = page * widthBytes * height;
outportb(CRTC_ADDR, 0x0C); // 高字节
outportb(CRTC_ADDR + 1, offset >> 8);
outportb(CRTC_ADDR, 0x0D); // 低字节
outportb(CRTC_ADDR + 1, offset & 0xFF);
}
活跃页面(Active Page)与可见页面(Visible Page)分离:CPU 在一个页面上执行绘制操作,同时显示器从另一个页面读取数据。绘制完成后,等待垂直同步信号(通过读取 Input Status Register 1 的 Bit 3 检测),然后原子性地更新 Start Address 寄存器完成翻页。这种方式彻底消除了画面撕裂(Tearing)和闪烁(Flickering),是 1993 年《毁灭战士》(Doom)等游戏实现流畅动画的底层机制。
像素绘制:平面选择与寻址
在非链式模式下,绘制单个像素需要显式选择目标平面。通过 Graphics Controller 的 Write Plane Enable 寄存器(Index 2,端口 0x3C4/0x3C5)实现:
void putPixel(int x, int y, unsigned char color) {
// 选择平面:1 << (x & 3)
outportb(0x3C4, 0x02);
outportb(0x3C5, 1 << (x & 3));
// 计算显存偏移并写入
unsigned offset = (widthBytes * y) + (x / 4) + activeStart;
VGA[offset] = color;
}
读取像素类似,但需通过 Read Plane Select 寄存器(Graphics Controller Index 4)选择源平面。这种设计使得单像素操作比 Mode 13h 慢(需要额外的 OUT 指令),但批量操作(如水平线填充、块复制)可通过同时启用多个平面实现四倍速写入,在填充纯色区域时显著优于链式模式。
工程实践建议
对于现代复古开发或模拟器实现,Mode X 仍具有参考价值:
- 显存布局预计算:在初始化阶段生成平面选择掩码表(0x01, 0x02, 0x04, 0x08)和地址偏移表,避免运行时位运算开销。
- 批量渲染优化:将精灵绘制分解为 "平面批量写入" 阶段,减少 OUT 指令调用次数。单个 OUT 在 386/486 上通常需要 10-20 个时钟周期。
- VSync 等待策略:在翻页前轮询 Input Status Register 1(0x3DA)的 Bit 3(Vertical Retrace),确保在垂直回扫期间完成寄存器更新。
- 多分辨率适配:Mode X 的寄存器配置方法可推广至 360×480、320×400 等分辨率,通过调整 CRTC 时序参数实现,但需验证目标显示器的同步范围。
Mode X 代表了 1990 年代 PC 图形编程的极致优化 —— 在硬件限制内通过精确的寄存器操控榨取每一分性能。其平面内存组织与硬件页面翻转机制,为理解现代 GPU 的帧缓冲管理与交换链(Swap Chain)提供了历史参照。
参考来源
- Robert Schmidt, "Introduction to Mode X," PC Game Programmer's Encyclopedia, 1993. http://bespin.org/~qz/pc-gpe/modex.txt
- Michael Abrash, "Mode X: 256-color VGA Magic," Dr. Dobb's Journal, 1991-1992.
内容声明:本文无广告投放、无付费植入。
如有事实性问题,欢迎发送勘误至 i@hotdrydog.com。