如何在没有 BIOS 的情况下在屏幕上绘制像素?

How to draw pixel on screen without BIOS?

我正在写 OS 并且想要 GUI。我找不到在屏幕上绘制像素的好教程。

我想要一些我可以构建的汇编 + C 示例和 运行 在像 BOCHS 或 v86

这样的模拟器上

基本思路是:

1) 引导加载程序使用固件(BIOS 上的 VBE、UEFI 上的 GOP 或 UGA)设置显示器、显卡和 OS 支持的图形模式;在这样做的同时,它从可以传递给 OS 的固件中获取有关帧缓冲区的相关信息(帧缓冲区的物理地址、水平和垂直分辨率、像素格式、水平线之间的字节数);这样 OS 可以在 "early initialisation" 期间(在本地视频驱动程序启动之前)使用此信息,并且如果没有合适的本地视频驱动程序,可以继续使用它(作为一种 "limp mode")视频驱动程序。

2) OS 使用这些信息来确定如何写入帧缓冲区。这可能是像 physical_address = base_address + y * bytes_between_lines + x * bytes_per_pixel 这样的计算(其中 bytes_per_pixel 由像素格式确定)。

"early initialisation" 的注释:

  • 出于性能原因,最好在 RAM 缓冲区中绘制所有内容,然后将数据从 RAM 缓冲区复制 ("blit") 到帧缓冲区。
  • 出于性能原因,将数据从 RAM 中的缓冲区复制 ("blit") 到帧缓冲区的代码 can/should 使用了一些技巧来避免复制自上次以来未更改的数据时间
  • 为了支持许多不同的像素格式,可以为 RAM 中的缓冲区使用 "standard" 像素格式(例如,可能是“8 位红色、8 位绿色、8 位蓝色、8-位填充”)并将其转换为视频卡恰好需要的任何像素格式(例如,可能是“5 位蓝色,6 位绿色,5 位红色,无填充”),同时将数据从 RAM 中的缓冲区复制到帧缓冲区。这使您可以使用单一版本的所有功能来绘制事物(字符、线条、矩形、图标等),而不是使用多个不同版本的许多不同功能(每种可能的像素格式一个)。

"middle initialisation" 的注释:

  • 最终 OS 将尝试为所有不同的设备查找并启动合适的设备驱动程序。这包括尝试为视频找到合适的驱动程序 card/s(例如,支持垂直同步、GPU、GPGPU 等)。
  • 您将需要设计一个视频驱动程序接口,本地视频驱动程序可以使用它(理想情况下)支持现代功能(例如,可能是全 3D 图形和着色器)。
  • 当没有本机视频驱动程序时,OS can/should 启动一个 "generic frame buffer" 驱动程序,该驱动程序实现相同的视频驱动程序接口(旨在支持硬件加速)一切都在软件中,没有硬件加速的好处。
  • 当视频 driver/s 启动时,OS 需要某种 "hand off" 帧缓冲区的所有权从较早的引导代码传递给视频驱动程序。在这 "hand off" 之后,早期的引导代码(旨在直接将内容绘制到帧缓冲区)不应接触帧缓冲区,并且应该要求视频驱动程序完成 "convert pixel data and copy to frame buffer" 工作。

"after initialisation" 的注释:

  • 对于传统的“2D GUI”;通常,对于 background/desktop,您有一个缓冲区(或 "canvas" 或 "texture" 或其他),再加上每个 window 或对话框的更多 buffers/canvases,甚至可能更多buffers/canvases 用于较小的东西(例如鼠标指针、下拉菜单、"widgets" 等);这样应用程序就可以修改它们的 buffer/canvas(但出于安全原因,不能直接或间接访问任何其他 buffer/canvas)。然后 GUI 告诉视频驱动程序这些 buffers/canvases 中的每一个应该被绘制在哪里;和视频驱动程序(如果它是本机视频驱动程序,则使用硬件加速)将这些部分组合在一起("composes")以获取整个帧的像素数据,然后进行像素格式转换(希望使用 GPU)以获取原始像素数据 display/to 发送到监视器。这意味着当有本地视频驱动程序时,各种操作(在屏幕上移动 windows,在 windows 之间移动 "alt tabbing",移动鼠标等)变得非常快,因为 CPU什么都不做,视频卡本身正在做所有的工作。

  • 理想情况下,应用程序可以通过某种方式(例如 OpenGL)请求视频驱动程序在应用程序 buffer/canvas 中绘制内容;这样更多的工作可以由视频卡完成(而不是由 CPU 完成)。这对于 3D 游戏尤其重要,但没有理由说明普通 2D 应用程序不能从对 2D 图形使用相同的方法中受益。

