Drawing In a Linear Framebuffer

来自osdev
Zhang3讨论 | 贡献2022年3月1日 (二) 07:53的版本 (创建页面,内容为“现在,你已经知道如何使用硬件VGA支持轻松地将文本写入屏幕,你可能想知道如何能够显示漂亮的图像、窗口、菜单、图标、漂亮的光标和按钮等。 本页介绍如何在线性帧缓冲区中显示图形,线性帧缓冲区是在内存中映射的一个简单数组,代表屏幕。 ==图形模式== {{Main|Getting VBE Mode Info}} {{Main|GOP}} VGA和VBE模式可以使用BIOS中断0x10选择(在实模式下)。…”)
(差异) ←上一版本 | 最后版本 (差异) | 下一版本→ (差异)
跳到导航 跳到搜索

现在,你已经知道如何使用硬件VGA支持轻松地将文本写入屏幕,你可能想知道如何能够显示漂亮的图像、窗口、菜单、图标、漂亮的光标和按钮等。 本页介绍如何在线性帧缓冲区中显示图形,线性帧缓冲区是在内存中映射的一个简单数组,代表屏幕。

图形模式

正文: Getting VBE Mode Info
正文: GOP

VGA和VBE模式可以使用BIOS中断0x10选择(在实模式下)。 INT 0x10, VESA Video ModesVESA 是 VGA 和 VBE/VESA的参考资料。

VGA仅限于16色640x480,而VBE(BIOS系统)和GOPUEFI机器,不包括一些非常早期的机器)可以达到显示器和视频卡支持的最大分辨率。

切换

设置视频模式最干净的方法是通过视频BIOS。 它可以通过常规的Int 0x10接口执行,也可以通过VBE3提供的(可选)包含模式接口执行。 正如你所猜测的,Int 0x10 需要一个16位的环境,所以你只能在实模式虚拟8086模式中使用它

