汇编中的画线算法
Line drawing algorithm in assembly
我正在尝试在汇编中创建一个画线算法(更具体地说是 Bresenham 的线算法)。在尝试实现该算法后,即使我几乎完全复制了 this Wikipedia page.
中的 plotLineLow() 函数,该算法仍无法正常工作
它应该是在两点之间画一条线,但是当我测试它时,它在 window 中的随机位置画点。我真的不知道会出什么问题,因为在汇编中调试很困难。
我正在使用 NASM 将程序转换为二进制,我 运行 QEMU 中的二进制文件。
[bits 16] ; 16-bit mode
[org 0x7c00] ; memory origin
section .text ; code segmant
global _start ; tells the kernal where to begin the program
_start: ; where to start the program
; main
call cls ; clears the screen
update: ; main loop
mov cx, 0x0101 ; line pos 1
mov bx, 0x0115 ; line pos 2
call line ; draws the line
jmp update ; jumps to the start of the loop
; functions
cls: ; function to clear the screen
mov ah, 0x00 ; set video mode
mov al, 0x03 ; text mode (80x25 16 colours)
int 0x10 ; BIOS interrupt
ret ; returns to where it was called
point: ; function to draw a dot at a certain point (dx)
mov bx, 0x00ff ; clears the bx register and sets color
mov cx, 0x0001 ; clears the cx register and sets print times
mov ah, 0x02 ; set cursor position
int 0x10 ; BIOS interrupt
mov ah, 0x09 ; write character
mov al, ' ' ; character to write
int 0x10 ; BIOS interrupt
ret ; returns to where it was called
line: ; function to draw a line at two points (bx, cx)
push cx ; saves cx for later
push bx ; saves bx for later
sub bh, ch ; gets the value of dx
mov [dx_L], bh ; puts it into dx
sub bl, cl ; gets the value of dy
mov [dy_L], bl ; puts it into dy
mov byte [yi_L], 1 ; puts 1 into yi (positive slope)
cmp byte [dy_L], 0 ; checks if the slope is negative
jl .negative_y ; jumps to the corresponding sub-label
jmp .after_negative_y ; if not, jump to after the if
.negative_y: ; if statement destination
mov byte [yi_L], -1 ; sets yi to -1 (negative slope)
neg byte [dy_L] ; makes dy negative as well
.after_negative_y: ; else statement destination
mov ah, [dy_L] ; moves dy_L into a temporary register
add ah, ah ; multiplies it by 2
sub ah, [dx_L] ; subtracts dx from that
mov [D_L], ah ; moves the value into D
pop bx ; pops bx to take a value off
mov [y_L], bh ; moves the variable into the output
pop cx ; pops the stack back into cx
mov ah, bh ; moves x0 into ah
mov al, ch ; moves x1 into al
.loop_x: ; loop to go through every x iteration
mov dh, ah ; moves the iteration count into dh
mov dl, [y_L] ; moves the y value into dl to be plotted
call point ; calls the point function
cmp byte [D_L], 0 ; compares d to 0
jg .greater_y ; if greater, jumps to the if statement
jmp .else_greater_y ; if less, jumps to the else statement
mov bh, [dy_L] ; moves dy into a temporary register
.greater_y: ; if label
mov bl, [yi_L] ; moves yi into a temporary register
add [y_L], bl ; increments y by the slope
sub bh, [dx_L] ; dy and dx
add bh, bh ; multiplies bh by 2
add [D_L], bh ; adds bh to D
jmp .after_greater_y ; jumps to after the if statement
.else_greater_y: ; else label
add bh, bh ; multiplies bh by 2
add [D_L], bh ; adds bh to D
.after_greater_y: ; after teh if statement
inc ah ; increments the loop variable
cmp ah, al ; checks to see if the loop should end
je .end_loop_x ; if it ended jump to the end of teh loop
jmp .loop_x ; if not, jump back to the start of the loop
.end_loop_x: ; place to send the program when the loop ends
ret ; returns to where it was called
section .data ; data segmant
dx_L: db 0 ; used for drawing lines
dy_L: db 0 ; ^
yi_L: db 0 ; ^
xi_L: db 0 ; ^
D_L: db 0 ; ^
y_L: db 0 ; ^
x_L: db 0 ; ^
section .text ; code segmant
; boot the OS
times 510-($-$$) db 0 ; fills up bootloader space with empty bytess
db 0x55, 0xaa ; defines the bootloader bytes
没看到
只是 80x25 文本 VGA 模式(模式 = 3),您在开始时设置 cls
那么如何渲染点?你应该设置你想要的视频模式假设 VGA 或 VESA/VBE 见上面的 link。
为什么要用VGA BIOS做点渲染?
那将是 slooooooooow,我不知道当没有 gfx 模式时它会做什么。您可以通过直接访问 VRAM(在 A000h
段)来渲染点。理想情况下使用 8/16/24/32 位视频模式,因为它们的像素与 BYTE 对齐...我最喜欢的是 320x200x256c(模式 = 19),因为它适合 64K 段,因此不需要分页,像素为字节。
如果您使用的是字符而不是像素,那么您仍然可以以相同的方式访问 VRAM,只需使用段 B800h
并且字符是 16 位(颜色和 ASCII)。
自 80386 以来,integer DDA 在 x86 CPU 上比 Bresenham 更快
我已经有大约 2 年没有在 NASM 中编写代码了,我在我的档案中找到的最接近行的是:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
line: pusha ;ax=x0,bx=x1,dl=y0,dh=y1,cl=col
push ax ;expecting ES = A000h
mov si,bx
sub si,ax
sub ah,ah
mov al,dl
mov bx,ax
mov al,dh
sub ax,bx
mov di,ax
mov ax,320
sub dh,dh
mul dx
pop bx
add ax,bx
mov bp,ax
mov ax,1
mov bx,320
cmp si,32768
jb .r0
neg si
neg ax
.r0: cmp di,32768
jb .r1
neg di
neg bx
.r1: cmp si,di
ja .r2
xchg ax,bx
xchg si,di
.r2: mov [.ct],si
.l0: mov [es:bp],cl
add bp,ax
sub dx,di
jnc .r3
add dx,si
add bp,bx
.r3: dec word [.ct]
jnz .l0
popa
ret
.ct: dw 0
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
所以你有东西要交叉检查(我花了一段时间才在我的档案中找到它,因为当时我用纹理编写了整个 3D 多边形引擎,所以我在 NASM 中没有太多的二维代码。 ..)
示例需要 320x200x256c VGA 视频模式
如果你写的是 DOS .com
文件,事情会简单一点:段寄存器都设置相同,你的 code/data 与它们的偏移量为 100。你可以以 ret
.
结尾
[BITS 16]
[ORG 100h]
[SEGMENT .text]
ret
正如@bitRAKE 和@PeterCoders 所指出的,以防您 运行 在 BOOT SECTOR 中 org
没问题。然而,在这种情况下,没有 OS 存在,因此如果您要对堆栈或 512 字节之外的任何其他内存块做更多的事情,您需要将堆栈指向已知的某个地方。 (不过,它确实开始有效,因为启用了中断。)
更重要的是,您需要 初始化 DS 以匹配您的 ORG 设置,因此 ds:org
达到线性地址 7C00。对于 org 0x7c00
,这意味着您希望 DS=0。否则像 mov [dx_L], bh
这样的指令会在某个未知位置使用内存。
[BITS 16]
[ORG 7C00h]
[SEGMENT .text]
mov ax,0000h
mov ds,ax ; DS=0 to match ORG
mov ss,ax ; if you set SS:SP at all, do it back-to-back
mov sp,7C00h ; so an interrupt can't fire half way through.
; here do your stuff
l0:
hlt ; save power
jmp l0
希望您使用 VC 或 NC 配置为 IDE NASM 而不是 compiling/linking 手动
这个可以在 MS-DOS 中使用,所以如果你是 运行ning BOT SECTOR,那你就不走运了。您仍然可以创建一个 *.com 可执行调试,一旦它在 dos 中工作,更改为 BOOT SECTOR...
参见Is there a way to link object files for DOS from Linux?了解如何设置MS-DOS Volkov commander自动编译和link你的asm源代码只需按下回车键...你也可以运行 只需将行添加到 vc.ext 行 ... 但我不想这样做,因此您可以先检查错误日志
调试方便
您可以尝试将 MS-DOS (DOSBox) 与古老的 Borland Turbo C/C++ 或 Pascal 一起使用,并使用它们的内联 asm { .... }
代码追踪并直接进入IDE。但是它使用 TASM(与 NASM 不同的语法)并且有一些限制...
遗憾的是,我从未在 x86 平台上看到任何像样的 IDE asm。我使用过的最好的 IDE asm 是 ZX Spectrum 上的 Herkules ... 甚至可以完成现代 C++ IDEs 没有的事情。
我正在尝试在汇编中创建一个画线算法(更具体地说是 Bresenham 的线算法)。在尝试实现该算法后,即使我几乎完全复制了 this Wikipedia page.
中的 plotLineLow() 函数,该算法仍无法正常工作它应该是在两点之间画一条线,但是当我测试它时,它在 window 中的随机位置画点。我真的不知道会出什么问题,因为在汇编中调试很困难。
我正在使用 NASM 将程序转换为二进制,我 运行 QEMU 中的二进制文件。
[bits 16] ; 16-bit mode
[org 0x7c00] ; memory origin
section .text ; code segmant
global _start ; tells the kernal where to begin the program
_start: ; where to start the program
; main
call cls ; clears the screen
update: ; main loop
mov cx, 0x0101 ; line pos 1
mov bx, 0x0115 ; line pos 2
call line ; draws the line
jmp update ; jumps to the start of the loop
; functions
cls: ; function to clear the screen
mov ah, 0x00 ; set video mode
mov al, 0x03 ; text mode (80x25 16 colours)
int 0x10 ; BIOS interrupt
ret ; returns to where it was called
point: ; function to draw a dot at a certain point (dx)
mov bx, 0x00ff ; clears the bx register and sets color
mov cx, 0x0001 ; clears the cx register and sets print times
mov ah, 0x02 ; set cursor position
int 0x10 ; BIOS interrupt
mov ah, 0x09 ; write character
mov al, ' ' ; character to write
int 0x10 ; BIOS interrupt
ret ; returns to where it was called
line: ; function to draw a line at two points (bx, cx)
push cx ; saves cx for later
push bx ; saves bx for later
sub bh, ch ; gets the value of dx
mov [dx_L], bh ; puts it into dx
sub bl, cl ; gets the value of dy
mov [dy_L], bl ; puts it into dy
mov byte [yi_L], 1 ; puts 1 into yi (positive slope)
cmp byte [dy_L], 0 ; checks if the slope is negative
jl .negative_y ; jumps to the corresponding sub-label
jmp .after_negative_y ; if not, jump to after the if
.negative_y: ; if statement destination
mov byte [yi_L], -1 ; sets yi to -1 (negative slope)
neg byte [dy_L] ; makes dy negative as well
.after_negative_y: ; else statement destination
mov ah, [dy_L] ; moves dy_L into a temporary register
add ah, ah ; multiplies it by 2
sub ah, [dx_L] ; subtracts dx from that
mov [D_L], ah ; moves the value into D
pop bx ; pops bx to take a value off
mov [y_L], bh ; moves the variable into the output
pop cx ; pops the stack back into cx
mov ah, bh ; moves x0 into ah
mov al, ch ; moves x1 into al
.loop_x: ; loop to go through every x iteration
mov dh, ah ; moves the iteration count into dh
mov dl, [y_L] ; moves the y value into dl to be plotted
call point ; calls the point function
cmp byte [D_L], 0 ; compares d to 0
jg .greater_y ; if greater, jumps to the if statement
jmp .else_greater_y ; if less, jumps to the else statement
mov bh, [dy_L] ; moves dy into a temporary register
.greater_y: ; if label
mov bl, [yi_L] ; moves yi into a temporary register
add [y_L], bl ; increments y by the slope
sub bh, [dx_L] ; dy and dx
add bh, bh ; multiplies bh by 2
add [D_L], bh ; adds bh to D
jmp .after_greater_y ; jumps to after the if statement
.else_greater_y: ; else label
add bh, bh ; multiplies bh by 2
add [D_L], bh ; adds bh to D
.after_greater_y: ; after teh if statement
inc ah ; increments the loop variable
cmp ah, al ; checks to see if the loop should end
je .end_loop_x ; if it ended jump to the end of teh loop
jmp .loop_x ; if not, jump back to the start of the loop
.end_loop_x: ; place to send the program when the loop ends
ret ; returns to where it was called
section .data ; data segmant
dx_L: db 0 ; used for drawing lines
dy_L: db 0 ; ^
yi_L: db 0 ; ^
xi_L: db 0 ; ^
D_L: db 0 ; ^
y_L: db 0 ; ^
x_L: db 0 ; ^
section .text ; code segmant
; boot the OS
times 510-($-$$) db 0 ; fills up bootloader space with empty bytess
db 0x55, 0xaa ; defines the bootloader bytes
没看到
只是 80x25 文本 VGA 模式(模式 = 3),您在开始时设置
cls
那么如何渲染点?你应该设置你想要的视频模式假设 VGA 或 VESA/VBE 见上面的 link。为什么要用VGA BIOS做点渲染?
那将是 slooooooooow,我不知道当没有 gfx 模式时它会做什么。您可以通过直接访问 VRAM(在
A000h
段)来渲染点。理想情况下使用 8/16/24/32 位视频模式,因为它们的像素与 BYTE 对齐...我最喜欢的是 320x200x256c(模式 = 19),因为它适合 64K 段,因此不需要分页,像素为字节。如果您使用的是字符而不是像素,那么您仍然可以以相同的方式访问 VRAM,只需使用段
B800h
并且字符是 16 位(颜色和 ASCII)。
自 80386 以来,integer DDA 在 x86 CPU 上比 Bresenham 更快
我已经有大约 2 年没有在 NASM 中编写代码了,我在我的档案中找到的最接近行的是:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; line: pusha ;ax=x0,bx=x1,dl=y0,dh=y1,cl=col push ax ;expecting ES = A000h mov si,bx sub si,ax sub ah,ah mov al,dl mov bx,ax mov al,dh sub ax,bx mov di,ax mov ax,320 sub dh,dh mul dx pop bx add ax,bx mov bp,ax mov ax,1 mov bx,320 cmp si,32768 jb .r0 neg si neg ax .r0: cmp di,32768 jb .r1 neg di neg bx .r1: cmp si,di ja .r2 xchg ax,bx xchg si,di .r2: mov [.ct],si .l0: mov [es:bp],cl add bp,ax sub dx,di jnc .r3 add dx,si add bp,bx .r3: dec word [.ct] jnz .l0 popa ret .ct: dw 0 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
所以你有东西要交叉检查(我花了一段时间才在我的档案中找到它,因为当时我用纹理编写了整个 3D 多边形引擎,所以我在 NASM 中没有太多的二维代码。 ..)
示例需要 320x200x256c VGA 视频模式
如果你写的是 DOS
结尾.com
文件,事情会简单一点:段寄存器都设置相同,你的 code/data 与它们的偏移量为 100。你可以以ret
.[BITS 16] [ORG 100h] [SEGMENT .text] ret
正如@bitRAKE 和@PeterCoders 所指出的,以防您 运行 在 BOOT SECTOR 中 org
没问题。然而,在这种情况下,没有 OS 存在,因此如果您要对堆栈或 512 字节之外的任何其他内存块做更多的事情,您需要将堆栈指向已知的某个地方。 (不过,它确实开始有效,因为启用了中断。)
更重要的是,您需要 初始化 DS 以匹配您的 ORG 设置,因此 ds:org
达到线性地址 7C00。对于 org 0x7c00
,这意味着您希望 DS=0。否则像 mov [dx_L], bh
这样的指令会在某个未知位置使用内存。
[BITS 16]
[ORG 7C00h]
[SEGMENT .text]
mov ax,0000h
mov ds,ax ; DS=0 to match ORG
mov ss,ax ; if you set SS:SP at all, do it back-to-back
mov sp,7C00h ; so an interrupt can't fire half way through.
; here do your stuff
l0:
hlt ; save power
jmp l0
希望您使用 VC 或 NC 配置为 IDE NASM 而不是 compiling/linking 手动
这个可以在 MS-DOS 中使用,所以如果你是 运行ning BOT SECTOR,那你就不走运了。您仍然可以创建一个 *.com 可执行调试,一旦它在 dos 中工作,更改为 BOOT SECTOR...
参见Is there a way to link object files for DOS from Linux?了解如何设置MS-DOS Volkov commander自动编译和link你的asm源代码只需按下回车键...你也可以运行 只需将行添加到 vc.ext 行 ... 但我不想这样做,因此您可以先检查错误日志
调试方便
您可以尝试将 MS-DOS (DOSBox) 与古老的 Borland Turbo C/C++ 或 Pascal 一起使用,并使用它们的内联
asm { .... }
代码追踪并直接进入IDE。但是它使用 TASM(与 NASM 不同的语法)并且有一些限制...遗憾的是,我从未在 x86 平台上看到任何像样的 IDE asm。我使用过的最好的 IDE asm 是 ZX Spectrum 上的 Herkules ... 甚至可以完成现代 C++ IDEs 没有的事情。