16 bit animation - getting started

后端 未结 1 840
感动是毒
感动是毒 2020-12-04 03:50

Took a while but finally got to square 1 in 16 bit graphics. Here I clear the screen and draw a single pixel:

mov ax, 0a000h
mov es, ax      ; es - Extra Seg         


        
相关标签:
1条回答
  • 2020-12-04 04:07

    Here is the most crude animation of 80x80 square.

    It works like:

    1. wait for vertical retrace of VGA to start blank period (beam is returning back to start of screen)
    2. set whole VRAM to zero ("clear whole screen")
    3. draw 80x80 square at position "bx"
    4. adjust bx by +-1 and keep it within 0..239 range
    5. repeat infinitely

    Not sure if you can affect speed of qemu like the cycles count in dosbox (and how accurate its VGA emulation is).

    Maybe try this in DOSBOX first (just rename the binary to "test.com", the source below will work as COM file too), to understand the pitfalls of graphics programming.

    Then in dosbox you can use Ctrl+F11/F12 to subtract/add machine cycles (speed of PC) to see what happens when this crude algorithm is used on very slow PCs.

    On fast PC the screen is cleared before the beam returns to first line, so the square is drawn ahead of beam, and everything looks solid.

    But my default setting of dosbox is slow ~286/386 PC-like, which will be still clearing the screen while the beam starts drawing first line on monitor, so it will draw the black empty lines. Once the code will start to draw the square, it will eventually catch up to the beam, somewhere around line ~50, so bottom ~30 lines of the square are visible.

    If you will play with machine speed, you can see more artefacts, like the square is completely drawn behind beam (not visible to user), or even blinking (when the whole drawing takes longer than single frame refresh (1000/60 = 16.6ms on 60Hz monitor).

        BITS    16
    
        MOV     ax,13h
        INT     10h             ; 320x200 256colour VGA mode
        MOV     ax,0a000h
        MOV     es,ax           ; video RAM segment
    
        XOR     bx,bx           ; square position = 0
        MOV     si,1            ; direction of movement
    
    AnimateLoop:
        CALL    waitforRetrace  ; destroys al, dx
        ; clear whole screen
        XOR     di,di
        XOR     eax,eax
        MOV     cx,320*200/4
        REP STOSD
        ; draw 80x80 pixels square with color 3
        MOV     eax,0x03030303
        MOV     di,bx
        MOV     dx,80           ; height
    drawSquareLoop:
        MOV     cx,80/4
        REP STOSD               ; draw 80 pixels (single line)
        ADD     di,320-80       ; next line address
        DEC     dx
        JNZ     drawSquareLoop
        ; move it left/right
        ADD     bx,si           ; move it first
        CMP     bx,240
        JB      AnimateLoop     ; 0..239 are OK
        ; too far on either side, reverse the movement
        NEG     si
        ADD     bx,si           ; fix position to valid range
        JMP     AnimateLoop
    
    waitforRetrace:
        MOV     dx,03dah
    waitforRetraceEnd:
        IN      al,dx
        AND     al,08h
        JNZ     waitforRetraceEnd
    waitforRetraceStart:
        IN      al,dx
        AND     al,08h
        JZ      waitforRetraceStart
        RET
    
        times 510-($-$$) db 0   ; PadZeros:
        dw 0xaa55       ; MagicNumber
    

    Now I see then INT 8 timer interrupt is actually BIOS provided, so I can rewrite this example to use that timing to show you difference (VSYNC vs timer animation) ... hmm... I'm extremely reluctant to, because timer animations sucks (I mean, even VSYNC animations have to work with timer to make up for skipped frames, but that's too complex for short example, but timer-based animations sucks inherently by design). I will give it ~10min at most and see if I can make it work...

    Ok, the INT 08h timer based version (don't watch if you are also prone to epileptic seizures from blinking images):

        BITS    16
        MOV     ax,13h
        INT     10h
        XOR     ax,ax
        ; ds = 0 segment (dangerous, don't do this at home)
        MOV     ds,ax
        MOV     ax,0a000h
        MOV     es,ax           ; video RAM segment
    AnimateLoop:
        ; clear whole screen
        XOR     di,di
        XOR     eax,eax
        MOV     cx,320*200/4
        REP STOSD
        ; draw square with color 3
        MOV     eax,0x03030303
        ; fetch position from BIOS timer-tick value
        ; (ticking every 55ms by default)
        MOVZX   di,byte [0x046C]    ; di = 0..255 from [0:046C]
        MOV     dx,80           ; height
    drawSquareLoop:
        MOV     cx,80/4
        REP STOSD
        ADD     di,320-80       ; next line address
        DEC     dx
        JNZ     drawSquareLoop
        JMP     AnimateLoop
    
        times 510-($-$$) db 0   ; PadZeros:
        dw 0xaa55       ; MagicNumber
    

    It has two major problems:

    1. int 8 timer by default is ticking in 55ms, while most of the screens were/are_again 60Hz, so 16.6ms tick is needed to be smooth on 60Hz, even less with higher refresh rates. Now the square moves +1 pixel every 3rd-4th display frame.

    2. even if the timer would be 10ms, it would still blink like crazy, because the erasing of screen + drawing square in new position is not in sync with the display beam.

    The 1. can be resolved by reconfiguring the 8253/8254 PIT.

    The 2. can be resolved by drawing into offscreen buffer first, and then copying the final image to real VRAM (ideally in VSYNC-ed way to prevent "tearing")


    Both example are very crude, basically just demonstrating that "clearing screen + drawing in new position" really does animate stuff. And that it is not sufficient enough to achieve even basic quality.

    To get anything reasonable you have use more sophisticated logic, but that strongly depends on what you are drawing and animating and how.

    A general purpose approach with VGA is to either use offscreen buffer and copy it to VRAM when drawing is finished (wasting machine cycles on 64k bytes copy.. may sounds laughable today, but it was big deal in 1990).

    Or use the VGA control registers to set up one of the unofficial "x modes" and set up the VGA memory layout in a way to support double/triple buffering scheme, so you draw new frame directly into VRAM, but into the hidden part, and when the drawing is finished, you switched the displayed part of VRAM to show the newly prepared content. This helped to avoid the 64k copy, but writing to VRAM was actually quite slow, so it was worth the effort only in situations when you had little overdrawing of pixels. When you had lot of overdraw, it was already too slow (no chance for 60FPS), and drawing it offscreen in ordinary RAM made it actually faster, even with the final 64k copy to VRAM.

    0 讨论(0)
提交回复
热议问题