向 VBE 返回的 LFB 绘制水平线和垂直线的结果不正确
Incorrect results drawing horizontal and vertical lines to the LFB returned by VBE
我终于设法使用 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 loop"在屏幕上画一条青色的横线是这样的:
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
但是,这不起作用:它绘制了一条绿线。
当我尝试使用 draw/draw 像素代码再次绘制垂直线时,它也不起作用。它在各处绘制具有随机颜色的像素。以下是我如何使用 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
有什么办法可以解决这些问题?
绘制水平线
根据评论,我解决了绘制水平线的问题,方法是只向视频显示器写入 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
寄存器中,其余部分补零。它代表“move zero 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
让我们从重写 DrawPixel 例程开始。目前有点乱!
使用 mul
指令毫无意义,因为它会不必要地破坏 EDX
寄存器。最好使用 imul
.
的变体
而不是使用 mov eax, 0
lea eax, [si]
加载 EAX
寄存器,为什么不简单地写 mov eax, esi
?
还有一个错误需要考虑。因为您在 24 位真彩色屏幕上工作,写入整个双字(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 例程不是一个好主意。一遍又一遍地计算像素的地址是浪费时间。下面我展示了一些专门针对这些任务的例程。
我添加了一些额外的更改:
- 您不应该让主程序负担寻址显存的技术细节。让图形例程检索 BytesPerScanLine 和 PhysBasePtr 值。
- 主程序应该处理 (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 $
我终于设法使用 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 loop"在屏幕上画一条青色的横线是这样的:
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
但是,这不起作用:它绘制了一条绿线。
当我尝试使用 draw/draw 像素代码再次绘制垂直线时,它也不起作用。它在各处绘制具有随机颜色的像素。以下是我如何使用 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
有什么办法可以解决这些问题?
绘制水平线
根据评论,我解决了绘制水平线的问题,方法是只向视频显示器写入 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
寄存器中,其余部分补零。它代表“move zero 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
让我们从重写 DrawPixel 例程开始。目前有点乱!
使用 mul
指令毫无意义,因为它会不必要地破坏 EDX
寄存器。最好使用 imul
.
而不是使用 mov eax, 0
lea eax, [si]
加载 EAX
寄存器,为什么不简单地写 mov eax, esi
?
还有一个错误需要考虑。因为您在 24 位真彩色屏幕上工作,写入整个双字(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 例程不是一个好主意。一遍又一遍地计算像素的地址是浪费时间。下面我展示了一些专门针对这些任务的例程。
我添加了一些额外的更改:
- 您不应该让主程序负担寻址显存的技术细节。让图形例程检索 BytesPerScanLine 和 PhysBasePtr 值。
- 主程序应该处理 (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 $