花了一段时间,但终于在16位图形中得到了1。 在这里,我清除屏幕并绘制一个像素:
mov ax, 0a000h
mov es, ax ; es - Extra Segment now points to the VGA location
mov ax, 0013h
int 10h
xor al, al
mov dx, 3c8h
out dx, al
inc dx
mov al, 63
out dx, al
out dx, al
out dx, al
mov ax, 0100h
int 21h
mov ax, 4c00h
int 21h
; draw single pixel :)
mov ah, 0ch;
mov al, 03h ; color
mov cx, 70 ; x co-ordinate
mov dx, 70 ; y co-ordinate
; mov bh,1 ; page #
int 10h
times 510-($-$$) db 0 ; PadZeros:
dw 0xaa55 ; MagicNumber
第2步:如何让它移动?
显然,这意味着交替擦除屏幕,更新并在循环中绘制像素。当然它会飞过屏幕,所以我猜你会访问内部时钟的毫秒,比较,然后当它大于某个常数时更新。
刚入门大会。我知道如何使用标签来制作一个伪函数,所以假设我可以在示例中继续并完成它。
我正在从nasm编译为bin然后在qemu中直接打开。注意我没有使用链接器,因此不需要使用.text或任何其他.bss 只是尝试从原始二进制文件中工作。
我还试图记录我在YouTube上学到的所有内容,如果有人对某些“低级机器代码入门”教程感兴趣: https://www.youtube.com/watch?v=XJdcoHjzvCo&list=PLJv7Sh0ZDUnpNnhNm3msK1C4K_8SzfMvO
如果其他人也在尝试编写内核,他们自己的操作系统,编译器或在组装中了解更多有关16位游戏图形的并行路径,请随时加入KAOS项目并帮助创建100 %视频记录操作系统: https://github.com/musicalglass/KAOS
KAOS无BS OS
答案 0 :(得分:2)
这是80x80平方的最粗糙的动画。
它的工作原理如下:
不确定你是否可以影响qemu的速度,就像dosbox中的周期数一样(以及它的VGA仿真有多精确)。
也许首先在DOSBOX中尝试这个(只需将二进制文件重命名为" test.com",下面的源代码也将作为COM文件使用),以了解图形编程的缺陷。
然后在dosbox中,您可以使用Ctrl + F11 / F12减去/添加机器周期(PC的速度),看看在非常慢的PC上使用这种原始算法时会发生什么。
在快速PC上,屏幕在光束返回第一行之前被清除,因此正方形在光束之前被绘制,并且一切看起来都很稳定。
但我的dosbox的默认设置很慢~286 / 386 PC,当光束开始在显示器上绘制第一行时,它仍将清除屏幕,因此它将绘制黑色空行。一旦代码开始绘制正方形,它将最终赶上光束,在约50行左右,因此可以看到底部~30行的正方形。
如果您将玩机器速度,您可以看到更多的人工制品,例如方块完全在光束后面绘制(对用户不可见),甚至闪烁(当整个绘图花费的时间比单帧刷新时长(1000/60)在60Hz监视器上= 16.6ms。
BITS 16
MOV ax,13h
INT 10h ; 320x200 256colour VGA mode
MOV ax,0a000h
MOV es,ax ; video RAM segment
XOR bx,bx ; square position = 0
MOV si,1 ; direction of movement
AnimateLoop:
CALL waitforRetrace ; destroys al, dx
; clear whole screen
XOR di,di
XOR eax,eax
MOV cx,320*200/4
REP STOSD
; draw 80x80 pixels square with color 3
MOV eax,0x03030303
MOV di,bx
MOV dx,80 ; height
drawSquareLoop:
MOV cx,80/4
REP STOSD ; draw 80 pixels (single line)
ADD di,320-80 ; next line address
DEC dx
JNZ drawSquareLoop
; move it left/right
ADD bx,si ; move it first
CMP bx,240
JB AnimateLoop ; 0..239 are OK
; too far on either side, reverse the movement
NEG si
ADD bx,si ; fix position to valid range
JMP AnimateLoop
waitforRetrace:
MOV dx,03dah
waitforRetraceEnd:
IN al,dx
AND al,08h
JNZ waitforRetraceEnd
waitforRetraceStart:
IN al,dx
AND al,08h
JZ waitforRetraceStart
RET
times 510-($-$$) db 0 ; PadZeros:
dw 0xaa55 ; MagicNumber
现在我看到INT 8
计时器中断实际上是BIOS提供的,所以我可以重写这个例子来使用那个时间来显示你的差异(VSYNC和计时器动画)...嗯...我'非常不情愿,因为计时器动画很糟糕(我的意思是,即使是VSYNC动画也必须使用计时器来弥补跳过的帧,但这对于简短的例子来说太复杂了,但基于计时器的动画本身就很糟糕了) 。我会给它〜最多10分钟,看看我是否能让它发挥作用......
好的,基于INT 08h
计时器的版本(如果您还因眨眼图像而容易发生癫痫发作,请不要注意):
BITS 16
MOV ax,13h
INT 10h
XOR ax,ax
; ds = 0 segment (dangerous, don't do this at home)
MOV ds,ax
MOV ax,0a000h
MOV es,ax ; video RAM segment
AnimateLoop:
; clear whole screen
XOR di,di
XOR eax,eax
MOV cx,320*200/4
REP STOSD
; draw square with color 3
MOV eax,0x03030303
; fetch position from BIOS timer-tick value
; (ticking every 55ms by default)
MOVZX di,byte [0x046C] ; di = 0..255 from [0:046C]
MOV dx,80 ; height
drawSquareLoop:
MOV cx,80/4
REP STOSD
ADD di,320-80 ; next line address
DEC dx
JNZ drawSquareLoop
JMP AnimateLoop
times 510-($-$$) db 0 ; PadZeros:
dw 0xaa55 ; MagicNumber
它有两个主要问题:
int 8
计时器在55ms内滴答,而大多数屏幕都是60Hz,因此需要16.6ms的刻度才能在60Hz上平滑,更高的刷新率需要更少。现在,正方形每隔3到4个显示帧移动+1个像素。
即使定时器为10ms,它仍然会像疯了一样闪烁,因为新位置的屏幕+绘图方块的擦除与显示光束不同步。
1.可以通过重新配置8253/8254 PIT来解决。
2.可以通过首先绘制到屏幕外缓冲区,然后将最终图像复制到真实VRAM(理想情况下采用VSYNC编辑方式来防止"撕裂")来解决。
这两个例子都很粗糙,基本上只是表明"清除屏幕+绘制新位置"真有效的东西。并且它甚至不足以实现基本的质量。
为了得到任何合理的东西,你使用了更复杂的逻辑,但这在很大程度上取决于你绘制和动画的内容以及如何。
使用VGA的通用方法是使用屏幕外缓冲区并在绘制完成后将其复制到VRAM(在64k字节复制上浪费机器周期......今天听起来很可笑,但在1990年它很重要。)
或使用VGA控制寄存器设置其中一个非正式的" x模式"并设置VGA内存布局以支持双/三缓冲方案,因此您可以将新帧直接绘制到VRAM中,但是将其绘制到隐藏部分中,当绘图完成后,您切换VRAM的显示部分以显示新准备的内容。这有助于避免64k拷贝,但是写入VRAM实际上非常慢,所以只有在几乎没有像素透支的情况下才值得努力。当你有很多透支时,它已经太慢了(没有机会获得60FPS),并且在普通RAM中将它拉到屏幕外使得它实际上更快,即使最终的64k拷贝到VRAM也是如此。