实际上,可用选项按难度排序是:

  • 在进入保护模式之前,在早期阶段(在引导加载程序中)设置所需的模式。
    • UEFI系统上开发,并在启动时获得GOP帧缓冲区。
    • 让你的Bootloader帮你切换。
  • 切换回真实模式非实模式以设置正确的视频模式(Napalm 于rohitab.com有一个简洁的小功能供参考。)
  • 编写一个 VGA驱动程序,可以在几乎所有硬件上执行低分辨率模式
  • 如果存在,使用VBE3中的PMID
  • 设置一个V8086Monitor来执行模式切换代码
  • 运行软件代码翻译工具,从bios rmode代码中生成pmode代码。(SANiK正在进行
  • 为特定的图形卡编写驱动程序

定位视频内存

对于标准VGA视频模式,对于EGA/VGA视频模式,视频内存将位于地址0xA0000,对于CGA和文本模式,视频内存将位于地址0xB8000。 要找出哪一个,请查看下表:

00 text 40*25 16 color (mono)
01 text 40*25 16 color
02 text 80*25 16 color (mono)
03 text 80*25 16 color
04 CGA 320*200 4 color
05 CGA 320*200 4 color (m)
06 CGA 640*200 2 color
07 MDA monochrome text 80*25
08 PCjr
09 PCjr
0A PCjr
0B reserved
0C reserved
0D EGA 320*200 16 color
0E EGA 640*200 16 color
0F EGA 640*350 mono
10 EGA 640*350 16 color
11 VGA 640*480 mono
12 VGA 640*480 16 color
13 VGA 320*200 256 color

对于VESA模式,帧缓冲区地址存储在mode info block中。 这是线性帧缓冲区的物理地址(它不是16位远指针,而是32位线性指针): 如果你使用分页,你必须把它映射到某个地方才能使用它。 帧缓冲区的长度(字节)是pitch*height。

对于GOP,帧缓冲区地址位于EFI_GRAPHICS_PROTOCOL结构中,GOP->Mode->FrameBufferBase。 GOP支持电传字符模式,只支持面向图形像素的模式。

绘制像素(Plotting Pixels)

定位

如果你想在屏幕中间绘制一个红色像素。 你首先要知道的是屏幕中间的位置。 在320x200x8(mode 13)中,这将是偏移100x320+160=32160。 一般来说,你的屏幕可以通过以下方式描述:

宽度 width 水平线上有多少像素
高度 height 有多少像素的水平线
间距 pitch 为了降低一个像素,你应该跳过多少字节的VRAM
深度 depth 你有多少种颜色
“像素宽度pixelwidth” 你应该跳过多少字节的VRAM才能向右移动一个像素。

“间距”和“宽度”乍一看可能显得多余,但事实并非如此。 当你使用更高的分辨率时,比如每行8K字节,而你的屏幕实际上是1500像素宽(每像素32位),这种情况并不罕见。 好消息是,它允许平滑的水平滚动(主要用于2D游戏:P)

间距和像素宽度通常由VESA模式信息发布。一旦你知道了它们,你就可以计算出你绘制像素的位置:

unsigned char *pixel = vram + y*pitch + x*pixelwidth;

颜色

要知道的第二件事是你应该为“红色”(或其它颜色)写什么值。 这同样取决于你的屏幕设置。 在EGA模式下,你有一个固定的调色板,具有暗红色(颜色4)和浅红色(颜色12)。 然而,EGA要求你在不同的像素平面上绘制每一位,因此,如果你 实在希望支持这种模式,请参阅EGA编程教程。 在传统的320x200x8 VGA模式下,你的4色和12色与EGA中的颜色相同,因此你可以使用如下方式

*pixel = 4;

然而,在VGA中,调色板是可重新编程的(正如你可以在FreeVGA文档中了解到的),因此几乎任何介于0..255之间的值都可能为“红色”。

所以VGA的完整“putpixel”功能如下

/* 320x200 VGA的示例 */
void putpixel(int pos_x, int pos_y, unsigned char VGA_COLOR)
{
    unsigned char* location = (unsigned char*)0xA0000 + 320 * pos_y + pos_x;
    *location = VGA_COLOR;
}

最后,在VESA和GOP模式下,通常使用truecolor或hicolor,在这两种模式下,必须为每个像素提供独立的红色、绿色和蓝色值。 modeinfo将(再次)指导你如何以像素位组织RGB组件。 例如,对于15位模式,你将使用0xRRRRRGGGBBBBB,这意味着#ff0000红色在那里0x7800,而#808080灰色在那里0x4210 (拿起铅笔,画出比特,自己看)

static void putpixel(unsigned char* screen, int x,int y, int color) {
    unsigned where = x*pixelwidth + y*pitch;
    screen[where] = color & 255;              // BLUE
    screen[where + 1] = (color >> 8) & 255;   // GREEN
    screen[where + 2] = (color >> 16) & 255;  // RED
}

优化

从这里可以很容易地编写其他绘图函数,从调用putpixel... 不要这样做。绘制一个填充矩形意味着你可以访问连续的像素,然后按“pitch-rect_width”前进以填充下一行。 如果你执行"for(y=100;y<200;y++) for(x=100;x<200;x++) putpixel (screen,x,y,RED);"循环时,你将重新计算大约10000次“where”,并进行过多的函数调用。 即使编译器将y*pitch优化为加法和移位,而不是乘法,在可能的情况下浪费CPU时间也是愚蠢的

static void fillrect(unsigned char *vram, unsigned char r, unsigned char g, unsigned   char b, unsigned char w, unsigned char h) {
    unsigned char *where = vram;
    int i, j;

    for (i = 0; i < w; i++) {
        for (j = 0; j < h; j++) {
            //putpixel(vram, 64 + j, 64 + i, (r << 16) + (g << 8) + b);
            where[j*pixelwidth] = r;
            where[j*pixelwidth + 1] = g;
            where[j*pixelwidth + 2] = b;
        }
        where+=pitch;
    }
}

这应该足以让你开始编写(或在谷歌上搜索)一个像样的视频库。

绘制文本

正文: VGA Fonts

一旦进入图形模式,你就不再有BIOS或硬件来为你绘制字体。 基本思想是为每个字符提供字体数据,并使用它来绘制(或留白)像素。 存储这些字体的方法有很多,这取决于它们是否有多种颜色、alpha通道等。 然而,你基本上拥有的是:

// 保存你所需要的每一个char
font_char* font_data[CHARS];
 
// 在给定font_data的情况下渲染其中一个character
void draw_char(screen, where, font_char*);

void draw_string(screen, where, char* input) {
    while(*input) {
        draw_char(screen,where,font_data[input]);
        where += char_width;
        input++;
    }
}
 
void draw_char(screen, where, font_char*) {
    for (l = 0; l < 8; l++) {
        for (i = 8; i > 0; i--) {
            j++;
            if ((font_char[l] & (1 << i))) {
                c = c1;
                put_pixel(j, h, c);
            }
        }
        h++;
        j = x;
    }
}

绘制图标

正文: Loading Icons

对于GUI,你可能需要显示图标。就如下这么简单:

void draw_icon(x, y, w, h, pixels) {
    for (l = j = 0; l < h; l++) {
        for (i = 0; i < w; i++, j++) {
            put_pixel(x + i, y + l, pixels[j]);
        }
    }
}

困难在于,图标不是以宽度、高度和像素阵列的形式存储在磁盘上。 首先,你必须解码图像文件才能获得这些信息。

另见

外部链接