web-dev-qa-db-ja.com

VBEから返されたLFBに水平線と垂直線を描画する誤った結果

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

これらの問題を解決する方法はありますか?

2
MARSHMALLOW

DrawPixelルーチンを書き直すことから始めましょう。現在それは少し混乱しています!

mulレジスタを不必要に上書きするEDX命令を使用しても意味がありません。 imulのバリアントを使用することをお勧めします。

そして、mov eax, 0lea 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 + 10hBytesPerScanLine情報を取得しません。そのためには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座標を介してループを制御します。他の利点(cmpjbが適切にペアリングされるため速度など)の中でも、これはレジスター使用のプレッシャーを軽減します。

水平および垂直線描画ルーチンの改善

特に、水平線と垂直線を描画する場合、繰り返しDrawPixelルーチンを呼び出すことはお勧めできません。ピクセルのアドレスを何度も計算するのは時間の無駄です。以下に、これらのタスク専用のルーチンをいくつか示します。

私はいくつかの追加の変更を加えました:

  • メインプログラムにビデオメモリのアドレス指定の技術的な詳細を負担させるべきではありません。グラフィックスルーチンにBytesPerScanLineおよびPhysBasePtrの値を取得させます。
  • メインプログラムは(X、Y)レベルのピクセルを処理する必要があります。 "times"は、グラフィックルーチンに属する技術的な詳細です。
  • 描画ルーチンで色をハードコーディングすることは、非常に柔軟ではありません。
; 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     $
2
Sep Roland

横線を引く

コメントに基づいて、ピクセルごとに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 + 10hmovzx 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
2
MARSHMALLOW