VESA BIOS Extensions(1920px * 1080px、24bpp)を使用して、画面にシアンのピクセルを描くことができました。
;esi = bytes per scan line
;edx = physical address of linear framebuffer memory.
;ebx = x coord * 3
;ecx = y coord
DrawPixel:
Push edx
mov edx, 0
mov eax, 0
lea eax, [esi]
;mov ecx, 0
mul ecx
add eax, ebx
jmp draw
draw:
pop edx
add edx, eax
mov ebx, 0x3296fa
mov [edx], ebx
ret
このように「forループ」を使用して、画面にシアンの水平線を描画しようとしました:
mov edi, 1920
call drawLoop
jmp $
drawLoop:
dec edi ;decrease edi
cmp edi, 0 ;is edi equal to zero?
jl doneLoop ;then return
imul ebx, edi, 3 ;multiply edi by three and save the result in ebx
mov ecx, 0 ;y = 0
mov esi, ModeInfoBlock + 10h
mov edx, dword[ModeInfoBlock + 28h]
call DrawPixel ;Draw it!
jmp drawLoop ;run this again
doneLoop:
ret
ただし、これは機能しません。代わりに緑色の線が描画されます。
ピクセルの描画/描画コードで垂直線をもう一度描画しようとしても、うまくいきません。それはどこでもランダムな色でピクセルをプロットします。これがDrawPixel
関数を使用して垂直線を描く方法です。
%include "../kernel/Services/Display/display.asm"
kernel:
mov edi, 1080
call drawLoop
jmp $
drawLoop:
dec edi
cmp edi, 0
jl doneLoop
mov ecx, edi
mov ebx, 0
mov esi, ModeInfoBlock + 10h
mov edx, dword[ModeInfoBlock + 28h]
call DrawPixel
jmp drawLoop
doneLoop:
ret
これらの問題を解決する方法はありますか?
DrawPixelルーチンを書き直すことから始めましょう。現在それは少し混乱しています!
mul
レジスタを不必要に上書きするEDX
命令を使用しても意味がありません。 imul
のバリアントを使用することをお勧めします。
そして、mov eax, 0
lea eax, [si]
を使用してEAX
レジスタをロードする代わりに、単にmov eax, esi
と書いてみませんか?
考慮すべきエラーもあります。 24ビットトゥルーカラースクリーンで作業しているため、dword(32ビット)全体を書き込むと、隣接するピクセルの一部が変更されます。
;esi = bytes per scan line
;edx = physical address of linear framebuffer memory.
;ebx = x coord * 3
;ecx = y coord
; IN (ebx,ecx,edx,esi) OUT () MOD (eax)
DrawPixel:
mov eax, esi ; BytesPerScanLine
imul eax, ecx ; BytesPerScanLine * Y
add eax, ebx ; BytesPerScanLine * Y + X * 3
mov Word [edx+eax], 0x96FA ; Low Word of RGB triplet
mov byte [edx+eax+2], 0x32 ; High byte of RGB triplet
ret
この新しいルーチンはEAX
レジスタのみを変更します
main部分には独自の問題があります:
mov esi, ModeInfoBlock + 10h
はBytesPerScanLine情報を取得しません。そのためにはmovzx esi, Word [ModeInfoBlock + 10h]
が必要です
ループは、反復ごとに2つの分岐を使用します。単一の分岐でループを書くことは完全に可能です。
次は私のバージョンです。新しいDrawPixelルーチンはすべてのレジスタを保存するため(EAX
を除く)、大幅な簡略化が可能です。
xor ebx, ebx ; X = 0 -> EBX = X * 3
xor ecx, ecx ; Y = 0
movzx esi, Word [ModeInfoBlock + 10h] ; BytesPerScanLine
mov edx, [ModeInfoBlock + 28h] ; PhysBasePtr
call drawLoop
jmp $
drawLoop:
call DrawPixel ; Modifies EAX
add ebx, 3 ; Like X = X + 1
cmp ebx, 1920*3 ; Length of the line is 1920 pixels
jb drawLoop
ret
私のバージョンでは、この水平線を左から右に描画しています。右から左に描くよりも少し速いと思います。
別のループカウンター(EDI
)を使用する代わりに、トリプルX座標を介してループを制御します。他の利点(cmp
とjb
が適切にペアリングされるため速度など)の中でも、これはレジスター使用のプレッシャーを軽減します。
特に、水平線と垂直線を描画する場合、繰り返しDrawPixelルーチンを呼び出すことはお勧めできません。ピクセルのアドレスを何度も計算するのは時間の無駄です。以下に、これらのタスク専用のルーチンをいくつか示します。
私はいくつかの追加の変更を加えました:
; IN (eax,ebx,ecx,edx) OUT () MOD (eax)
; EAX = X
; EBX = Y
; ECX = Color
; EDX = Line length
HLine:
Push edx
Push edi
movzx edi, Word [ModeInfoBlock + 10h] ; BytesPerScanLine
imul edi, ebx ; BytesPerScanLine * Y
imul eax, 3 ; X * 3
add edi, eax ; BytesPerScanLine * Y + X * 3
add edi, [ModeInfoBlock + 28h] ; ... + PhysBasePtr
mov eax, ecx ; Color 24 bits
shr eax, 8
imul edx, 3 ; Line length * 3
add edx, edi ; Address of the end of line
.a: mov [edi], cx ; Low Word of RGB triplet
mov [edi+2], ah ; High byte of RGB triplet
add edi, 3 ; Like (X + 1)
cmp edi, edx
jb .a
pop edi
pop edx
ret
上記のHLineルーチンは、左から右に水平線を描画します。
; IN (eax,ebx,ecx,edx) OUT () MOD (eax)
; EAX = X
; EBX = Y
; ECX = Color
; EDX = Line length
VLine:
Push edx
Push esi
Push edi
movzx esi, Word [ModeInfoBlock + 10h] ; BytesPerScanLine
mov edi, esi
imul edi, ebx ; BytesPerScanLine * Y
imul eax, 3 ; X * 3
add edi, eax ; BytesPerScanLine * Y + X * 3
add edi, [ModeInfoBlock + 28h] ; ... + PhysBasePtr
mov eax, ecx ; Color 24 bits
shr eax, 8
imul edx, esi ; Line length * BytesPerScanLine
add edx, edi ; Address of the end of line
.a: mov [edi], cx ; Low Word of RGB triplet
mov [edi+2], ah ; High byte of RGB triplet
add edi, esi ; Like (Y + 1)
cmp edi, edx
jb .a
pop edi
pop esi
pop edx
ret
上記のVLineルーチンは、上から下に垂直線を描画します。
これは、これらを使用する方法です。
Main:
xor eax, eax ; X = 0
xor ebx, ebx ; Y = 0
mov ecx, 0x003296FA ; Color cyan
mov edx, 1920 ; Line length
call HLine ; -> (EAX)
mov edx, 1080
call VLine ; -> (EAX)
jmp $
コメントに基づいて、ピクセルごとに4バイトではなく3バイトをビデオディスプレイに書き込むだけで、水平線の描画に関する問題を解決しました。余分なバイトは、画面上の次のピクセルの色を変更していました。私の改訂されたコードは次のようになります:
DrawPixel:
Push edx
mov edx, 0
mov eax, 0
mov eax, esi
mul ecx
add eax, ebx
jmp draw
draw:
pop edx
add edx, eax
mov Word[edx], 0x96fa
mov byte[edx + 2], 0x32
ret
縦線を生成するコードで、mov esi, ModeInfoBlock + 10h
をmovzx esi, Word[ModeInfoBlock + 10h]
に置き換えることで問題を解決することができました。
movzx
命令は16ビットのbytesPerScanLine
値を32ビットのesi
レジスタに移動し、残りをゼロで埋めるためです。 「mov e z ero e x tend」の略です。
私の改訂した垂直描画コード:
%include "../kernel/Services/Display/display.asm"
kernel:
mov edi, 1920
call drawLoop
jmp $
drawLoop:
dec edi
cmp edi, 0
jl doneLoop
imul ebx, edi, 3
mov ecx, edi
movzx esi, Word[ModeInfoBlock + 10h]
mov edx, dword[ModeInfoBlock + 28h]
call DrawPixel
jmp drawLoop
doneLoop:
ret
これらは私の最終的な描画関数です:
;esi = bytes per scan line
;edx = physical address of linear framebuffer memory.
;ebx = x coord * 3
;ecx = y coord
DrawPixel:
Push edx
mov edx, 0
mov eax, 0
mov eax, esi
mul ecx
add eax, ebx
jmp draw
draw:
pop edx
add edx, eax
mov Word[edx], 0x96fa
mov byte[edx + 2], 0x32
ret