如何在 16 位汇编中将 320x200 像素映射到 VGA 显存

How to map 320x200 pixels to VGA video memory in 16 bit assembly

为了大学的奖励作业,我们需要在汇编中制作一个游戏。我的想法是重新创建马里奥 NES 的第一级,但是在编码时我无法使我正在使用的模拟器 (qemu) 在屏幕上显示像素。

我正在 Linux Debian 发行版上编写代码,并使用 NASM 编译我的代码。我正在使用这个引导加载程序:( https://github.com/Pusty/realmode-assembly/blob/master/part6/bootloader.asm ) 使游戏可以启动并自己编写内核。
为了绘制到屏幕上,我使用 320x200 VGA 模式(int 0x10、0x13)并使用双缓冲方法写入屏幕以使其 运行 更流畅。

我目前正在使用这段代码绘制双缓冲区

; si = image to draw, ax = x location offset of image, bx = y location offset of image
drawBuffer:
pusha
push es

xor di, di
imul di, bx, 320     ; translate the y offset to y position in buffer
add di, ax           ; adds the x offset to buffer to obtain (x,y) of image in buffer
mov es, word [bufferPos]  ; moves the address of the buffer to es
mov bp, ax           ; saves the x offset for later use

; image is in binary and first two words contain width and height of image
xor ax, ax
lodsb
mov cx, ax           ; first byte of img is the width of the image
lodsb
mov dx, ax           ; second byte of img is height of the image

    .forY:
     ; check if within screen box
     mov bx, di
     add bx, cx      ; adds length of image to offset to get top right pixel
     cmp bx, 0       ; if top right pixel is less than 0 it is outside top screen
     jl .skipX       ; if less then 0 skip the x pixels row
     sub bx, cx
     sub bx, 320*200 ; subtracts total screen pixels off of image to get left botom pixel
      jge .skipX     ; if greater then 0 it is outside of bottom of screen
      xor bx, bx

      .forX:
           ; check if within left and right of screen
           cmp bx, 320
           jg .skip
           sub bx, bp
           cmp bx, 0
           jl .skip
           ; if more bx (x position) is more >320 or <0 it is outside of screen           

           mov al, byte [si + bx]   ; moves the color of the next pixel to al
           test al, al              ; if al is 0x0 it is a 'transparant' pixel
           jz .skip
           mov byte [es:di], al     ; move the pixel to the buffer
           .skip:
           inc bx                   ; move to next pixel in image
           cmp bx, cx               ; check if all pixels in x row of image are done
           jl .forX                 ; if not all images are done repeat forX
     .skipX:
     add di, 320             ; if forX is done move to next y position
     add si, cx              ; moves to the next y row in image
     dec dx                  ; decrements yloop counter
     jnz .forY
pop es
popa
ret


bufferPos dw 0x7E0      ; address at which to store the second buffer

此代码存储在名为 buffer.s 的单独文件中,并使用

包含在 kernel.s 中
%include buffer.s

内核位于 0x8000,它所做的只是包含要使用 x 和 y 偏移绘制的图像,并调用双缓冲区

org 0x8000
bits 16

setup:
   mov ah, 0
   mov al, 0x13
   int 0x10

main:
   call resetBuffer     ; method to set all pixels in buffer to light blue
   mov ax, 10           ; x position of image
   mov bx, 100          ; y position of image
   mov si, [mario]      ; moves the image location to si
   call drawBuffer

   call writeVideoMem   ; simply stores all bytes in the buffer to the videoMemory
   jmp main

jmp $

mario
   dw mario_0

mario_0 incbin "images/mario_right_0.bin"

times (512*16)-($-$$) db 0

我希望它能画马里奥,但是当我 运行 它在 qemu 上使用

nasm -fbin bootloader.s -o bootloader.bin
nasm -fbin kernel.s -o kernel.bin
cat bootloader.bin kernel.bin > game.bin

qemu-system-i386 game.bin

它只是显示黑屏,没有绘制任何东西。

我唯一能想到的是访问所有像素 16 位寄存器没有足够的位,这就是它不起作用的原因。

PS。如果需要更多信息,我很乐意提供

bufferPos dw 0x7E0 

写入缓冲区会覆盖内核!

您使用的引导加载程序将内核存储在线性地址 0x00008000 处。对于加载,它设置 ES:BX == 0x0000:0x8000。并且您已正确地将 ORG 0x8000 放在内核源代码的顶部。

但是 您已经在线性地址 0x00007E00 (ES * 16) 处定义了双缓冲区。您已设置 ES == 0x07E0.

一个适合 320x200x8 视频模式的缓冲区应该有 64000 字节。因此,例如将双缓冲区设置为线性地址 0x00010000 需要你写:

bufferPos dw 0x1000

这为 32KB 内核留出了空间。目前您只使用了 8KB 的内核空间。


您用来检查图片是否在 screen/buffer 范围内的代码看起来很假。我建议你暂时删除它。
处理 1 条水平线的内部循环忘记递增 DI 寄存器!

.forY:
    PUSH DI              <******************
    xor bx, bx
    .forX:
        mov al, byte [si + bx]
        test al, al           ; if al is 0x0 it is a 'transparant' pixel
        jz .skip
        mov byte [es:di], al  ; move the pixel to the buffer
      .skip:
        INC DI           <******************
        inc bx                ; move to next pixel in image
        cmp bx, cx            ; check if all pixels in row are done
        jl .forX                 ; if not all images are done repeat forX
    .skipX:
    POP DI               <******************
    add di, 320             ; if forX is done move to next y position
    add si, cx              ; moves to the next y row in image
    dec dx                  ; decrements yloop counter
    jnz .forY

试试这个简化的代码,一旦成功,重新考虑如何检查限制。 IMO 认为 不涉及 DI.

中的地址

对上述代码片段的一个小改进:

.forY:
    xor bx, bx
    .forX:
        mov al, byte [si + bx]
        test al, al           ; if al is 0x0 it is a 'transparant' pixel
        jz .skip
        mov byte [es:di+BX], al  ; move the pixel to the buffer
      .skip:
        inc bx                ; move to next pixel in image
        cmp bx, cx            ; check if all pixels in row are done
        jl .forX                 ; if not all images are done repeat forX
    .skipX:
    add di, 320             ; if forX is done move to next y position
    add si, cx              ; moves to the next y row in image
    dec dx                  ; decrements yloop counter
    jnz .forY