汇编中的画线算法

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
  1. 没看到

    只是 80x25 文本 VGA 模式(模式 = 3),您在开始时设置 cls 那么如何渲染点?你应该设置你想要的视频模式假设 VGA 或 VESA/VBE 见上面的 link。

  2. 为什么要用VGA BIOS做点渲染?

    那将是 slooooooooow,我不知道当没有 gfx 模式时它会做什么。您可以通过直接访问 VRAM(在 A000h 段)来渲染点。理想情况下使用 8/16/24/32 位视频模式,因为它们的像素与 BYTE 对齐...我最喜欢的是 320x200x256c(模式 = 19),因为它适合 64K 段,因此不需要分页,像素为字节。

    如果您使用的是字符而不是像素,那么您仍然可以以相同的方式访问 VRAM,只需使用段 B800h 并且字符是 16 位(颜色和 ASCII)。

  3. 自 80386

    以来,
  4. 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 视频模式

  5. 如果你写的是 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  
  1. 希望您使用 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 行 ... 但我不想这样做,因此您可以先检查错误日志

  2. 调试方便

    您可以尝试将 MS-DOS (DOSBox) 与古老的 Borland Turbo C/C++ 或 Pascal 一起使用,并使用它们的内联 asm { .... } 代码追踪并直接进入IDE。但是它使用 TASM(与 NASM 不同的语法)并且有一些限制...

    遗憾的是,我从未在 x86 平台上看到任何像样的 IDE asm。我使用过的最好的 IDE asm 是 ZX Spectrum 上的 Herkules ... 甚至可以完成现代 C++ IDEs 没有的事情。