16 bit animation - getting started
Asked Answered
Y

1

1

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

Youngs answered 16/3, 2017 at 19:55 Comment(19)
If I read that source correctly, it will set colour index 0 to full white, and then wait for a key and exit (it would, if there would be some DOS loaded). The code after ; draw single pixel is unreachable? (now I understand, it's boot sector, so the int 21h are void, somehow doesn't crash it, just return, and then you draw the pixel through BIOS... I see) And why do you draw pixel through BIOS? :-o .. simply write it to memory like mov al,3 mov es:[70*320+70],al. About your code "tutorials" ... yeah, student tutoring and archiving that, sounds great, but I prefer less tragic sitcoms.Apostolate
You should visit wiki.osdev.org/Main_PageAlarm
How do you make it move is obvious: 1) clear (will become "restore background" later when you will have background, and next stage is "I will draw the whole frame from scratch every frame") the old one. 2) draw the new one. ... The "too broad" part is timing, back in the real HW age VSYNC synchronization has been used heavily, as that's the only way to make it smooth (as long as your code performs fast enough to be on par with the beam). Then CRT displays with higher freq. were sold and all the vsync timing was off, games ran faster, etc.. Now LCDs are mostly 60Hz = good enough for experimentsApostolate
But to do a proper timing for animation, that's actually quite a complex topic (I mean far beyond simple SO answer, but it's not rocket science either). Once you don't have fixed screen refresh rate, you will have to do some compromise here and there, probably not having perfect solution for every display (especially in case of old "2D parallax" planes scrolling). If you don't care about smooth graphics without visual artifacts, then just read some hw clock (just make sure you don't use the machine cycles one, which is dynamically speeding up/down by cpu freq) and interpolate positions.Apostolate
OK so far I get the usual no help answers anyone can find by doing a google search. What you will find is countless links to forums where you get the same old cliche' answers; 1: Argue with you: "Why in the world would anyone want to do 16 bit graphics?" 2: Go read a book somewhere else on Operating System Development. 3: Stating the obvious: "Animation is a series of frames, and here some more useless stuff I just grabbed off a WIKI search" GREAT! Thanks a lot! You all want to come off as appearing so knowledeable but no one is answering a very rudimenatareYoungs
7gods.org/files/7g_aoc2k.zip (my 2k game, based on VSYNC timing (old TASM sources included, but I don't remember any details any more, it's many years old))Apostolate
Oh no, I did take a peek and due to size constraints I actually used some BIOS int to wait for VSYNC, which did work on many machines, but not all of them. Using the classic reading value directly from VGA status register was more stable, but I didn't find it separately, only inside this bigger article: github.com/jagregory/abrash-black-book/blob/master/src/… ... BTW, why do you expect better answer for such broad question? It's not clear, which part is giving you trouble and there're many ways how to tackle this problem. You can still take a look at that AOC source...Apostolate
And finally, that 2 step guide is, even in it's very general and vague form, not exhausting all possibilities, not even close to it. For example a large square of solid colour doesn't need to be redrawn as a whole, you can just clear some pixels on one edge, and add so many on the other edge, and voila, it "moves"...Apostolate
What good are TASM sources? I am just getting started in assembly! Like I really know how to port from a dozen different languages.Youngs
I understand all the obvious stuff about how in the old days animation was done using your CPUs refresh rate, etc. I am asking if one can access the millis from your computer's internal clock to use as a timer. Not looking for historical lectures and "I forget how I used to do it" is not an answer at all! Waxing nostalgic does not help me in any way.Youngs
I am interested in 16 bit graphics on a x86_64. Whether it runs on every other type of processor or not is not a concernYoungs
Looks like you need to study this wiki.osdev.org/HPET (2 clicks away from google "x86 assembly rtc time" through this SO answer. The VSYNC is still actual, actually it's the only way to get smooth graphics (either by syncing your main loop to it, or by preparing correctly vsync-time-aligned frames into buffer and using double/triple buffering scheme to switch them on screen at proper frame). Problem with VSYNC timing is not that it is being obsolete, but with variable display rates it is more tricky to be done correctly = needs a good plan.Apostolate
The HPET solution will be of worse visual quality, but probably much easier to implement, so try that osdev wiki first, the vsync stuff and advanced VGA graphics looks to be way over your head any way.Apostolate
Hmm.. reading about it for a while, actually maybe you should switch to the VSYNC just by reading the VGA port, looks much easier for experiments (doable in 5min). Overall it's hard to follow your goals, as you are writing OS (well, okay), but then suddenly you want to deal with graphics and animation. An OS should provide graphics driver and timers API, but the animation itself + gfx drawing should be part of user application (the "desktop/menu" can be seen as user application too, just internal and a bit more privileged one, but not ring-0 privileged). Doing ALL at the same time is hard.Apostolate
My goal is to learn more about 16 bit graphics as it clearly states in the descriptive title. I am sorry if I have thrown everyone off by also having a desire to learn more about OS development. I have plenty of books and have in fact already written a working 32 bit kernel. Video tutorials will come eventually on all that. Right now the topic is animation. Here is a working assembly animation I found on gist: gist.github.com/terabaud/5c9cf9316f0aed88cefd4d0957be6d03 Clearly one does not need a page and a half of VSYNC code to make something move on the screen. Too complex to hack thoYoungs
Unfortunately, I have astigmatism. While i used to love to read as kid, I find that now it is extremely difficult to remain focused on words that seem to dance all over the page. That is why I respect people who make comprehensive videos. Pointing me in the direction of 3 inch thick books and lengthy articles that take forever to get to the point is about as useful as shining a spotlight in my face.Youngs
You can belittle my desire to make videos and encourage other to do so as a pathetic example of the blind leading the blind all you want. At least at Khan Academy, it is taken for granted that students may be 8 years old and people are able to give useful answers. Around here all people do is condescend, talk in circles and tell people they are too dumb to understand and need to go read a book!Youngs
That's very unfortunate (about your reading). An assembly is hard to describe in short way (or video) accurately enough, as it is based on the HW machine design, so usually many details pop out from the HW design. High level languages are much better at hiding different "weird" machine things, making it more general and logical, and shorter to explain. ... for example your original post: those int 21h are still puzzling me, why you put them there, if you are doing your own OS, I would guess that will crash, but somehow you are lucky enough to see your pixel instead.Apostolate
I put up some examples for you... but ANIMATION examples. I have no intention to do anything with timers, that's OS stuff, outside of my expertise, I'm glad I can use the linux kernel to do all this for me. You can probably open another question without any animation/graphics mentioned, how to set up some OS-level timers on x86. But from that HPET description I think that's not trivial topic for few lines answer like the animation thing below. Anyway, both topics are pretty much unrelated, in animation you need just some time reference, not important how you produce it.Apostolate
A
2

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.

Apostolate answered 22/3, 2017 at 8:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.