To have smooth movement on screen, you need to synchronize with the "vertical sync" of display mode.
The image on the screen is built over time, it's not instant, for example when your display mode is at 60Hz, it means one single image is being generated roughly 1000/60 = 16.666ms, part of that period is emulating "returning the ray to top-left" period, called "vertical retrace", which is not really needed for modern LCD, but it was needed for CRT tube displays and it's still used.
The vertical retrace took usually 5% of time (0.83ms), and it is the best period to update video RAM content, as it prevents any of the "tearing" to happen. But having just such short time period for data was usually not enough (unless you use some of double/triple buffering schemes), so in some cases the drawing to the screen did happen also outside of vertical retrace, precisely timed to either happen ahead of or behind the display ray (which goes from top left line by line from top to bottom, for each line from left to right pixel by pixel, and having a very short horizontal retrace period when the ray is returning to left side and moving one line down ... which makes me actually now wonder, why the old CRT displays didn't use left-to-right + right-to-left odd/even line signal encoding, saving the horizontal retrace time).
If you don't do this, and you simply overwrite display data any time, it may happen you will put new phase of animation ahead of ray just when the old image was already partially displayed, so the display above that point will show old phase, and after it there will be new phase. This does produce the "tearing", which is especially visible with some vertical lines moving sideways.
So if you want smooth movement animation, and I'm guessing you are coding for x86 DOS (from the int 1Ah
), you should synchronize against VSYNC (search for VGA "wait for retrace" examples).
But this will open another can of worms, as modern PC displays may operate on different refresh rates, so a game designed to have correct speed at 60Hz will run twice the speed on 120Hz display (if it is fast enough to finish all the game code and drawing in ~8ms).
As this is school assignment, and you are probably running it in dosbox, I think assuming 60Hz display mode and syncing by VSYNC is reasonable.
(of course the output in the dosbox window may still be "torn apart" because the emulated display may not be synced with the window content updating vs real display refresh rate, but when you switch the dosbox to fullscreen, it usually does use the real VGA modes, for example the popular 320x200 256 colour "13h mode" has 60Hz, and most of the LCDs will display that properly, and make the emulated DOS app to run "smooth", if you give it enough CPU cycles)
And about int 1Ah
... that's the DOS system clock, which by default ticks about 18.2 times per second, that's not fast enough even for the slow 60Hz refresh rate, not even talking about the modern 100+ Hz display modes.
And to make this wall of text a bit more "practical", and example of old-era game main loop (in case the redraw is fast enough, like tetris/etc):
- do anything else (not mentioned in next steps), especially if it takes long
- wait for retrace
- scan input devices for player input
- update world according to input (has to be super fast)
- draw the screen (hoping the display is still in vertical retrace, or behind your writes to VRAM, so usually drawing changes from top to bottom)
Or in case the game is not fast enough to draw ahead/after ray, but fast enough to fit into 16.6ms on 60Hz display (full screen redraw), you can use some video mode which allows double/triple buffering scheme:
- scan input devices
- update world according to input (can take some time)
- wait for retrace and flip buffers (so the old image prepared in off-screen buffer will be now on-screen, and you have new unused off-screen buffer)
- draw world to off-screen buffer (can take some time, and can be drawn in any order)
- do other stuff
And in case the game is not even fast enough to fit into 60Hz, in DOS you will have to reprogram the timer chip to tick faster than 18.2 times, and use this to count missed vertical retrace periods in previous loop, so you know when your world update should update 2 or more frames ahead (skipping some frames), because previous draw was too slow.
About "not efficient":
In modern multitasking OS you can call some kind of delay(ms)
OS method, which is guaranteed to make your code be "off" for at least the specified amount, and meanwhile the OS can run other threads/processes or just "idle" in platform specific (optimized for particular chipset/bios to save power as much as possible).
This is rarely used in games, because the delay is not guaranteed and if the OS makes your thread sleep way too long, you will miss the display retrace and the animation will be erratic, but in case of very performant game it's an option (especially in double/triple buffering schemes, where the graphics driver allows to program the screen flip ahead of actual retrace, so it's not a disaster when the game sleeps a bit over by accident).
In DOS the CPU churns through instruction all the time (unless you would go through insane length to support particular platform/chipset power saving methods, actually using NOP
loop is usually quite close to it, as many x86 CPUs will detect this pattern and lower the power usage). It doesn't matter whether you are looping till some register decrements to zero, or till the VGA chipset reports VSYNC bit ON, or till the timer counter "ticks", you can NOT switch the CPU "off" in DOS in any generally usable way.
What is a bit inefficient about the delay timing of animation; that it will not keep the same speed over different CPUs due to their different frequencies (just like the VSYNC wait will not have same speed over different display refresh rates). Considering this aspect, using the int 1Ah
timer has its merits, while it is lacking behind the VSYNCed smoothness, it will keep the speed constant on all kind of machines.
Also while doing the "nop" delay, you can in some cases do something more meaningful, like calculating something for the game, but once you have your frame done, there's not much point to churn out another one, for example running FPS shooter at 300FPS with VSYNC ON is almost the same thing, as running it at the frequency of display mode, because the VSYNC would make all the images above the display rate to be dropped and never shown to the user, that's why such games under VSYNC ON option usually cap the FPS to the display rate. The very tiny difference between capped run can be in the period of reading user inputs, and the frequency of physic simulation, if the physic is not stable enough to produce the same result, the 300 FPS experience may differ. Actually there were old DOS games, which got unplayable on fast machines, because their "physics" stopped to move anything once the frequency got too high, as they did use dynamic time-delta "step", which was too small to actually move the object (imagine having speed of object 0.5 pixel per physics step, being calculated as mov ax,time_tick_delta
shr ax,1
-> when run on machine where time_tick_delta
was just 1, the movement stopped completely ... if the original programmer had on his best PCs that value above 8+ all the time, it's easy to not foresee such problem in the 5 year future).
So in this way your delay is "not efficient", because you could have done something more useful, but if your game is already running at full display rate refresh rate, there's not much more meaningful to do, at least I would have no clue.
The professional DOS games often did took more than single display frame to update everything (as the game was too complex for that), so they had to reprogram the timer to count how many frames they missed, and skip so many to catch up with the next one. This is probably closest to int 1Ah
approach. But it often still involved additional dichotomy of waiting also for vertical retrace, to avoid tearing. So you ended doing both and having even more complex game loop logic to evaluate how many frames has to be skipped to maintain correct speed of game.