模式号,想当然不得
咱们还没动手,笔者就把当年在这块地上摔的那一跤原样摆出来。这一跤不是因为粗心大意了,根子在当年那套做法的姿势上,所以值得用整节的篇幅重走一遍。
当年的 0x118
VBE 的世界里,一个图形模式对应的是一个 16 位的模式号。当年笔者头一遍写这段,流程是抄着资料走的:挑了模式填 0x118,注释和教程的正文里都写着 1024x768x32。切换倒是很顺利,屏幕唰地一黑,按当时的想法就算成了。可 framebuffer 里到底住着什么,笔者压根就没验过。
排查是在黑屏之后做的。模式一被咱们切掉,文本输出就没了,当年还没有 debugcon 这样的调试通道,等于睁眼的瞎子。笔者提着 GDB 上,把存下来的 framebuffer 参数读了出来:基地址 0xFD000000,行距 pitch 的值是 3072。您拿计算器按一按:3072 除以 1024 得 3,每像素摊到的就是 3 个字节。24 位色实打实摆在这儿了,说好的 32 位呢?它从头到尾就没有过,只活在笔者的注释里。咱们自己这一算只当个旁证就好,后面讲到行距的时候您会看到,咱们不能拿宽乘色深再除以 8 当准,今天它恰好对上而已。
打脸的第二记,偏偏这时候才来。咱们翻开维基百科的 VESA 模式号表一对:标准表给 0x118 定的口径就是 1024×768×24,这是出处写下的,不靠咱们反推。32 位色在标准表里压根就找不到了,1024×768×32 对应的 0x138,则落在厂商常用、却没有标准的那一栏里。所以当年那个值,语义是编的,出处是抄的,咱们两头都没对上。
常用号,也不可靠
您可能想,那咱们把 0x138 背下来用不就得了?咱们真去验过了。动工之前咱们做过一次试写探测,咱们那会儿还没正式铺开代码,只编了一版出来,把还不确定的点挨个撞一遍。那次咱们把 QEMU 里 SeaBIOS 报上来的模式列表整个打了出来,九十三个号逐个查了参数。结果就摆在眼前,QEMU 的列表里真有 0x144,如假包换的 1024×768×32,而维基常用号表里的那个 0x138,列表里压根没有它的影子。您品品:连厂商常用那一栏,到头来也不过是些传说而已,每家的显卡、每个模拟器,手里攥着的都是自己的一份清单。咱们手里 1024×768 有 24 位的 0x118、32 位的 0x144,这些编号,只有这台 QEMU 自己说了算。
倒不是咱们运气差。维基那一页的下半页写着官方的态度:VBE 2.0 起,VESA 不再定义新的标准模式号,旧的号也不保证各家继续支持。正确做法官方也给了:调 0x4F00 拿到这块显卡自己的模式列表,咱们再拿 0x4F01 一个一个查参数,按宽、高、色深来挑了。OSDev 的 VESA 页把这套枚举流程写成了样例代码,清单的头一条,就是咱们写死模式号这一类。
枚举,不要钱
那当年为什么要写死呢?咱们替当年的自己说句公道话:汇编的年代,写死就是一条三个字节的 mov,把立即数直接编进去就完了。可要咱们跑一趟枚举循环,循环里的判定和搬运加起来是几十上百条指令,在那个每条指令都要手数的阶段,写死是省字节的无奈。现在咱们写的是 C++,咱们写一个 for 循环、一个判定函数,编译器就替咱们生成全部的指令,咱们花的只是几行源码。笔者当时定的调子是:“反正都 C++ 了,枚举循环的那点开销,咱们不差。”
于是本站的路线定下来了:咱们不写任何模式号,只把咱们的诉求用大白话说清楚了,宽高这一项就定在了 1024×768,32 位色咱们最想要,24 位咱们也认。然后咱们挨个问 BIOS,谁的条件对得上,就用谁的模式号切过去。模式号从头到尾都是问出来的,不是背出来的。
咱们顺着枚举走下去,连 bpp 的讲究都顺手办了,模式号那边的纠结也一并省掉。bpp 说的是每像素多少位,它跟上一站咱们立的纪律沾亲:BIOS 写进 ModeInfo 的字段才是真话,从模式号里推出来的都是脑补。咱们的判定函数读的就是 ModeInfo 的 BitsPerPixel 字段,模式号在手里只是一把寻址用的钥匙。