Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
669 views
in Technique[技术] by (71.8m points)

assembly - 16 bit animation - getting started

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 Segment now points to the VGA location

mov ax, 0013h
int 10h

xor al, al
mov dx, 3c8h
out dx, al

inc dx
mov al, 63
out dx, al
out dx, al
out dx, al

mov ax, 0100h
int 21h
mov ax, 4c00h
int 21h

; draw single pixel :)

mov ah, 0ch;
mov al, 03h     ; color
mov cx, 70      ; x co-ordinate
mov dx, 70      ; y co-ordinate
; mov bh,1      ; page #
int 10h 

times 510-($-$$) db 0   ; PadZeros:
dw 0xaa55       ; MagicNumber

Step 2: How do I make it move?

Clearly that means alternately wiping the screen, updating and drawing the pixel in a loop. Of course it would fly across the screen, so I would guess you would access the internal clock's millis, compare, then update when it is greater than some constant.

Just getting started in Assembly. I do know how to use a label to make a pseudo function so suppose I probabaly could have gone ahead and done that in the example.

I am compiling from nasm as bin then open direct in qemu. Note I don't use a linker and therefore don't need to use .text or any of that other .bss Just trying to work from raw binaries.

I am also trying to document everything I learn on YouTube if anybody is interested in some Getting Started in Lower Level Machine Code tutorials: https://www.youtube.com/watch?v=XJdcoHjzvCo&list=PLJv7Sh0ZDUnpNnhNm3msK1C4K_8SzfMvO

If anybody else is also on a parallel path of trying to write a kernel, their own Operating System, Compiler, or learn more about 16 bit game graphics in assembly, feel free to join in the KAOS Project and help create a 100% video documented OS: https://github.com/musicalglass/KAOS

KAOS The no BS OS

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

2.1m questions

2.1m answers

60 comments

57.0k users

...