如何在 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
为了大学的奖励作业,我们需要在汇编中制作一个游戏。我的想法是重新创建马里奥 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