我正在使用视频模式13h的DOS游戏。
我一直遇到屏幕撕裂的问题,但直到今天我一直忽视这个问题。我认为这将是一个挑战,因为它会涉及延迟像素写入一段精确的时间。但它实际上是一个非常简单的修复。
您需要做的就是等待VGA状态字节的垂直回扫位(第3位),在颜色模式下的端口0x3da处可用,以便重新设置。
所以我只需要修改这个旧程序,它将我的帧缓冲区写入从A000:0000开始的VGA像素缓冲区:
WRITE_FRAME PROC
;WRITES ALL 64,000 PIXELS (32,000 WORDS) IN THE FRAME BUFFER TO VIDEO MEMORY
push es
push di
push ds
push si
push cx
mov cx, frame
mov ds, cx
xor si, si ;ds:si -> frame buffer (source)
mov cx, vidMemSeg
mov es, cx
xor di, di ;es:di -> video memory (destination)
mov cx, (scrArea)/2 ;writing 32,000 words of pixels
rep movsw ;write the frame
pop cx
pop si
pop ds
pop di
pop es
ret
WRITE_FRAME ENDP
这里是等待垂直回扫位新设置的修改过程:
WRITE_FRAME PROC
;WRITES ALL 64,000 PIXELS (32,000 WORDS) IN THE FRAME BUFFER TO VIDEO MEMORY
push es
push di
push ds
push si
push ax
push cx
push dx
mov cx, frame
mov ds, cx
xor si, si ;ds:si -> frame buffer (source)
mov cx, vidMemSeg
mov es, cx
xor di, di ;es:di -> video memory (destination)
mov cx, (scrArea)/2 ;writing 32,000 words of pixels
;If vert. retrace bit is set, wait for it to clear
mov dx, 3dah ;dx <- VGA status register
VRET_SET:
in al, dx ;al <- status byte
and al, 8 ;is bit 3 (vertical retrace bit) set
jnz VRET_SET ;If so, wait for it to clear
VRET_CLR: ;When it's cleared, wait for it to be set
in al, dx
and al, 8
jz VRET_CLR ;loop back till vert. retrace bit is newly set
rep movsw ;write the frame
pop dx
pop cx
pop ax
pop si
pop ds
pop di
pop es
ret
WRITE_FRAME ENDP
它并不完美。还有一点点抖动,特别是当精灵背后的背景向上或向下滚动时,再看看它不会有什么伤害。
我的问题是,为什么这有效?
我的猜测是,当设置垂直回扫位时,像素已经被读入VGA卡的存储器,并且它当前正在写入已经加载的像素。但是,当垂直回扫位清零时,它正在将像素从A000:0000加载到本地存储器中。它使用DMA,对吗?
因此,当VGA卡写入像素(位设置)而不加载像素(位清除)时,写入A000:0000是唯一安全的
还是我完全错了?
答案 0 :(得分:4)
VGA卡没有单独的缓冲区。 (请记住,当VGA是新的时,甚至32kiB的DRAM也很昂贵。而且,内存带宽很低。有些视频卡过去常常使用dual-ported RAM,因此CPU的访问不会干扰扫描;它可以在CRTC / RAMDAC读取像素数据时,可以在一个端口上读/写。)
在vertical-blanking interval期间,视频卡根本无法读取或写入视频RAM;它存在,因此CRT可以将电子束偏转板的电压改变回屏幕顶部,而不会在屏幕上划线。然后VGA硬件再次开始读取视频RAM,以便下一次扫描出下一帧。
(现代硬件当然不会驱动CRT,但是按照&#34;消隐间隔&#34;顺序读取VRAM仍然是一件事。)
等待设置然后清除有助于使代码在消隐间隔开始时开始运行,而不是可能接近消隐间隔的结束。
如果修改视频RAM的代码运行得足够快,那么在硬件再次开始阅读之前就已经完成了,所以你不会撕裂。 (实际上,因为您以扫描输出顺序编写屏幕,只需要足够快以保持光栅扫描之前,因此屏幕输出无法通过memcpy并在帧的后面显示一些&#34; old&#34;像素。)
在旧硬件上,rep movsw
速度不够快,无法在VBI期间复制整个数据帧,尤其是在通过ISA总线写入内存映射I / O时。相反,您通常会在VBI期间通过将VGA基准更改为指向已绘制的帧来double-buffer。所以你在一个缓冲区中绘制而另一个缓冲区被扫描出来,给你一个整个帧间隔来更新它,而不仅仅是VBI。
rep movsw
在实际的现代CPU上快速运行非常(例如,如果您以实模式启动现代PC)。如果VRAM映射为WC(又名USWC:不可缓存的推测性写入组合),那么rep movsw
将一次复制16或32个字节(快速字符串模式或甚至ERMSB(增强型Rep Mov / Stos B)),受益于写入组合缓冲区。 (WC内存上的常规存储就像普通WB(写回)内存上的NT存储一样)。英特尔勘误表(like IvyBridge BU2)表明WC内存上的REP MOVS确实以这种方式工作:如果你将一个页面从WC跨越到UC内存,一些商店到UC内存可能会发生宽带快速字符串存储而不是单独存储rep movsw
的比特商店。这意味着CPU必须在WC内存中进行广泛存储。
如果源数据在L1d或L2缓存中很热,因为您刚刚编写它,并且目标是USWC视频RAM,那么在VBI期间应该很容易用rep movsw
进行blitting。如果它被映射为UC(当WC是一个相对较新的功能时,至少在奔腾III /早期的K8板上,这曾经是BIOS选项),那么现代的多GHz PC可能仍然很快。 / p>
(BTW,repne cmpsb
仍然很慢,但是rep movs / stos很快。)
无论如何,在低rez的现代硬件上,你可能只检查被清除的位是否正常,因为与刷新率相比,复制运行速度非常快,因此得到任何撕裂。或者也许第一个或第一个像素可能来自旧框架。
在DOSBOX上模拟具有真实时钟速度的真正旧PC :
@Ped7G says rep movsw
在VBI期间复制帧速度不够快,除非您将DOSBOX设置为在~70MHz处模拟486,或者&#34;动态/最大&#34 ;速度。