请注意,大多数初学者都做错了(没有设计良好的本机视频驱动程序接口),因此永远不会有任何本机视频驱动程序,因为他们的所有软件无论如何都不能使用本机视频驱动程序。这些人可能会试图说服您,这不值得麻烦(因为根据他们的经验,本地视频驱动程序永远不会存在)。事实上,大多数本地视频驱动程序都非常难写,但其中一些(用于虚拟机)并不难写;你的目标应该是最终允许其他人编写驱动程序(通过设计合适的接口和提供足够的文档)而不是自己编写所有驱动程序。

最佳答案解释得很好。您确实要求提供一些示例代码,所以这是我的 GitHub 中的代码片段,随后将进行详细解释。

1. bios_setup:
2.  mov ah, 00h     ; tell the bios we'll be in graphics mode
3.  mov al, 13h
4.  int 10h         ; call the BIOS

5.  mov ah, 0Ch     ; set video mode
6.  mov bh, 0       ; set output vga
7.  mov al, 0       ; set initial color
8.  mov cx, 0       ; x = 0
9.  mov dx, 0       ; y = 0

10. int 10h         ; BIOS interrupt

第 2 行是乐趣的开始。首先,我们将值 0 移动到 ah 寄存器中。在第 3 行,我们将 13 hex 移入 al - 现在我们已准备好调用 BIOS。 第 4 行使用中断向量 10 hex 调用 bios。 BIOS 现在签入 ah 和 al。

AH:
 - tells BIOS to set video mode
AL:
 - tells BIOS to enter write string mode.

既然我们在第 4 行调用了中断,我们就可以将新值移动到一些寄存器中了。 在第 5 行,我们将 0C hex 放入 ah 寄存器。 这告诉 BIOS 我们要写入一个图形像素。 在第 6 行,我们将 0 放入 bh 寄存器,它告诉 BIOS 我们将使用 CGA、EGA、MCGA 或 VGA 适配器进行输出。所以基本上输出模式0。 接下来我们要做的就是设置颜色。所以让我们从 0 开始,它是黑色的。 这一切都很好,但是我们实际上想把这个黑色像素画到哪里呢? 这就是第 8-9 行的用武之地,寄存器 cx 和 dx 分别存储要绘制的像素的 x、y 坐标。 一旦它们被设置,我们用中断 10 hex 调用 BIOS。以及绘制的像素。

在阅读了 Brendan 详尽而翔实的回答后,这段代码将使 更有意义。在调用之前,某些值必须存在于某些寄存器中 BIOS 只是因为这些是相应中断所在的寄存器 会检查。其他一切都非常简单。如果你想要另一个 颜色,只需更改 al 中的值。你想把你的像素转移到别的地方吗? 弄乱 cx 和 dx 中的 x 和 y 值。再一次,这不是很 对于图形密集型程序非常有效,因为它非常慢。 然而,出于教育目的,它胜过编写自己的图形驱动程序 ;)

你仍然可以通过在 RAM 的缓冲区中绘制所有内容来提高效率 像 Brendan 所说的那样在屏幕上闪烁,但我宁愿保持简单 我的例子。

查看 my GitHub 上的完整免费示例。我还包含了一个 README 和一个 Makefile,但它们是 Linux 独有的。如果您在 Windows 上 运行,一些谷歌搜索将产生将 OS 组装到可启动软盘所需的任何信息,几乎任何虚拟机主机都可以。另外,如果有任何不清楚的地方,请随时问我。干杯!

Ps:我没有写工具,只是 NASM 中的一个小脚本,用于组装到软盘和 运行 作为内核(在 VM 中如果你愿意的话)