装配8086延迟

时间:2017-05-29 15:51:44

标签: assembly delay

我试图在我创造的游戏中推迟我的运动。 我的问题是,每次使用int 1Ah时,运动突然变为 "生锈" (不是在一条恒定的线上移动)我的延迟程序,我使用" nop"使运动稳固而不生锈。我的老师说我不能用" nop"而且我不知道怎么做延迟使用1Ah并且看起来很好(不生锈) 我正在使用图形模式和汇编x86 谢谢。它工作得很好,但我的老师可能因某种原因不喜欢它。

我的延迟过程:

proc delay   

delRep:
    push    cx  
    mov     cx, 0FFFFH 
delDec:
    dec     cx 
    jnz     delDec
    pop     cx
    dec     cx
    jnz     delRep
    ret
endp delay

3 个答案:

答案 0 :(得分:0)

要在屏幕上流畅移动,您需要与"垂直同步"进行同步。显示模式。

屏幕上的图像是随着时间的推移而建立的,它不是即时的,例如当您的显示模式为60Hz时,它意味着生成一个单独的图像大约1000/60 = 16.666ms,其中一部分是期间正在模仿"将光线返回到左上角"期间,称为"垂直回扫",这对于现代液晶显示器来说并不是真正需要的,但CRT管显示器需要它,它仍在使用。

