在8086 dos汇编中获取精确时间

Get precise time in 8086 dos assembly

我正在使用视频模式 13h 为 ms-dos 汇编制作乒乓球游戏。我需要在每帧之间添加 1/60 秒的延迟。我尝试使用 AH=0 的中断 1A 来获取系统时间,但这每秒仅更改 18.2 次(因此我的帧速率将为 18.2 fps)。我怎样才能更精确地测量时间?

gameloop:

mov     ah, 0
int     1ah         ;get system time
cmp     dx, prevt
jz      gameloop
mov     prevt, dx

实际上,您可能希望帧之间有 1/60 秒的延迟(即,一帧结束到下一帧开始)。

可能想要的是从一帧开始到下一帧开始的延迟。或者,更简单地说每秒 60 帧 (fps)。


如果 tick 周期小于该值,实际上 不会将您限制在较低的帧速率,除非它是导致渲染代码 运行 的实际 tick(例如有中断)。

您可以使用一些技巧,通过使用自适应等待循环来获得更高的帧速率。

我接触(非 ARM)程序集已经有一段时间了,所以我将用伪代码进行解释。我还将使用 20/秒的滴答周期(请参阅附录了解如何处理其他速率)来简化此处的数学运算。

您需要的是一个尽可能快地 渲染帧的循环 但是,在该循环的某处,包含等待循环的代码:

reg1 = busyTime + 1
while reg1 != 0:
    reg1 = reg1 - 1

现在唯一的技巧是弄清楚 busyTime 的值应该是多少,以获得正确的帧速率。

由于您可用的基准时间和当前时间,并且您实际上可以计算您渲染的帧,您可以计算出当前帧率:

tps = 20
fpsRate = frameCount * tps / (currentTime - baseTime)

tps 或 ticks/second 变量允许您以秒为单位工作,因为当前和基准时间以滴答为单位,以秒为单位更容易思考。

As an aside, I'm actually using this exact calculation in my current work task to figure out the FPS rates we can get out of our embedded system (~74fps with vsync, which isn't too bad).

所以,调整 busyTime:

就很简单了
  • 如果fpsRate小于六十,减少busyTime但不低于零。
  • 如果 fpsRate 大于 60,增加 busyTime 但不允许溢出。
  • 如果 fpsRate 刚好是六十,请不要理会 busyTime

这样,繁忙的循环将自动调整到正确的持续时间以保持每秒所需的帧数。

我在计算时发现的唯一棘手的一点是,在 50fps 下,我的数字很快就变得相当大。

因为我只对最后十秒左右感兴趣,所以我可以即时调整值以避免溢出。一个简单的调整可以是:

// Once we have eleven seconds of data,
//   strip away the first second.

while currentTime - baseTime >= tps * 11:
    // Get rid of frames.

    frameCount = frameCount - fpsRate * tps

    // Get rid of time.

    baseTime = baseTime + tps

// Then calculate new FPS and adjust busy loop.

fpsRate = frameCount / (currentTime - baseTime)
switch (fpsRate):
    < 60: busyTime = busyTime - 1
    > 60: busyTime = busyTime + 1

这实际上是 "queue" 最长十秒的数据。它实际上 不是一个队列,因为它不维护个人计数,而是使用平均值来调整数据。但是,除此之外,它的行为就像一个。


附录:仅用整数处理每秒 18.2 个滴答等数字也相对简单。

只在五秒边界而不是一秒边界上工作(每秒 18.2 个滴答正好是每五秒 91 个滴答):

// Once we have twenty seconds of data (four
//   five-second slots), strip away the first
//   five seconds (one slot).

tp5s = 91
while currentTime - baseTime >= tp5s * 4:
    // Get rid of frames.

    frameCount = frameCount - fpsRate * tp5s

    // Get rid of time.

    baseTime = baseTime + tp5s

假设这是实模式 DOS 运行ning 在 PC 上,您可以使用计时器 0(8254 通道 0)进行繁忙循环。这是一个快速计时器。您可能需要连接到 INT 8,因为当计时器环绕时会出现问题。我不记得所有细节,但是如果在读取定时器的指令中出现 INT 8,则需要重新读取。

来自我对旧汇编计时器例程的评论的信息:

;       Timer based on 8254 channel 0 and system timer interrupt (8)    ;
;       Channel 0 runs at 1.19318 mhz or 838.0965 nsecs / cycle         ;
;       System timer interrupts every 65536 cycles = 54.925 ms          ;
;       or about 18.2 interrupts / second                               ;
;       1 ms   = 1193.18 cycles                                         ;
;       1 hour = 65536 * 65536 cycles = 3599.59 secs                    ;

Link 到 运行 固定频率且不随时间漂移的代码示例。它在 C 中,它使用 sleep(),在你的情况下你只是循环,但你会了解如何以固定频率将代码获取到 运行:

how to sleep accurately in a while loop in C (Linux)?