移植 MS-DOS 图形应用程序

作者:Jawed Karim

我最初开始在 MS-DOS 下使用 VGA 编程,使用的是流行的 DJGPP C 编译器。近年来,这款保护模式 32 位编译器,基本上是 gcc 的 MS-DOS 移植版,已经确立了自己在 MS-DOS 游戏程序员社区中首选编译器之一的地位。DJGPP 实际上是 idsoftware 游戏 Quake 的 MS-DOS 编译器首选。对于 Quake 的 Linux 控制台移植版,使用了 Linux Super VGA 库 SVGALIB。

当我第一次决定将我自己的 3D 模型查看器 jaw3d 从 MS-DOS 移植到 Linux 时,使用相同的方法似乎是合乎逻辑的。SVGALIB 非常直观,让我可以轻松地维护和进一步开发我的 3D 模型查看器,使其同时支持这两个平台。

我发现,对于两个平台使用一套源代码的最简单方法是在需要不同代码的地方使用预处理器指令。由于我已经为 MS-DOS 版本编写和使用了 DJGPP 的底层 VGA 和鼠标指令,我只是在每个实例中添加了等效的 SVGALIB Linux 代码,并使用预处理器指令 #ifdef 分隔了 MS-DOS 和 Linux 代码。以下代码片段代表了实现此目的的多种方法之一

#ifdef __DJGPP__
...
...
#endif
#ifndef __DJGPP__
...
...
#endif

__DJGPP__ 由 DJGPP 编译器自动定义,而 Linux 下的 gcc 则未定义。

在 Linux 下使用 SVGALIB 的另一个优点是,SVGALIB 也有 DJGPP 版本。让我们尽量不要在此处感到困惑:SVGALIB 是一个图形库,它为用户完成一些幕后底层的 VGA 和鼠标工作。尽管 SVGALIB 最初是为 Linux 开发的,但最终有人发布了一个可以在 MS-DOS 下与 DJGPP 一起工作的版本。为什么不为 MS-DOS 和 Linux 都使用 SVGALIB 呢?这将使我们能够为两个平台拥有 100% 相同的代码。

但是,我不推荐这种方法,原因有二。首先,当我比较我的 3D 引擎在两个平台之间的速度时,我注意到在 MS-DOS 下使用带有 DJGPP 的 SVGALIB 时,图形性能与 Linux 下的 SVGALIB 相比显得迟缓。其次,MS-DOS 可执行文件不必要地变得很大,因为它必须与 SVGALIB 库静态链接。在 Linux 下使用 SVGALIB 似乎没有任何问题。由于 Linux 下使用了共享库,可执行文件在动态链接时仍然很小,并且 Linux 下的图形性能实际上略好于 MS-DOS 下。为了性能和可执行文件大小,我发现最好在 MS-DOS 下使用 DJGPP 的底层指令,而在 Linux 下使用 SVGALIB。这会产生影响,尤其是在像 3D 引擎这样的环境中,每一帧每秒都很重要。

从使用 DJGPP 移植的 SVGALIB 获得的优势是,您可以在 MS-DOS 下测试您的 SVGALIB Linux 代码,而无需重新启动。除了速度和可执行文件大小之外,两个版本的 SVGALIB 行为完全相同。

请注意,DJGPP 移植的 SVGALIB 仍处于 beta 测试阶段,但我只遇到一个小的并且很容易修复的问题。DJGPP 移植的 SVGALIB 附带的文件 vgakeybo.h 似乎与 Linux 下的文件 vgakeyboard.h 不同;因此,在使用了键盘代码的情况下,不可能进行交叉编译。当然,这两个文件应该是相同的,解决方案是将 Linux 版本的包含文件复制到 DOS 版本上。

三个编译器特定的代码方面是 VGA、鼠标输入和键盘输入。如果您已经完成了一个 MS-DOS 图形应用程序,您可能已经使用了大部分此类代码,并且可以快速添加等效的 SVGALIB 代码。另一方面,如果您没有任何以前的图形编程经验,您会发现清单 1 到 4 中显示的代码非常有用。

清单 1. 包含文件

清单 2. 初始化 VGA 模式

清单 3. 键盘代码

清单 4. 鼠标代码

将缓冲区复制到视频

在我的 3D 模型查看器 jaw3d 的情况下,首先将完整帧渲染到具有与屏幕相同尺寸的缓冲区上,然后一次性复制到视频内存,从而使我们能够连续显示频繁更新的屏幕而不会出现任何闪烁。这是通过以下方式完成的

        memcpy (video_buff, image_buffer,
                DIM_X * DIM_Y);
        /* video_buff was initialized above */
        dosmemput (image_buffer, DIM_X * DIM_Y,
                0xA0000);
        /* 0xA0000 is the video memory in VGA mode
         * 13h */
等待 VGA 回扫

通过等待 VGA 回扫,我们告诉程序等待直到显示器的电子束到达屏幕底部。由于在它跳回顶部之前有一个短暂的停顿,因此这是切换调色板而不会看到“彩虹色”的好时机。因此,在切换调色板之前,我们应该等待 VGA 回扫,如下所示

        while (!(inportb(0x3da) & 8));
        while ( (inportb(0x3da) & 8));
        vga_waitretrace();
设置 VGA 调色板

以下代码假定您有一个包含 768 个值的字符数组,表示 256 种颜色的 RGB 值。例如

char palette[768];
        where palette[0] = R value of color 0;
        where palette[1] = G value of color 0;
        where palette[2] = B value of color 0;
                ...
        for (i = 0; i < 256; i++)
        vga_setpalette(i, palette[i*3],
                palette[i*3+1], palette[i*3+2]);
        outportb(0x3C8,0);
        for (i = 0; i < 768; i ++)
                outportb(0x3C9, palette[i]);
编译

在程序中添加 SVGALIB 代码后,就可以编译了。只需使用 -lvga 选项编译即可链接 SVGALIB 库。此库已预装在大多数 Linux 系统上;因此,如果您在链接时遇到问题,则可能未安装该库,应下载它。

jaw3d 由作者编程,是 Nullsoft 公司 的产品。其他跨平台应用程序可以在 http://www.nullsoft.com/ 获取。

资源

Jawed Karim 是伊利诺伊大学厄巴纳-香槟分校的计算机科学专业新生,并在国家超级计算应用中心兼职工作。他的爱好包括编程和公路自行车赛。可以通过 jkarim@students.uiuc.edu 与他联系。

加载 Disqus 评论