垂直回扫通常花费5%的时间(0.83ms),这是更新视频RAM内容的最佳时期,因为它可以防止任何"撕裂"即将发生。但是只有这么短的数据时间通常是不够的(除非你使用一些双/三缓冲方案),所以在某些情况下,绘制到屏幕确实也发生在垂直回扫之外,精确定时要么发生在或者在显示射线后面(从上到下逐行从左到右,从左到右逐行逐行,并且当光线返回到左侧并移动一条线时具有非常短的水平回扫时段down ...这让我真的很奇怪,为什么旧​​的CRT显示器没有使用从左到右+从右到左的奇数/偶数行信号编码,节省了水平回扫时间。

如果你不这样做,而你只是覆盖显示数据任何时间,那么可能会发生这样的情况:当旧图像已部分显示时,你会将新的动画阶段放在光线之前显示,因此该点上方的显示将显示旧相位,之后将显示新相位。这确实产生了撕裂"这一点特别明显,一些垂直线向侧面移动。

因此,如果你想要流畅的动画动画,并且我猜你正在为x86 DOS编码(来自int 1Ah),你应该与VSYNC同步(搜索VGA"等待回扫& #34;示例)。

但是这会打开另一种蠕虫,因为现代PC显示器可能会以不同的刷新率运行,所以设计为60Hz正确速度的游戏将在120Hz显示器上运行速度的两倍(如果它足够快以完成所有游戏代码和绘图在~8ms)。

由于这是学校作业,你可能在dosbox中运行它,我认为假设60Hz显示模式和VSYNC同步是合理的。

(当然,dosbox窗口中的输出可能仍然是"撕裂"因为模拟的显示可能无法与窗口内容更新与实际显示刷新率同步,但是当您将dosbox切换为全屏,它通常使用真正的VGA模式,例如流行的320x200 256色" 13h模式"具有60Hz,并且大多数LCD将正确显示,并使模拟的DOS应用程序运行&# 34;光滑",如果你给它足够的CPU周期)

关于int 1Ah ... DOS系统时钟,默认情况下每秒大约18.2次,即使对于60Hz的慢速刷新率也不够快,不是甚至谈论现代100+ Hz显示模式。

并使这个文本墙更加实用",以及旧时代游戏主循环的示例(如果重绘速度足够快,如俄罗斯方块/等):

  1. 做其他事情(下一步未提及),特别是如果需要很长时间
  2. 等待回撤
  3. 扫描输入设备以进行播放器输入
  4. 根据输入更新世界(必须超快)
  5. 绘制屏幕(希望显示屏仍处于垂直回扫状态,或者在写入VRAM后面,因此通常从上到下绘制更改)
  6. 或者,如果游戏速度不足以在射线前方/之后进行绘制,但速度足以在60Hz显示器上进行16.6ms(全屏重绘),则可以使用一些允许双/三缓冲方案的视频模式:

    1. 扫描输入设备
    2. 根据输入更新世界(可能需要一些时间)
    3. 等待回扫和翻转缓冲区(因此在屏幕外缓冲区中准备的旧图像现在将显示在屏幕上,并且您有新的未使用的屏幕外缓冲区)
    4. 将世界绘制到离屏缓冲区(可能需要一些时间,并且可以按任何顺序绘制)
    5. 做其他事情
    6. 如果游戏的速度甚至不足以达到60Hz,那么在DOS中你必须重新编程定时器芯片,以便比18.2倍更快地打勾,并使用它来计算前一循环中错过的垂直回扫期,所以你知道你的世界更新何时应该提前2帧或更多帧(跳过一些帧),因为之前的抽奖太慢了。

      关于"效率不高":

      在现代多任务操作系统中,您可以调用某种delay(ms)操作系统方法,这样可以保证您的代码不会被关闭。对于至少指定的数量,同时操作系统可以运行其他线程/进程或只是"空闲"特定于平台(针对特定芯片组/ BIOS进行了优化,以尽可能节省功耗)。

      这在游戏中很少使用,因为延迟不能得到保证,如果操作系统让你的线程睡眠时间过长,你会错过显示回扫并且动画会不稳定,但是如果性能非常高,那么#&# 39;是一个选项(特别是在双/三缓冲方案中,图形驱动程序允许在实际回扫之前对屏幕进行编程,因此当游戏偶然睡不着时,这不是灾难)。 / p>

      在DOS中,CPU总是通过指令进行搅拌(除非你经历疯狂的长度以支持特定的平台/芯片组省电方法,实际上使用NOP循环通常非常接近它,因为许多x86 CPU将检测此模式并降低功耗)。是否循环直到某些寄存器递减到零,或者直到VGA芯片组报告VSYNC位为ON,或者直到定时器计数器"滴答"为止,您无法切换CPU" 34;关闭"在DOS中以任何通常可用的方式。

      对于动画的延迟时间有点低效;由于它们的频率不同,它不会在不同的CPU上保持相同的速度(就像VSYNC等待在不同的显示刷新率上不会有相同的速度)。考虑到这一点,使用int 1Ah计时器有其优点,虽然它缺乏VSYNCed平滑性,但它会使速度保持在所有类型的机器上。

      同时做" nop"延迟,你可以在某些情况下做一些更有意义的事情,比如为游戏计算一些东西,但是一旦完成你的框架,没有太多的意义来制作另一个,例如用VSYNC以300FPS运行FPS射击游戏ON几乎是同样的事情,因为以显示模式的频率运行它,因为VSYNC会使所有高于显示速率的图像被丢弃并且从未向用户显示,这就是VSYNC ON下的这类游戏的原因选项通常将FPS限制为显示速率。限制运行之间的微小差异可以是在读取用户输入的时间段和物理模拟的频率,如果物理不够稳定以产生相同的结果,则300 FPS体验可能不同。实际上有旧的DOS游戏,它们在快速机器上无法播放,因为它们的物理特性很好。一旦频率变得太高就停止移动任何东西,因为他们确实使用了动态时间 - 增量"步骤",这太小而不能实际移动物体(想象物体速度为物理步长0.5像素,是计算为mov ax,time_tick_delta shr ax,1 - >当在time_tick_delta仅为1的机器上运行时,运动完全停止...如果原始程序员拥有价值超过8+的最佳PC一直以来,在5年的未来很容易预见到这样的问题。)

      所以这样你的延迟就是#34;效率不高",因为你可以做一些更有用的事情,但是如果你的游戏已经以全速率刷新率运行,那就不多了更有意义,至少我不知道。

      专业的DOS游戏经常花费不止一个显示框来更新所有东西(因为游戏太复杂了),所以他们不得不重新编程计时器来计算他们错过了多少帧,并跳过这么多来抓住与下一个。这可能与int 1Ah方法最接近。但它往往还涉及额外的二分法,即等待垂直回扫,以避免撕裂。所以你结束了两者并且拥有更复杂的游戏循环逻辑来评估必须跳过多少帧才能保持正确的游戏速度。

答案 1 :(得分:0)

这是另一个"延迟"将int 15hah=86h一起使用,在游戏中对其进行测试:

;DELAY 500000 (7A120h).
delay proc   
  mov cx, 7      ;HIGH WORD.
  mov dx, 0A120h ;LOW WORD.
  mov ah, 86h    ;WAIT.
  int 15h
  ret
delay endp      

另一个"延迟"使用系统时间,它应该每秒重复约5次:

delay proc  
system_time:   
;GET SYSTEM TIME.
  mov  ah, 2ch
  int  21h ;RETURN HUNDREDTHS IN DL.
;CHECK IF 20 HUNDREDTHS HAVE PASSED. 
  xor  dh, dh   ;MOVE HUNDREDTHS...
  mov  ax, dx   ;...TO AX REGISTER.
  mov  bl, 20
  div  bl       ;HUNDREDTHS / 20.
  cmp  ah, 0    ;REMAINDER.
  jnz  system_time
  ret
delay endp  

接下来是中的第三个延迟,它需要一个变量:

seconds db 0  ;◄■■ VARIABLE IN DATA SEGMENT.

delay proc  
delaying:   
;GET SYSTEM TIME.
  mov  ah, 2ch
  int  21h      ;◄■■ RETURN SECONDS IN DH.
;CHECK IF ONE SECOND HAS PASSED. 
  cmp  dh, seconds  ;◄■■ IF SECONDS ARE THE SAME...
  je   delaying     ;    ...WE ARE STILL IN THE SAME SECONDS.
  mov  seconds, dh  ;◄■■ SECONDS CHANGED. PRESERVE NEW SECONDS.
  ret
delay endp      

答案 2 :(得分:0)

跟进Jose ManuelAbarcaRodríguez回答:

INT 15 - BIOS - WAIT (AT,PS)
    AH = 86h
    CX:DX = interval in microseconds
Return: CF clear if successful (wait interval elapsed)
    CF set on error or AH=83h wait already in progress
        AH = status (see #0400)
Note:   the resolution of the wait period is 977 microseconds on most systems
      because most BIOSes use the 1/1024 second fast interrupt from the AT
      real-time clock chip which is available on INT 70

如果在您运行游戏的环境中实现了timer0,您可以使用类似的内容来阅读它:

;       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                    ;
;
TMR     equ     040h
;-----------------------------------------------------------------------;
;       code                                                            ;
;-----------------------------------------------------------------------;
        .code
        assume  cs:@code,ds:nothing,es:nothing,ss:nothing

;-----------------------------------------------------------------------;
;       TmrGet  Returns current timer value.                            ;
;       If Timer 0 is in mode 3, it uses an output bit and the upper 14 ;
;       bits of the timer 0 register.  Each cycle decrements timer 0    ;     
;       by 2 (the upper 14 bits are decremented by 1). The output bit   ;
;       toggles when the timer 0 register goes from a value of 2 to 0.  ;
;       If Timer 0 is in mode 2, then the count is in timer 0 register. ;
;       Timer 0 is initialized to 0 (65536 cycles per interrupt).       ;
;-----------------------------------------------------------------------;
TmrGet  proc    near uses si di bp ds
Tmrgt0: cli
        mov     al,0c2h                 ;output read channel 0 cmd
        out     TMR+3,al
        jmp     short $+2
        in      al,TMR                  ;get bit  (15  )
        test    al,2                    ;br if in mode 2
        jz      Tmrgt2
        shl     al,1                    ;put bit 15 into carry
        jmp     short $+2
        in      al,TMR                  ;get bits ( 7-0) << 1
        mov     ah,al
        jmp     short $+2
        in      al,TMR                  ;get bits (14-8) << 1
        sti
        xchg    al,ah                   ;ax = bits 15-0
        rcr     ax,1
        test    ax,07fffh               ;br if bits (14-0) != 0
        jnz     Tmrgt1
        xor     ax,08000h               ;toggle bit 15
Tmrgt1: neg     ax                      ;make count positive
        ret

Tmrgt2: in      al,TMR                  ;get bits ( 7-0)
        mov     ah,al
        jmp     short $+2
        in      al,TMR                  ;get bits (15-8)
        sti
        xchg    al,ah                   ;ax = bits 15-0
        neg     ax                      ;make count positive
        ret
TmrGet  endp