assembly - 如何在 16 位汇编中将 320x200 像素映射到 VGA 视频内存
问题描述
对于大学的奖金作业,我们需要在组装中制作游戏。我的想法是重新创建 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
我期望它画马里奥,但是当我在 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 的内核。
用于检查图片是否停留在屏幕/缓冲区内的代码看起来非常虚假。我建议你暂时删除它。
处理 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
推荐阅读
- python - 如何在谷歌应用脚本中更新 mailgun 路由
- php - “无法读取数据,因为它的格式不正确” IOS 使用 swift 和 MySQL 数据库的登录系统
- ansible - 在 k8s_facts 模块上没有观察到输出
- xslt - Report_Entry(worker) 的第一行需要默认为“YES”。工人返回“NO”的所有其他行
- c - 我怎样才能继续为某个点添加某个值?
- javascript - Javascript 在比较整数或布尔数组时更快吗?
- javascript - 如何使用字符串变量作为名称提取数组的数据?
- angular - Angular 没有显示带有延迟加载的组件
- xslt - XPATH 从前一个元素复制节点值
- asp.net - 在 ASP.NET MVC 的单个请求生命周期中处理基于路由的 cookie?