当垂直回扫位被清除时,VGA 卡是否读取像素缓冲区?

Do VGA cards read in the pixel buffer when the vertical retrace bit is cleared?

我正在开发一款使用视频模式 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 才是安全的

还是我完全错了?

VGA 卡读入时没有单独的缓冲区。 (请记住,当 VGA 刚出现时,即使是 32kiB 的 DRAM 也很昂贵。此外,内存带宽很低。一些视频卡过去使用 dual-ported RAM,因此从 CPU 访问不会干扰扫描输出;它可能是 read/written 在一个端口上,而 CRTC / RAMDAC 正在读取像素数据。)

vertical-blanking interval 期间,视频卡根本不读取或写入视频 RAM;它的存在使 CRT 可以将电子束偏转板的电压改变回屏幕的顶部,而无需在屏幕上画一条线。然后 VGA 硬件开始读取视频 RAM,以便再次扫描下一帧。

(现代硬件当然不驱动 CRT,但使用 "blanking interval" 按顺序读取 VRAM 仍然是一回事)。


等待该位被设置然后清除有助于使您的代码有可能在消隐间隔开始时 运行 开始,而不是可能在消隐间隔结束时开始。

如果您修改视频 RAM 的代码运行得足够快,它会在硬件再次开始读取之前完成,所以您不会被撕裂。 (实际上,因为您是按扫描输出顺序编写屏幕,它只需要足够快以保持领先于光栅扫描,所以屏幕输出不会通过memcpy 并在帧后面显示一些 "old" 像素。)

在旧硬件上,rep movsw 不够快,无法在 VBI 期间复制整个数据帧,尤其是在通过 ISA 总线写入内存映射 I/O 时。相反,您通常 double-buffer 通过更改 VGA 基址以指向 VBI 期间已绘制的帧。所以你在一个缓冲区中绘制,而另一个被扫描出来,给你一个完整的帧间隔来更新它,而不仅仅是 VBI。


rep movsw 在实际的现代 CPU 上运行 非常 快(例如,如果您以实模式启动现代 PC)。如果 VRAM 被映射为 WC(又名 USWC:不可缓存的推测写入组合),那么 rep movsw 将一次复制 16 或 32 个字节(快速字符串模式甚至 ERMSB(增强型 Mov/Stos B)),受益于写组合缓冲区。 (WC 内存上的常规存储就像普通 WB(回写)内存上的 NT 存储)。 Intel 勘误表(like IvyBridge BU2)表明 WC 内存上的 REP MOVS 确实以这种方式工作:如果您将一个页面从 WC 跨入 UC 内存,一些到 UC 内存的存储可能会使用宽快速字符串存储而不是单独的 16 -bit 存储 rep movsw。这意味着 CPU 必须对 WC 内存进行广泛存储。

如果源数据在 L1d 或 L2 缓存中很热,因为您刚刚写入它,并且目标是 USWC 视频 RAM,那么使用 rep movsw 块化它应该可以在 VBI 期间轻松完成。如果它被映射为 UC(当 WC 是一个相对较新的功能时,这曾经是 BIOS 选项,至少在 Pentium III / 早期的 K8 板上),那么现代多 GHz PC 可能仍然足够快。

(顺便说一句,repne cmpsb 仍然很慢,但是 rep movs/stos 很快)。

顺便说一句,即使集成显卡 "video RAM" 仍然只是常规 DRAM 的一部分,它也将是 UC(不可缓存)或 WC(不可缓存写入组合)。当然,现在大多数 VGA 接口都是仿真的。不过,VGA 内存可能是您的图形硬件使用的真正帧缓冲区(如果 运行 在裸机上,而不是 DOSBOX 或其他模拟器)。

无论如何,在低分辨率的现代硬件上,您可能只检查被清除的位就可以了,因为与刷新率相比,副本运行得如此之快,以至于出现任何撕裂的可能性几乎为零。或者也许第一个或两个像素可能来自旧帧。


在 DOSBOX 上模拟具有实际时钟速度的真实旧 PC:

rep movsw 速度不够快,无法在 VBI 期间复制帧,除非您将 DOSBOX 设置为以 ~70MHz 或 "dynamic / max" 速度模拟 486。