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

My *_*ug. 2 assembly vga x86-16

我正在开发一款使用视频模式 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
Run Code Online (Sandbox Code Playgroud)

这是等待新设置垂直回扫位的修改程序:

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 
Run Code Online (Sandbox Code Playgroud)

它并不完全完美。仍然有一点抖动,尤其是当精灵后面的背景向上或向下滚动时,但不再有什么问题了。

我的问题是,为什么这有效?

我的猜测是,当垂直回扫位被设置时,像素已经被读入VGA卡的内存中,并且当前正在写入已经加载的像素。然而,当垂直回扫位被清除时,它正在将像素从A000:0000加载到本地存储器中。它使用 DMA 来实现这一点,对吗?

因此,只有当 VGA 卡正在写入像素(位设置)并且不加载像素(位清除)时,写入 A000:0000 才是安全的

还是我完全错了?

Pet*_*des 7

VGA 卡没有读取数据的单独缓冲区。(请记住,当 VGA 还很新时,即使是 32kiB 的 DRAM 也很昂贵。而且,内存带宽很低。一些显卡过去使用双端口 RAM,因此来自 CPU 的访问不会干扰扫描输出;它可以被读取/当 CRTC / RAMDAC 读取像素数据时,写入一个端口。)

垂直消隐间隔期间,显卡根本不读取或写入视频 RAM;它的存在是为了让 CRT 可以将电子束偏转板的电压改变回屏幕顶部,而无需在屏幕上画一条线。然后 VGA 硬件开始读取视频 RAM,以便再次扫描输出下一帧。

(现代硬件当然不驱动 CRT,但按“消隐间隔”顺序读取 VRAM 仍然是一件事)。


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

如果修改视频 RAM 的代码运行得足够快,它会在硬件再次开始读取之前完成,因此不会出现撕裂现象。(实际上,因为您按照扫描输出顺序写入屏幕,所以它只需要足够快以保持在光栅扫描之前,因此屏幕输出不会通过 memcpy 并稍后显示一些“旧”像素框架。)

在旧硬件上,rep movsw在 VBI 期间复制整个数据帧的速度不够快,尤其是在通过 ISA 总线写入内存映射 I/O 时。相反,您通常会通过更改 VGA 基址以在 VBI 期间指向已绘制的帧来进行双缓冲。因此,您在一个缓冲区中进行绘制,同时扫描另一个缓冲区,从而为您提供了整个帧间隔来更新它,而不仅仅是 VBI。


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

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

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

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

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


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

@Ped7G 说 rep movsw在 VBI 期间复制帧的速度不够快,除非您将 DOSBOX 设置为以 ~70MHz 或“动态/最大”速度模拟 486。

  • REP MOVSW 不需要足够快才能在 VBI 期间完成,它需要足够快以免让光栅赶上它。 (3认同)
  • 我不认为这个答案有什么问题,也许最后一部分是不必要的,因为我们正在谈论 80 年代的硬件和相关的 CPU。如今,VGA 接口已被模拟,事情很快就会变得复杂(例如,英特尔显卡具有缓存感知 IIRC)。然而,等待新的垂直同步比冒险更好。 (2认同)