LibGGI:又一个图形 API
我们不喜欢又出现一个图形库的想法,但是当我们在 GGI 项目启动时检查了可用的解决方案后,我们发现没有任何一个能满足我们所有的需求
可移植性
简洁性
透明加速支持
多头显示支持
可扩展性
小尺寸
首先,可移植性是我们对抗商业软件市场的唯一武器。如果我们足够可移植,可以在任何平台(包括主流市场)上运行,我们就有可能获得那些优秀的程序,因为移植它们不成问题。
X 窗口系统是程序能达到的最可移植的程度,并且 X 应用程序通常相当可移植。然而,使用 X 通常是过度设计,并会导致相当大的开销。此外,编写 X 程序相当困难(取决于您使用的工具包),并且对于大多数非 UNIX 大师来说显得非常陌生。
然而,X 是 UNIX 世界中最重要的平台,为了称自己为可移植的,我们需要支持 X。LibGGI 使用动态加载目标驱动程序的系统,使其能够在任何具有图形显示能力的设备上运行。显示目标是某种类型的服务器软件、类似 KGI 的设备、直接访问图形硬件的东西、打印机、微内核操作系统的系统服务或其他东西,这都没有区别。表 1 显示了 LibGGI 程序可以运行的一些可用目标系统。
图形服务器系统
X 窗口系统:AIX、IRIX、Solaris、Linux/x86/Alpha
Microsoft Windows(非常早期版本)
面向设备的系统
KGI:Linux/x86;计划中:Solaris/x86、Linux/Alpha
Solaris /dev/fb
VESA/DOS
SVGAlib、GLIDE、SUID-KGI:Linux
LibGGI 检测当前硬件上最理想的目标,并自动使用它。这可以被轻松地覆盖以强制不同的行为。
在同一平台内,兼容性在二进制级别上保持。也就是说,为 Linux x86 编译的 LibGGI 应用程序可以在 KGI 全屏、X 窗口、使用 SVGAlib 或 GLIDE 的情况下无需修改即可运行。它甚至可以通过 LibAA 或任何可用的方式在文本模式屏幕上运行。
跨平台兼容性需要重新编译,但如果周围的代码没有大量使用操作系统特定的东西,这应该是无痛的。
所以,移植应用程序很容易。但是移植 LibGGI 本身呢?我们已经尽力保持 LibGGI 尽可能地可移植。大多数 GGI 代码在 **gcc -pedantic** 模式下编译时不会出现警告。我们也尽力将操作系统特定内容的使用保持在最低限度。
LibGGI 应该可以在任何听说过 POSIX 的系统上轻松构建。即使 libdl 也不是严格必需的了,以便允许不识别动态库的系统。
LibGGI 设计中的另一个重点是简洁性。如果程序员只想编写一个小的图形实用程序,他可能会被 X 的复杂性吓到。为了让您了解如何使用 LibGGI 编程,让我们看一下 清单 1 中显示的小示例程序。
它没有展示良好的风格,但旨在易于阅读。与任何库一样,您必须包含其头文件。这些头文件位于一个子目录中。由于我们有多个库,我们决定分配一个完整的子目录会减少混乱。
使用 LibGGI 时,您要做的第一件事是调用 ggiInit。这会初始化 LibGGI 内部数据结构并设置一切。接下来,您调用 ggiOpen。此调用返回一个 ggi_visual_t,它是一种不透明类型,类似于 X 称之为“可绘制对象”的东西。可以将其视为您在其上绘制的显示器的抽象句柄。请注意,根据处理多个屏幕的复杂应用程序的要求,每个程序可以有多个显示器。
您将需要在视觉对象上设置图形模式。模式由可见区域的大小或分辨率(visx、visy)以及视口可以在其上平移的虚拟区域的大小(virtx、virty)组成。此外,您需要指定您请求的显示类型;例如,GT_24BIT 真彩色视觉对象。请注意,可以使用调用来请求其他选项,以及自动选择值的功能。为了增强可移植性,强烈建议这样做。
现在我们准备开始绘图了。LibGGI 使用 GC(图形上下文)来表示绘图系统的当前状态。我们考虑过无状态方法,但这将意味着
一些函数需要大量参数
对于习惯 GC 概念的程序员来说,看起来非常笨拙
忽略了实际的加速硬件通常具有 GC
现在我们使用 ggiSetGCForeground 和 ggiDrawPixel 以不同的颜色绘制一些点。作为替代方案,我们使用 ggiPutPixel 绘制下一组像素。更高级别的函数也可用,但仅限于有限的程度。正如您可以从示例程序中看到的那样,我们支持各种类型的线条和框(是的,如果底层目标支持,这些是加速的),但仅此而已。
不要在这里感到失望。有一个名为 LibGGI2D 的更高级别的库提供更复杂的功能。LibGGI 被设计为一个基本的“基础”库,在其之上可以构建专门的库以满足更复杂的需求,例如 3-D 和动画。
当我们完成绘图后,我们使用 ggiEventPoll 等待键盘或鼠标事件。ggiEventPoll 确定给定类型的一个或多个事件是否存在,并且如果指向 timeval 结构的指针为 NULL(在我们的简单示例中),则最终会为此事件阻塞指定的时间或无限期地阻塞。
然后我们使用一个便捷函数来获取按键。请注意,如果轮询由于鼠标活动而终止,这将再次阻塞。在大多数情况下,您将希望使用 LibGGI 的事件系统从任何可连接到计算机系统的设备获取输入。对于事件分类和配置,可以使用一个名为 LibGII 的辅助库,它为您提供了一种灵活而简单的方式,将设备输入映射到程序操作。在 ggiGetc 返回后,我们使用 ggiClose 关闭视觉对象,然后使用 ggiExit 关闭整个 LibGGI 库。请注意,您可以在 ggiExit 之前重新打开另一个视觉对象,例如,这可以用于将程序从一个目标转移到另一个目标。在 ggiExit 之后,对 LibGGI 函数的所有其他调用都是未定义的。您需要首先再次调用 ggiInit。现在您已经对 LibGGI 程序的外观有了初步的了解。
许多应用程序,尤其是那些从 DOS 和其他存在相对直接的硬件访问路径的系统移植过来的应用程序,将希望直接访问图形 RAM。虽然绑定到特定卡/模式的布局对于可移植性来说不是一个好主意,但它是获得额外速度的好方法。LibGGI 通过导出 DirectBuffer 结构来解决这个难题,该结构描述了当前活动的视频缓冲区的所有细节。应用程序可以决定是使用它还是回退到标准的 LibGGI 调用。
LibGGI 应用程序可以同时服务于多个视觉对象,从而允许多头显示应用程序(如 CAD 或游戏屏幕)在多个显示器上分割。为了方便起见,我们有“内存视觉对象”,可以先绘制一个“不可见”的区域,然后将其位图传输到屏幕(交叉位图传输)。简单的色彩空间管理(如伽玛设置)以及对双/三缓冲和等待垂直回扫或甚至 CRT 光束的特定位置(在硬件允许的情况下)的支持也是可用的。
LibGGI 大概止步于 DrawBox 的级别,对于许多应用程序来说,这不是一个理想的环境,并且透明加速是有限的。LibGGI 的设计目的是保持小巧,以便在嵌入式系统等受限条件下良好工作,并且不会为不需要高级功能的应用程序浪费空间。
我们扩展了 LibGGI,以便可以在其“之上”实现更复杂的 API。到目前为止,我们已经运行了 LibGGI2D、Mesa-GGI 和一个微小的窗口库 LibGWT。轻量级 3-D 库、字体和动画支持正在开发中。这些库被实现为 LibGGI 扩展。作为扩展,与仅仅“使用” LibGGI 相比,有几个好处;其中之一是,您继承了关于库加载和目标支持的完整功能。因此,扩展库也带来了它们自己的 API 驱动程序集,这些驱动程序可用于实现透明加速。LibGGI 确保基本服务,因此所有扩展库都将在所有 LibGGI 目标上运行,但加速级别将根据扩展的驱动程序库的可用性而有所不同。
LibGGI 的核心是一个技巧,它可以帮助 LibGGI 在加速方面保持可移植性和智能性——创造性地使用动态加载的库。LibGGI 函数可以通过加载合适的库来覆盖。LibGGI 知道两种不同类型的此类库
显示模块描述了连接到给定类型的后端(如 X、KGI、SVGAlib 等)的方式。它们在 ggiOpen 时加载。
驱动程序模块通常在模式设置时加载,每个模块描述了用于在当前目标上绘图的给定 API。这些 API 通常由后端选择,后端会查询一组“建议字符串”,这些字符串映射到这些 API。参见图 1。
通用存根(回退模拟)
线性 8 位帧缓冲区
通用 KGI ioctl
供应商 ABC KGI 私有 Ioctl
供应商 ABC MMIO 直接访问

