将水平线和垂直线绘制到 VBE 返回的 LFB 的结果不正确



我终于设法使用 VESA BIOS 扩展(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, 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寄存器


主要部分有自己的问题:

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坐标控制循环。除了其他好处(如速度,因为cmpjb配对良好(外,这减轻了寄存器使用的压力。

更好的水平和垂直线条绘制程序

特别是对于绘制水平线和垂直线,重复调用DrawPixel例程并不是一个好主意。一遍又一遍地计算像素的地址是浪费时间。下面我将展示几个专门针对这些任务的例程。

我添加了一些额外的更改:

  • 您不应该给主程序带来寻址视频内存的技术细节。让图形例程检索BytesPerScanLinePhysBasePtr值。
  • 主程序应该处理 (X,Y( 级别的像素。">乘以 3"的东西又是属于图形例程的技术细节。
  • 在绘图例程中对颜色进行硬编码是非常不灵活的。
; 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     $

绘制水平线

根据评论,我通过仅在视频显示中写入 3 个字节而不是每个像素写入 4 个字节来解决绘制水平线的问题。额外的字节正在改变屏幕上下一个像素的颜色。我修改后的代码如下所示:

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寄存器中,并用零填充其余值。它代表">movezero extend"。

我修改后的垂直绘图代码:

%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

最新更新