概要
『30日でできる!OS自作入門』で取り扱っているはりぼてOSでは,最大256色使用できる8ビットカラーを使用しています*1.これに対し,最大16,777,216色使用できる24ビットカラーや32ビットカラーが存在します.この記事では,はりぼてOSで24ビットカラーや32ビットカラーを使用するようにコードを書き換えます.また,解像度も大きくします.
本の中で述べられている解像度の拡大の方法との違い
使用するVBEモードに対応するモードナンバの選択が,本中のやり方とこの記事でのやり方で異なります.本中では,予めVESAが定義したモードナンバを使用していますが,この記事では,BIOSに使用可能なモードナンバを問い合わせ,その中で最も解像度の高い,かつ24ビットカラーか32ビットカラーでダイレクトカラーのモードナンバを使用します.
VBEのバージョンが2.0未満の時代は,予めVESAが定義したモードナンバに対応するVBEモードを使用していましたが,2.0からは,VESAはモードナンバを新たに定義することを止めました*2.バージョン2.0以上でもVESAが既に定義しているモードナンバを使用することも可能ですが,1280x1024以上の解像度が定義されていません*3.これより大きい解像度を使用したい場合,BIOSに利用可能なVBEモードを問い合わせる必要があります.この記事ではBIOSに使用可能なモードナンバを問い合わせ,その中で最も解像度の高い,かつ24ビットカラーか32ビットカラーそしてダイレクトカラーのモードナンバを使用します.
ダイレクトカラー
ダイレクトカラーとは,色をその構成要素に分解し,構成要素毎に値を指定して最終的な色を指定する方式です.例えばRGB,つまり赤緑青の値を指定する方式などが該当します.これに対し,予めパレットに色を登録し,そのパレットの番号を指定する,インデックスカラーというものも存在します.はりぼてOSではこのインデックスカラーを使用しています.今回使用する24ビットカラーや32ビットカラーでは,ダイレクトカラー方式で色を指定します.
24ビットカラーと32ビットカラーの違い
まず,24ビットカラーも32ビットカラーも,赤,緑,青の3色の強さをそれぞれ0から255の計256種類の値で指定します.よって256の3乗で16,777,216色を表すことができます.また,赤緑青でそれぞれ8ビット使用することから,計24ビット使用することになります.
32ビットカラーはこの24ビットに加え,追加の8ビットが与えられます.これはアルファチャンネルと言われ,例えば透明度の設定などに使用されますが,全く使用されない場合もあります.何れにせよ,表現できる総色数は変わりません.
24ビットカラーや32ビットカラー以外を選択肢から外す理由
単純に24ビットカラーや32ビットカラーが一番多くの種類の色を扱えるから,というのもありますが,他の色深度*4,例えば16ビットカラーや15ビットカラーだと色を指定するのが少し面倒なんですよね.VBE3.0の規格書のPage19にある表によれば,15ビットカラーは赤緑青でそれぞれ5ビットを使用します.これならまだいいのですが,16ビットカラーでは1つの色だけ*56ビットを使用しています.このようにビット数が異なると当然その分コードも複雑になるので,ここでは24ビットカラーか32ビットカラーのみを選択肢として残すことにします.
注意
この記事では本の著者が提供しているツールを使用せず,アセンブリ言語のコンパイルにはnasmを使用しています.そのため一部文法が異なる場合があります.そして,記事を書いている本人は本の最後まで実装しきれていません*6.故に今回使用しているメモリ領域が後に別の項目で使用される場合もあります.そのような場合,適宜使用するメモリの番地を変更してください.
やり方
asmhead.nasの書き換え
書き換えたものはこちらになります.
以下は説明となります.
定数定義の追加と名称変更
使用するVBEのモードナンバがどこに格納されているかと,VBEの情報の大きさに関する定義を追加します.
VBEMODE EQU 0x0FFC VBE_INFO_SIZE EQU 512
後にVBEの情報を取得する関数を紹介しますが,VBEの情報は,VBEのバージョンが2.0未満では256バイト,2.0以上では512バイトの大きさです.
また,VMODE
に関しては,BPP
*7の方が分かりやすいので,名前を変更しています.これは任意です.
BPP EQU 0x0FF2
利用可能な画面モードの取得
VBE3.0の規格書のPage25より引用.
Function 00h - Return VBE Controller Information
Input:
AX = 4F00h Return VBE Controller Information
ES:DI = Pointer to buffer in which to place VbeInfoBlock structure (VbeSignature should be set to 'VBE2' when function is called to indicate VBE 3.0 information is desired and the information block is 512 bytes in size.)
Output: AX = VBE Return Status
Note: All other registers are preserved.
この関数を使うことで,利用可能なVBEモードなどが格納されている情報を取得することができます.
OutputのVBE Return Statusは,0x004F
なら関数の実行の成功,それ以外なら失敗を表します.
情報の構成は,次の構造体のような構成になっています.VBE3.0の規格書Page25に記載されているものを表にしました.
名前 | 大きさ | 格納されているデータなど | 説明 |
---|---|---|---|
VbeSignature | db*8 | 'VESA' | VBE Signature |
VbeVersioin | dw*9 | 0300h | VBE Version |
OemStringPtr | dd*10 | VbeFarPtr to OEM String | |
Capabilities | db | 4dup?*11 | Capabilities of graphics controller |
VideoModePtr | dd | VbeFarPtr to VideoModeList | |
TotalMemory | dw | Number of 64kb memory blocks. Added for VBE 2.0+ | |
OemSoftwareRev | dw | VBE implementation Software revision | |
OemVendorNamePtr | dd | VbeFarPtr to Vendor Name String | |
OemProductNamePtr | dd | VbeFarPtr to Product Name String | |
OemProductRevPtr | dd | VbeFarPtr to Product Revision String | |
Reserved | db | 222dup? | Reserved for VBE implementation scratch area |
OemData | db | 256dup? | Data Area for OEM Strings |
ところで,この関数は本中のP278でも使用されてます.そこではVBEの存在確認としてこの関数を呼び出してまずが,以下のコードはそれに若干改変を加えたものです.具体的には,VBE
に0x9000
を対応付け,それをES
レジスタに代入して利用しています.また,scrn_320
をscreen_320
に変更しています.この先,同書から引用する際,scrn_320
となっているものはすべてscreen_320
に変更しています.
VBE EQU 0x9000 MOV AX,VBE MOV ES,AX MOV DI,0 MOV AX,0x4F00 INT 0x10 CMP AX,0x004F JNE screen_320
関数の実行に成功すれば,AX
レジスタに0x004F
が代入され,アドレスVESABIOS
から512バイト*12,VBEに関する情報が格納されます.異なっていればscreen_320
ラベルに飛ばします.本ではもしVBEが存在しなかった場合,解像度を320x200にするという意味でJNE screen_320
を書いています.
この関数で得られた情報の中にはVBEのバージョンも含まれています*13.VBEのバージョンが2.0未満である場合,linear frame bufferを使用することができない*14ので,解像度320x200の8ビットカラーを使用します.以下のコードは,本のP279からの引用です.
MOV AX,[ES:DI+4] CMP AX,0x0200 JB screen_320
linear frame bufferを有効にすると,ビデオRAMのすべてのメモリが一列に並びます.つまり,ディスプレイのどのピクセルもこのビデオRAMのどこかしらに対応しています.linear frame bufferに対応していない場合,ビデオRAMが複数に分けられ,これらを切り替えながら操作する必要があります.
[ES:DI+4]
は,VbeVersion
変数が構造体の先頭から4バイト離れたところに格納されているためです.
使用するモードナンバの選択
今回は,24ビットカラーあるいは32ビットカラーで,かつ解像度が最大のモードナンバを使用することにします.
使用可能なモードナンバはメモリ上に,配列のように連続して格納されています.モードナンバの終端は0xFFFF
という値で終了しています*15.今回はこのモードナンバの配列を走査して,順に解像度と色深度を確かめていきます.
ループの初期化
ループの初期化として,色深度,解像度の横の長さ,縦の長さの初期値を指定します.
MOV BYTE[BPP],8 MOV WORD[SCRNX],320 MOV WORD[SCRNY],200
また,DI
レジスタに,VBEの情報の大きさ分の値を代入しておきます.
MOV DI,VBE_INFO_SIZE
VBEの情報を取得したときと同様,モードナンバに対応するVBEモードの情報を取得すると,情報はES:DI
を始点として情報が格納されます.従って,VBEの情報を上書きしないために,VBEの情報の大きさの値をDI
レジスタに格納しておきます.
ループ本体
ループ開始位置のラベル付与
ループの開始を示すためにラベルを配置します.
select_mode:
定数定義
モードナンバの配列の先頭のアドレスが,先程載せた,VBEの情報が格納されている構造体のvideo_modes
変数に格納されています.この変数は構造体の先頭から14バイト離れた位置にあるので,それを定数とします.
VMODE_PTR EQU 14
モードナンバの取得
モードナンバの配列からモードナンバを取得します.
MOV SI,WORD[ES:VMODE_PTR] MOV FS,WORD[ES:VMODE_PTR+2] MOV CX,WORD[FS:SI]
そして,もしモードナンバの値が0xFFFF
だった場合,モードナンバの選択を終了します.
CMP CX,0xFFFF JE finish_select_mode
モードナンバに対応するVBEモードの情報取得
VBE3.0の規格書のPage30より引用.
Function 01h - Return VBE Mode Information
Input: AX = 0x4F01 Return VBE Mode Information
CX = Mode number
ES:DI = Pointer to ModeInfoBlock structure
Output: AX = VBE Return Status
Note: All other registers are preserved.
この関数を利用して,VBEモードの情報を取得します.
MOV AX,0x4F01 INT 0x10
もし関数の実行に失敗した場合,次のモードナンバを調べます.
CMP AX,0x004F JNE next_mode
ところで,モードナンバが配列の要素として格納されていながら,実際にその番号を使用してVBEモードの情報を得ようとして失敗する場合は存在するようです.VBE3.0の規格書のPage27のNoteより引用.
It is responsibility of the application to verify the actual availability of any mode returnedby this function through the Return VBE Mode Information (VBE Function 01h) call.Some of the returned modes may not be available due to the actual amount of memoryphysically installed on the display board or due to the capabilities of the attached monitor.
つまり,例えば使用できるメモリの大きさが,ビデオRAMの大きさよりも小さい場合などが該当するようです.
VBEモードの情報は,以下の構造体のような構成となっています.VBE3.0の規格書のPage30の説明を表にしました.
すべてのバージョンのVBEで格納されている情報
名前 | 大きさ | 格納されているデータなど | 説明 |
---|---|---|---|
ModeAttributes | dw | mode attributes | |
WinAAttributes | db | window A attributes | |
WinBAttributes | db | window B attributes | |
WinGranularity | dw | window granularity | |
WinSize | dw | window size | |
WinASegment | dw | window A start segment | |
WinBSegment | dw | window B start segment | |
WinFuncPtr | dd | real mode pointer to window function | |
BytesPerScanLine | dw | bytes per scan line |
バージョン1.2以降のVBEで格納されている情報
名前 | 大きさ | 格納されているデータなど | 説明 |
---|---|---|---|
XResolution | dw | horizontal resolution in pixels of characters | |
YResolution | dw | vertical resolution in pixels of characters | |
XCharSize | db | character cell width in pixels | |
YCharSize | db | character cell height in pixels | |
NumberOfPlanes | db | number of memory planes | |
BitsPerPixel | db | bits per pixel | |
NumberOfBanks | db | number of banks | |
MemoryModel | db | memory model type | |
BankSize | db | bank size in KB | |
NumberOfImagePages | db | number of images | |
Reserved | db | 1 | reserved for page function |
ダイレクトカラー情報
名前 | 大きさ | 格納されているデータなど | 説明 |
---|---|---|---|
RedMaskSize | db | size of direct color red mask in bits | |
RedFieldPosition | db | bit position of lsb of red mask | |
GreenMaskSize | db | size of direct color green mask in bits | |
GreenFieldPosition | db | bit position of lsb of green mask | |
BlueMaskSize | db | size of direct color blue mask in bits | |
BlueFieldPosition | db | bit position of lsb of blue mask | |
RsvdMaskSize | db | size of direct color reserved mask in bits | |
RsvdFieldPosition | db | bit position of lsb of reserved mask | |
DirectColorModeInfo | db | direct color mode attributes |
バージョン2.0以降のVBEで格納されている情報
名前 | 大きさ | 格納されているデータなど | 説明 |
---|---|---|---|
PhysBasePtr | dd | physical address for flat memory frame buffer | |
Reserved | dd | 0 | Reserved - always set to 0 |
Reserved | dw | 0 | Reserved - always set to 0 |
バージョン3.0以降のVBEで格納されている情報
名前 | 大きさ | 格納されているデータなど | 説明 |
---|---|---|---|
LinBytesPerScanLine | dw | bytes per scan line for linear modes | |
BnkNumberOfImagePages | db | number of images for banked modes | |
LinNumberOfImagePages | db | number of images for linear modes | |
LinRedMaskSize | db | size of direct color red mask (linear modes) | |
LinRedFieldPosition | db | bit position of lsb of red mask (linear modes) | |
LinGreenMaskSize | db | size of direct color green mask (linear modes) | |
LinGreenFieldPosition | db | bit position of lsb of green mask (linear modes) | |
LinBlueMaskSize | db | size of direct color blue mask (linear modes) | |
LinBlueFieldPosition | db | bit position of lsb of blue mask (linear modes) | |
LinRsvdMaskSize | db | size of direct color reserved mask (linear modes) | |
LinRsvdFieldPosition | db | bit position of lsb of reserved mask (linear modes) | |
MaxPixelClook | dd | maximum pixel clock (in Hz) for graphics mode |
その他
名前 | 大きさ | 格納されているデータなど | 説明 |
---|---|---|---|
Reserved | db | 189dub? | remainder of ModeInfoBlock |
linear frame bufferに対応しているかの確認
VBEモードがlinear frame bufferに対応しているかは,VBEモードの情報の中のattributes
の第7ビットが1になっているかで確認します.これが1ならlinear frame bufferに対応しています.
MOV AX,WORD[ES:DI] AND AX,0x80 CMP AX,0x80 JNE next_mode
packed pixelかどうかの確認
packed pixelというのは,各ピクセルとメモリをバイト単位で結びつけるのではなく,ビット単位で結びつけます.例えば4ビットカラーならば,1バイトを2ピクセルと対応付け,前半後半の4ビットをぞれぞれ1ピクセルと対応付けます.1ピクセルを1バイトと結びつけ,最初の4ビットを使用し残りの4ビットを使用しないのではありません,つまり隙間を作らないという意味でpackedということです.
現在調べているVBEモードがpacked pixelかどうかは構造体のmemory_model
の値を確認することで判別します.
MOV AX,WORD[ES:DI+27] CMP AX,4 JE valid_mode
[ES:DI+27]
の27というのは,memory_model
が構造体の先頭から27バイト離れているためです.もしこの値,すなわちAX
の値が4ならば,使用するVBEモードの候補として有効なため,valid_mode
ラベルに飛ばします.
ダイレクトカラーかどうかの確認
ダイレクトカラーかどうかは,memory_model
の値が6かどうかを確認することで判別します.
CMP AX,6 JE valid_mode
既にmemory_model
の値はAX
レジスタに格納しているので,ここではMOV
の必要はありません.
最後に,もしpacked pixelでもなくダイレクトカラーでもなければ,次のモードナンバを調べます.
JMP next_mode
ところで,packed pixelかどうか,そしてダイレクトカラーかどうかの確認については,OSDev WikiのfindMode
関数を参考にしました.
解像度の比較
現在調べているVBEモードがpacked pixelかダイレクトカラーを使用すると確認できれば,次は解像度の比較を行います.まずラベルを貼ります.
valid_mode:
解像度の横,縦はそれぞれ構造体のwidth
,height
に格納されています.構造体の先頭からの距離はそれぞれ18バイト,20バイトです.
MOV AX,WORD[ES:DI+18] CMP AX,WORD[SCRNX] JB next_mode MOV AX,WORD[ES:DI+20] CMP AX,WORD[SCRNY] JB next_mode
もし横,縦のどちらかでも,それまでの候補のVBEモードより小さければ次の候補を調べます.
色数の比較
先程述べたように,24ビットカラーか32ビットカラー以外は選択肢から外します.色数は構造体のbppに格納され,先頭から25バイト離れています.
CMP BYTE[ES:DI+25],24 JB next_mode
候補の更新
見事,今までの選考基準を通過したVBEモードが登場した場合,解像度や色数に関する情報を更新します.色数の更新の時,AX
レジスタではなくAL
レジスタを使用していることに注意してください.これは,色数がバイト単位で保存されているためです.
MOV AX,WORD[ES:DI+18] MOV WORD[SCRNX],AX MOV AX,WORD[ES:DI+20] MOV WORD[SCRNY],AX MOV AL,BYTE[ES:DI+25] MOV BYTE[BPP],AL MOV AX,WORD[ES:DI+40] MOV WORD[VRAM],AX MOV AX,WORD[ES:DI+40+2] MOV WORD[VRAM+2],AX MOV WORD[VBEMODE],CX
次のループへの処理
モードナンバの配列のポインタを更新し,ループの先頭へ飛びます.
MOV AX,WORD[ES:VMODE_PTR] ADD AX,2 MOV WORD[ES:VMODE_PTR],AX JMP select_mode
ループ終了後の処理
まずラベルを追加します.
finish_select_mode:
解像度と色数の確認
解像度や色数が初期値のままの場合,使用可能なVBEモードが存在しないので320x200を使用します.
CMP WORD[SCRNX],320 JNE set_vbe_mode CMP WORD[SCRNY],200 JNE set_vbe_mode CMP BYTE[BPP],8 JNE set_vbe_mode JMP screen_320
VBEモードの設定
まずラベルを貼ります.
set_vbe_mode:
VBEモードを登録します.以下の情報はVBE3.0の規格書のPage40からです.
Function 02h - Set VBE Mode
Input:
AX = 4F02h Set VBE Mode
BX = Desired Mode to set
ES:DI = Pointer to CRTCInfoBlock structure
Output:
AX = VBE Return Status
Note: All other registers are preserved.
BX
レジスタの,各ビットの意味は以下の表のとおりです.
第nビット | 値 | 説明 |
---|---|---|
0-8 | Mode number | |
9-10 | Reserved (must be 0) | |
11 | 0 | Use current default refresh rate |
11 | 1 | Use user specified CRTC values for refresh rate |
12-13 | Reserved for VBE/AF (must be 0) | |
14 | 0 | Use windowed frame buffer model |
14 | 1 | Use linear/flat frame buffer model |
15 | 0 | Clear display memory |
15 | 1 | Don't clear display memory |
この関数が失敗する場合もあります.その場合,320x200を使用します.
MOV AX,0x4F02 MOV BX,WORD[VBEMODE] OR BX,0x4000 INT 0x10 CMP AX,0x004F JE keystatus
表の通り,BX
レジスタの第14ビット目は,linear frame bufferを使用するか否かについてのビットであり,このビットが1だと使用することを表明します.OR BX,0x4000
というコードはそのためです.
320x200を使用する時の処理
本中のP280のコードを使用していますが,ラベルはscrn320
からscreen_320
に,VMODE
はBPP
に変更しています.
screen_320: MOV AL,0x13 MOV AH,0x00 INT 0x10 MOV BYTE [BPP],8 MOV WORD [SCRNX],320 MOV WORD [SCRNY],200
本ではこの先,ビデオRAMの番地を0x000A0000に指定していますが,この値にするとQEMU上で何も表示されなくなる場合があります*16.「30日でできる! OS自作入門」をMac向けに環境構築するによれば,番地を0xFD000000に変更することで表示されるようです.
MOV DWORD [VRAM],0xFD000000
Cファイルの書き換え
基本的なこと
今まではビデオRAMの特定の番地に,色の番号を格納していましたが,ダイレクトカラーを使用する時は番号ではなく構成要素のそれぞれの値を格納します.今回使用する24ビットカラーや32ビットカラーでは赤,緑,青の色の強さを格納します.
例として,本中のP94にある,boxfill8
関数を改造します.元の関数は以下の通りです.
void boxfill8(unsigned char *vram, int xsize, unsigned char c, int x0, int y0, int x1, int y1) { int x, y; for (y = y0; y <= y1; y++){ for (x = x0; x <= x1; x++) vram[y * xsize + x] = c; } return; }
書き換え後は以下の通りです.
void boxfill8(struct BootInfo* boot_info, unsigned c, int x0, int y0, int x1, int y1) { int x, y; int xsize = boot_info->scrnx; char* vram = boot_info->vram; char vmode = boot_info->vmode; for (y = y0; y <= y1; y++) { for (x = x0; x <= x1; x++) { int idx = (y * xsize + x) * vmode / 8; vram[idx] = (c & 0x0000FF); vram[idx + 1] = (c & 0x00FF00) >> 8; vram[idx + 2] = (c & 0xFF0000) >> 16; } } }
まず,改造前では引数c
の型がunsigned char
となっていたのが,改造後はunsigned
となっているところに気をつけてください.改造前ではこの引数には色のインデックスを代入していましたが,改造後は色のRGBの値を代入します.
次にfor
文中で,ビデオRAMに値を格納しているところに注目してください.まず,格納する番地です.vmode / 8
を掛けているところに注意してください.これは1つのピクセルを表すのに必要なバイト数です.RGB値はB,G,Rの順番で,分解して格納します.R,G,Bの順番ではないことに注意してください.
このように,24ビットカラーや32ビットカラーを使用する時は,ビデオRAMに値を格納する処理すべてにおいて変更が必要となります.
320x200への対応
関数の失敗や,使用可能なVBEモードが存在しないと言った理由で320x200を使用する場合があります.このような場合,ダイレクトカラーではなくインデックスカラーを使用することになります.従ってRGB値をインデックスに変換するなど,何かしらの処理が必要となります.こちらの説明については長くなってしまうので今は省略させていただきます.
トラブルシューティング
上手く行かない時は,QEMUでメモリの特定の番地の値を確認するといいかもしれません.QEMUでのメモリの値の確認はこちらの「QEMUでメモリの内容を見る」を参考にしました.
ここでは,色数などの情報を確認します.QEMUを使用してOSを起動したあと,Ctrl+Alt+2
でQEMUのターミナルを開いてください.
起動したあと,以下のコマンドを入力してください.
xp /10xb 0x0FF2
はじめの1バイトは色深度で,この場合は0x18
すなわち24ビットカラーです.1つ飛ばして次の2バイトは解像度の横の長さで,この場合は0x0A00
すなわち2560ピクセルです.リトルエンディアンなのではじめのバイトが下位,次のバイトが上位であることに注意してください.その次は解像度の縦の長さで,0x0640
すなわち1600ピクセルです.表示される色がおかしい場合,はじめの1バイトの値が0x18
や0x20
ではないかもしれません.
終わりの言葉
はりぼてOSを24ビットカラーや32ビットカラーに対応させることができました.また解像度も大きくしたので,そのうち背景画像を設定できるようになるのではと思っています.
僕が製作中のOSです.Rustで書かれていますが,もしよければ見ていってください.
参考文献
monitor - Is 32-bit color depth enough? - Graphic Design Stack Exchange
VESA BIOS Extensions - Wikipedia
「30日でできる! OS自作入門」をMac向けに環境構築する - Qiita
*1:パレットに16色しか登録していないので4ビットカラーと勘違いするかもしれない(僕も勘違いした)ですが,P83に記載があるように8ビットカラーを使用しています.
*2:VBE3.0の規格書のPage19にあるNoteを参照.
*3:VBE3.0の規格書のPage19にある表を参照
*5:恐らく緑
*6:具体的にはまだ9日目に到達していない.
*7:bits per pixel
*8:バイト
*9:ワード
*10:ダブルワード
*11:4つの要素が存在する
*12:VBEのバージョンが2.0未満ならば216バイト
*13:表中のVbeVersion
*14:VBE3.0の規格書のAppendix 3 - Differences Between VBE Revisionsを見ると,VBE2.0でFlat Frame Buffer,すなわちlinear frame bufferが追加されたと書かれている.
*16:僕も遭遇した.