要在屏幕上平滑移动,需要与显示模式的“垂直同步”同步。
屏幕上的图像是随着时间构建的,它不是即时的,例如当您的显示模式为 60Hz 时,这意味着大约 1000/60 = 16.666ms 生成一个图像,该时间段的一部分正在模拟“将光线返回到左上角”期间,称为“垂直回扫”,现代 LCD 并不真正需要它,但 CRT 管显示器需要它并且仍在使用。
垂直回溯通常需要 5% 的时间(0.83 毫秒),这是更新视频 RAM 内容的最佳时间,因为它可以防止任何“撕裂”发生。但是只有这么短的数据时间段通常是不够的(除非你使用一些双/三缓冲方案),所以在某些情况下,屏幕上的绘制也确实发生在垂直回扫之外,精确地定时在之前发生或在显示光线后面(从左上角逐行从上到下,每一行从左到右逐个像素,当光线返回左侧并移动一条线时,水平回扫周期非常短down ...这让我现在真的想知道,为什么旧的CRT显示器没有使用从左到右+从右到左的奇/偶线信号编码,从而节省了水平回扫时间。
如果您不这样做,并且您只是简单地覆盖显示数据任何时间,您可能会在旧图像已经部分显示时将新的动画阶段放在光线之前,因此该点上方的显示将显示旧阶段,之后将显示新阶段。这确实会产生“撕裂”,在一些垂直线横向移动时尤其明显。
因此,如果您想要平滑的运动动画,并且我猜您正在为 x86 DOS 编码(来自int 1Ah),您应该与 VSYNC 同步(搜索 VGA“等待回溯”示例)。
但这会引发另一波蠕虫,因为现代 PC 显示器可能以不同的刷新率运行,因此设计为在 60Hz 下具有正确速度的游戏将在 120Hz 显示器上运行两倍的速度(如果它足够快以完成所有大约 8 毫秒的游戏代码和绘图)。
由于这是学校作业,你可能在 dosbox 中运行它,我认为假设 60Hz 显示模式和 VSYNC 同步是合理的。
(当然,dosbox 窗口中的输出可能仍会被“撕裂”,因为模拟显示可能不会与窗口内容更新与实际显示刷新率同步,但是当您将 dosbox 切换到全屏时,它通常会使用真正的 VGA 模式,例如流行的 320x200 256 色“13h 模式”有 60Hz,如果你给它足够的 CPU 周期,大多数 LCD 都会正确显示,并使模拟的 DOS 应用程序运行“流畅”)
关于int 1Ah ... 那是 DOS 系统时钟,默认情况下每秒大约滴答 18.2 次,即使对于缓慢的 60Hz 刷新率也不够快,更不用说现代 100+ Hz 显示模式.
为了让这堵文字墙更“实用”,以及旧时代游戏主循环的示例(以防重绘速度足够快,如俄罗斯方块/等):
- 做其他事情(在接下来的步骤中没有提到),特别是如果它需要很长时间
- 等待回溯
- 扫描输入设备以获取玩家输入
- 根据输入更新世界(必须超快)
- 绘制屏幕(希望显示器仍处于垂直回扫状态,或在您写入 VRAM 之后,因此通常从上到下绘制变化)
或者,如果游戏速度不足以在光线前/后绘制,但在 60Hz 显示(全屏重绘)上足够快以适应 16.6 毫秒,您可以使用一些允许双/三缓冲方案的视频模式:
- 扫描输入设备
- 根据输入更新世界(可能需要一些时间)
- 等待回溯和翻转缓冲区(因此在屏幕外缓冲区中准备的旧图像现在将在屏幕上,并且您有新的未使用的屏幕外缓冲区)
- 将世界绘制到屏幕外缓冲区(可能需要一些时间,并且可以按任意顺序绘制)
- 做其他事情
如果游戏的速度甚至不足以适应 60Hz,在 DOS 中,您将不得不重新编程计时器芯片以使其滴答速度快于 18.2 倍,并使用它来计算前一个循环中错过的垂直回扫周期,所以您知道您的世界更新何时应该提前更新 2 帧或更多帧(跳过一些帧),因为之前的绘制太慢了。
关于“效率不高”:
在现代多任务操作系统中,您可以调用某种delay(ms) OS 方法,这可以保证您的代码在至少指定的时间内“关闭”,同时操作系统可以运行其他线程/进程或只是在特定平台“空闲”(针对特定芯片组/BIOS 进行优化以尽可能节省电力)。
这在游戏中很少使用,因为不能保证延迟,如果操作系统让你的线程睡眠时间过长,你会错过显示回溯并且动画会不稳定,但在性能非常好的游戏的情况下,这是一个选项(特别是在双/三缓冲方案中,图形驱动程序允许在实际回溯之前对屏幕翻转进行编程,因此当游戏意外休眠时不会造成灾难)。
在 DOS 中,CPU 一直在执行指令(除非您为了支持特定的平台/芯片组节能方法而费尽心思,否则实际上使用 NOP 循环通常非常接近它,因为许多 x86 CPU 会检测到这种模式并降低功耗)。无论您是循环直到某些寄存器减为零,还是直到 VGA 芯片组报告 VSYNC 位打开,或者直到计时器计数器“滴答”,您都不能在任何通常可用的 DOS 中“关闭”CPU方式。
什么是动画的延迟时间有点低效;由于它们的频率不同,它不会在不同的 CPU 上保持相同的速度(就像 VSYNC 等待在不同的显示刷新率下不会有相同的速度)。考虑到这一点,使用int 1Ah定时器有其优点,虽然在VSYNCed平滑度方面有所欠缺,但它会在各种机器上保持速度恒定。
此外,在进行“nop”延迟时,在某些情况下,您可以做一些更有意义的事情,比如为游戏计算一些东西,但是一旦你完成了你的框架,就没有太多意义去生产另一个框架,例如运行FPS 射击游戏在 300FPS 下开启 VSYNC 几乎是一样的,因为它在显示模式的频率下运行,因为 VSYNC 会使所有高于显示速率的图像都被丢弃并且永远不会显示给用户,这就是为什么这类游戏在VSYNC ON 选项通常将 FPS 限制为显示速率。 capped run 之间的微小差异可能在于读取用户输入的周期,以及物理模拟的频率,如果物理不够稳定以产生相同的结果,300 FPS 的体验可能会有所不同。实际上有一些旧的 DOS 游戏在快速机器上无法运行,因为一旦频率太高,它们的“物理”就会停止移动任何东西,因为它们确实使用了动态时间增量“步长”,它太小而无法真正移动对象(想象每个物理步骤的对象速度为 0.5 像素,计算为 mov ax,time_tick_delta shr ax,1 -> 当在 time_tick_delta 仅为 1 的机器上运行时,运动完全停止......如果原始程序员在他的最好的 PC 一直在 8+ 以上,很容易在未来 5 年内无法预见到这样的问题。
因此,通过这种方式,您的延迟“效率不高”,因为您本可以做一些更有用的事情,但是如果您的游戏已经以全显示速率刷新率运行,那么没有什么更有意义的事情可做,至少我会一点头绪都没有。
专业的 DOS 游戏通常需要不止一个显示帧来更新所有内容(因为游戏太复杂了),所以他们不得不重新编程计时器来计算他们错过了多少帧,并跳过这么多帧来捕捉与下一个。这可能最接近int 1Ah 方法。但它通常仍然涉及额外的二分法,即等待垂直回扫,以避免撕裂。因此,您结束了这两项工作,并拥有更复杂的游戏循环逻辑来评估必须跳过多少帧才能保持正确的游戏速度。