在8086实模式下绘制图形时,避免闪烁/闪烁

时间:2017-05-04 23:48:35

标签: assembly x86 x86-16

我是汇编语言8086的新手。我正在使用masm611。我正在制作恐龙游戏(Google Chrome离线游戏)。我画的越多,屏幕开始闪烁。我已经尝试了一切,但找不到任何东西。代码编译完美无误。 这是代码

MainLoop:
    ;call clearRegisters
    mov ah,01h
    int 16h
    mov bh,ah
    mov bl,0

    mov al,0
    mov ah,0Ch
    int 21h

    cmp bh,12h
    je Exit

    call SelectScreen

    mov ax,dinoObj.x
    mov x,ax
    mov ax,dinoObj.y
    mov y,ax

    call clearRegisters
    call DrawBackground

    call clearRegisters
    call drawDinoBase
    call clearRegisters

    call drawDino
    call clearRegisters
jmp MainLoop`

SelectScreen功能如下

SelectScreen proc
    mov ah,0
    mov al,12h
    int 10h
    Terminate:
    ret
SelectScreen Endp`

DrawDino功能片段

drawDino proc
mov ax,y
mov tempY,ax
mov cx,8
outer1:
    push cx
    mov ax,x
    mov tempX,ax
    mov cx,20
    inner1:
        push cx
        mov ah,0ch
        mov al,10
        mov cx,tempX
        mov dx,tempY
        int 10h
        inc tempX
        pop cx
    loop inner1
    inc tempY
    pop cx
    loop outer1

    mov ax,y
    add ax,8
    mov tempY,ax
    mov cx,12
    outer2:
        push cx
        mov ax,x
        mov tempX,ax
        mov cx,38
        inner2:
            push cx
            mov ah,0ch
            mov al,10
            mov cx,tempX
            mov dx,tempY
            int 10h
            inc tempX
            pop cx
        loop inner2
        inc tempY
        pop cx
        loop outer2

        mov ax,y
        add ax,21
        mov tempY,ax
        mov cx,8
        outer3:
            push cx
            mov ax,x
            mov tempX,ax
            mov cx,20
            inner3:
                push cx
                mov ah,0ch
                mov al,10
                mov cx,tempX
                mov dx,tempY
                int 10h
                inc tempX
                pop cx
            loop inner3
            inc tempY
            pop cx
            loop outer3

2 个答案:

答案 0 :(得分:3)

您所描述的内容称为Flickering。直接引用维基百科:

  

例如,直接在帧缓冲区中消隐区域,然后在顶部绘制'它可以使空白区域暂时出现在屏幕上。

这是由于您对帧缓冲区的写入与视频子系统刷新周期之间缺乏同步造成的 为了保持一致的图形状态,您应该仅在回扫期间修改帧缓冲区,通常是vertical retrace,因为它的延伸时间较长。
在视频子系统读取帧缓冲区时写入帧缓冲区会产生不一致状态,因为只会更新当前扫描线下方的屏幕部分。

此问题的解决方案都具有相同的轮廓 - 在回扫期间一次写入帧缓冲区。

最佳解决方案为Page flipping,并已在评论中提出 它包括在显示适配器的存储器中的两个不同区域之间切换活动帧缓冲区 不幸的是,VGA适配器并不支持您选择的视频模式 1 - 只有一页。

替代方法是double buffering,它包括使用后缓冲区 - 与帧缓冲区一样大 - 用通常的绘图基元写入。
然后呈现后缓冲区,在下一次可用的回溯期间将其一次性复制(Bit blited)到帧缓冲区。

软件架构的确切编写过于宽泛,有很多方法可以实现这一点,每个方法都有不同的技能。
最简单的方法是使用同步表示 - 代码在后台缓冲区上执行所有图形操作,然后等待下一个垂直回扫来呈现它。
如果你的代码足够快,那么等待回溯的浪费时间就不重要了 但请记住,渲染循环将以监视器刷新率的速度运行(这在游戏中通常是一件好事,因为它是一个免费的时钟挂钟)。

不幸的是,您使用BIOS服务作为图形基元,它们只在帧缓冲区中写入(实际上是活动页面,但只有一个)。

正如CodyGray提醒的那样,另一种选择是直接写入帧缓冲区 - 您选择的视频模式不是很容易使用,因为它涉及位平面。模式13h反而将帧缓冲区暴露为连续的内存区域 - 非常像矩阵。

如果您不想重写吸引的代码,可以尝试使用一些胶带进行调整。
通过测试Input status #1 register of the VGA adapter的第3位可以判断监视器何时处于垂直回扫状态 - 当该位为1时,监视器处于垂直回扫状态。

想法是在开始绘制新框架之前等待垂直回扫 这可能与您编写渲染循环的方式兼容也可能不兼容 - 此外,我从未在BIOS调用和垂直回扫逻辑中使用这种混合方法。
我不能排除BIOS实现的可能性,因为它与视频子系统交互的方式使等待回扫无用。
特别是,CodyGray指出BIOS调用将无情地导致闪烁。

为了避免在接近尾端的回扫中启动帧的风险,最好等待新的回扫。

waitForNewVR:
 mov dx, 3dah

 ;Wait for bit 3 to be zero (not in VR).
 ;We want to detect a 0->1 transition.
_waitForEnd:
 in al, dx
 test al, 08h
jnz _waitForEnd

 ;Wait for bit 3 to be one (in VR)
_waitForNew:
 in al, dx
 test al, 08h
jz _waitForNew

 ret

我还没有保存注册dxax,您可能需要根据需要添加一对push/pop

1 根据Ped7g,可以实施Page flipping manipulating the hardware directly

答案 1 :(得分:0)

所以我能够解决这个问题'闪烁'通过以下两个步骤来解决问题:

  1. 在绘制画布后添加 TimeDelay (睡眠)。
  2. DOSBox 的CPU周期增加到340000000。
  3. 谢谢大家的答案。