图 1. 透明加速和多 API 的工作原理
您可能会对“set of”(集合)这个术语感到惊讶。通常,有多个 API 可用于在给定目标上绘图。让我针对 KGI 目标进一步解释这一点,KGI 目标最广泛地使用了此功能。表 2 是通过 KGI 访问的虚构 ABC 显卡的建议字符串集。管理该卡的 KGI 模块将告诉 LibGGI 首先加载一个“存根”库,该库用于在函数未得到本机支持时进行模拟。此存根库包含回退功能,例如从多条水平线组成一个填充框。然后,LibGGI 加载一个库,该库访问 KGI 导出的线性 8 位宽帧缓冲区。该库将包含诸如 DrawPixel 之类的原语,并覆盖存根库。然后,LibGGI 将加载 KGI 的通用 ioctl 方法以访问加速功能。该库将处理通常加速的函数。下一个建议字符串添加了一些不太常见但在 ABC 中存在的命令,这些命令通过 ioctl 的私有区域访问。最后加载的库访问导出 MMIO 区域中的 ABC 寄存器。所有库都按优先级升序加载。后面的库如果能做得更好,则会覆盖前面库的功能。请注意,这不是一个静态过程——如果需要,它仍然可以在运行时更改。
当涉及到图形性能时,许多人担心 LibGGI 会很慢,因为它扩展库提供的抽象级别相对较高。实际上,如果我们想充分利用所有显卡的最大性能,这种高抽象级别是必要的。一些高端显卡确实具有真正的高级内部 API。让应用程序使用低级 API 会使显卡的这一部分未被使用。
另一方面,在某些情况下,很难决定使用哪个级别的 API。考虑一个 3-D 游戏。您通常可以根据您对场景的了解进行一些巧妙的优化。对于低端显卡,您可能能够更快地自己计算到光栅化级别。对于高端显卡,您最好直接使用 OpenGL,因为所有计算都转到显卡,显卡执行计算的速度比主机 CPU 快。
这是一个难题,实际上唯一好的解决方案是同时实现两者并在运行时选择一种方法。
另一个始终存在的问题是调用开销。使用内联代码比任何类型的库都快。然而,最大的相对收益/损失是在非常快速的小操作(如 DrawPixel)中实现的。这是我们选择实现 DirectBuffer 功能的主要原因。如果应用程序知道显卡使用的 DirectBuffer 格式,它可以使用自己的内联代码来绕过调用开销。
LibGGI 应该在所有可能的应用程序和显卡范围内表现良好,尽管专门的解决方案可能会表现略好。
如果您正在考虑将 LibGGI 用作消费者或用于编程您自己的应用程序,您可能会对哪些程序已经可用感兴趣。
LibGGI 专为高速图形设计,因此游戏设计师是我们的主要客户。许多流行的游戏已被移植为使用 LibGGI。Descent 和 DOOM 是其中两个更知名的游戏。使用 LibGGI,我们在源代码发布后几周内在 Linux-Alpha 机器上成功运行了 Descent。
关于 GGI 项目的一个常见误解是我们试图取代 X。这是错误的。我们处于比窗口系统低得多的层级,并在 LibGGI 之上实现了一些流行的窗口系统。我们有自己的 X 服务器 (Xggi)、用于 VNC 网络桌面的查看器应用程序,柏林联盟正在 LibGGI 之上构建其服务器。这些服务器的存在以及 LibGGI 在其上显示的能力将我们带入了下一代互操作性。
另一大类应用程序处理文件查看。LibGGI 具有在控制台或 X 窗口中查看 JPG 文件的出色能力,而无需生成应用程序(如 mc)意识到它正在 X 或控制台上运行。
上述大多数程序都是从其他图形 API 移植过来的。所有移植者都告诉我,学习 LibGGI 很容易,并且在移植后,程序的外观得到了改善。
如果您对 LibGGI 感兴趣,您会想知道在哪里可以获取它(请参阅“资源”)。我们的项目主页提供了许多指针和相当多的资源。LibGGI 可以从 Sunsite 和 tsx 等几个主要的软件存档库中以发行版的形式获得,也可以从我们的网页及其镜像的每日 CVS 快照中获得,还可以通过 CVS 从我们的公共 CVS 镜像中获得。
还有几个预编译的二进制文件可用,这对于不想费心编译 LibGGI 的“纯用户”应该很有用。下次您编写图形应用程序时,请尝试一下 LibGGI。
