【问题标题】:How to map 320x200 pixels to VGA video memory in 16 bit assembly如何在 16 位汇编中将 320x200 像素映射到 VGA 视频内存
【发布时间】:2019-10-15 21:22:27
【问题描述】:

对于大学的奖励任务,我们需要在组装中制作游戏。我的想法是重新创建 mario 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

我希望它能够绘制 mario,但是当我使用 qemu 在 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 位寄存器没有足够的位,这就是它不起作用的原因。

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

【问题讨论】:

  • 那不是minimal reproducible example。请注意,如果您甚至没有获得浅蓝色背景,请不要专注于 mario 图像,因为您有更深层次的问题。另外,学习使用调试器。
  • 您使用的 320x200x8bpp VGA 模式是每像素 8 位而不是 16 位!!!此外,我没有看到任何目标段 0xA000,所以我希望这是您复制屏幕缓冲区的位置。看看我的这个老 MS-DOS 游戏What is the best way to move an object on the screen? 它可能对你的项目有点帮助......还有这个Graphics mode in assembly 8086
  • 如果我是对的,当我将寄存器 al 中的数据移动到缓冲区中的内存位置时,我使用的是每像素 8 位。这只是将其写入屏幕的双缓冲区,我确实将其复制到 0xA000 段。我是否还应该提供用于将缓冲区复制到视频内存以使其更清晰的代码?
  • @LazyL0tus 如果您确定它可以正常工作,则不需要……我能想到的可能还有更多问题。 1. 我对 qemu 和 debian 不熟悉,但您的模拟器可能期望某些 VGA 访问方式来刷新屏幕,例如访问某些 VGA 寄存器或调用特定的 BIOS 调用。 2. 另一件事是你的东西是可启动的,你的可执行文件真的是 16 位 x86 还是 32/64 位? IIRC 后者无法访问 BIOS 功能... 3. 你有 LCD 吗?有些无法正确处理旧的 VGA 低分辨率并且不同步... 4. 你的精灵真的加载到内存中了吗?
  • 尝试渲染一些模式而不是像pixel[y*320+x]=x+y; 来检查您的渲染工作......像这样的No Signal 这样可以排除 1,2,3 问题如果工作

标签: assembly graphics x86-16 bootloader


【解决方案1】:
bufferPos dw 0x7E0 

写入缓冲区会覆盖内核!

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

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

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

bufferPos dw 0x1000

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


用于检查图片是否停留在屏幕/缓冲区内的代码看起来非常虚假。我建议您暂时删除它。
处理 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

试试这个简化的代码,一旦它工作,重新考虑如何检查限制。不应不涉及DI中的地址的IMO。

对上述sn-p的小改进:

.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

【讨论】:

  • 这至少解决了一个问题,我的屏幕现在显示一个完整的浅蓝色屏幕!但是仍然没有加载任何精灵,所以我仍然需要研究一下
  • @LazyL0tus 请务必从您的代码中删除 jmp main。只保留jmp $。您在绘制图片后立即将其擦除。也许这就是为什么你只看到蓝色背景!
  • 删除jmp main 后,它仍然只显示蓝屏,这是预期的,因为重置缓冲区仅重置缓冲区中的所有像素,然后将精灵重绘到缓冲区。问题出在缓冲区,因为它要么没有绘制精灵,要么没有正确复制
猜你喜欢
  • 1970-01-01
  • 2012-08-12
  • 2016-11-11
  • 2017-04-03
  • 2020-08-27
  • 2019-08-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多