Vector graphics programming under Dos

Started by
14 comments, last by undead 12 years, 7 months ago
I haven't done that in a few years now but I seem to remember the Borland C++ 3.1 compiler could simulate a dos environment on Windows 98 and earlier versions.
It doesn't matter much which compiler you use though, as long as it allows inline assembly you can easily get into Mode 13H with a couple of instructions.
Mode 13H is a 320x200 graphic mode which should be a good place to start.
Google it for more info

Advertisement

[quote name='szecs' timestamp='1314795562' post='4855862']
[quote name='SuperVGA' timestamp='1314793755' post='4855850']
[quote name='szecs' timestamp='1314793463' post='4855847']
Anyhoo, what do you mean by "vector graphics"? What makes you think Prince of Persia has "vector graphics"? Because it is NOT vector graphics....

The animations of the lead character in POP, AW/OOTW and FB are all very smooth... I'm not sure about POP,
but the OOTW character animations could have been made with vector transformations. Notice the characters here:
[/quote]
The animations of the main character in POP are not that smooth. It seems to be smooth and life-like, because the sprites where drawn using real videos of a guy doing those moves.
(AFAIK)
[/quote]
Yep, that's probably true. I read an interview on it some years ago, and now that you mention it, the interview explained sprites drawn from filmed movement.
-Also, it's easier to achieve "smooth" hand-drawn animations on lower resolutions. I just remembered the Quake animations,
and they always seemed pretty coarse...
[/quote]

This technic is called: Rotoscoping
Now, my question is how to achieve this graphics under Dos ?
do i need any special drawing / animation software for Dos and does it require any special routines in C language ?
In Dos, you can use standard libraries and drivers (egavga.bgi for example). Usually there's a "graphics.h" with your IDE/compiler (Borland C++ for example), there are a lot of drawing routines in that. Like lines, rectangles, polygons, circles, text drawing etc. These routines are the interfaces for the device driver [size="1"](clarification needed).

Ant the beginning of the program, you have to initialize the device driver, set the display mode (resolution for example), that's usually a few lines of specific code (by calling routines from graphics.h). Then you simply call the appropriate draw routines, which immediately draw the stuff on the screen (at least in mode 13h and 256 indexed color modes).

I don't know about true color or any other RGB color modes, I could only set up indexed color modes, and cannot, for the life set up RGB modes (or whatever the hell the proper terms/names are).

In Dos, you can use standard libraries and drivers (egavga.bgi for example). Usually there's a "graphics.h" with your IDE/compiler (Borland C++ for example), there are a lot of drawing routines in that. Like lines, rectangles, polygons, circles, text drawing etc. These routines are the interfaces for the device driver [size="1"](clarification needed).

In DOS there was nothing like a device driver, all video cards just had to comply to the VGA standard. You submit commands using interrupts, ports and pointers... and nothing else. I remember I had a tseng labs et-4000 with special features and it was just a matter of outputting to the proper ports.

Those libraries were very very slow and as far as I can remember drawing a sprite on a 386 was already a pain.


Ant the beginning of the program, you have to initialize the device driver, set the display mode (resolution for example), that's usually a few lines of specific code (by calling routines from graphics.h). Then you simply call the appropriate draw routines, which immediately draw the stuff on the screen (at least in mode 13h and 256 indexed color modes).
[/quote]
I wouldn't draw onscreen, that would result in a lot of tearing (no backbuffer).

Since the OP wants to use C/C++, I suggest starting with something like this:
1- Create a backbuffer the dirty way.

unsigned char m_aBackBuffer[64000];

2- Write a routine getting a pointer to it.

unsigned char * GetBackBuffer()
{
return &m_aBackBuffer[0];
}

3- write a set of common functions for clearing/copying the backbuffer, inlining asm via the _asm directive (best solution write all low level routines into an external assembly file). Never never never use memset or memcpy. When copying or cleaning always use the fastest cpu instruction available (movsd/stosd operating on DWORDs).
4- if your game has a background, store the main background in another buffer and use your lowlevel copy function to do two jobs with one call: clear the backbuffer while drawing the background.
5- Since it's a platform game, write a low level routine to copy platforms (non transparent elements). The trick is to use movsd for an entire platform line, then skip to the following line in backbuffer adding (screenxresolution-platformxsize), copy the following line and repeat for platformysize.
6- as for sprites the problem is transparency which is a performance killer. Basicly you can't take advantage of movsd because it copies 4 pixels, so you have to manually check every pixel. Even reading a pixel from a sprite, check transparency and then decide if writing it or skipping it is a performance killer on a 286/386. So how did they manage to do something like prince of persia? Since none of the two games had scrolling my guess is they use one of the following techniques (or maybe both):
1- update only the rectangular portion of the screen that changed since the last frame, saving bandwith.
2- take advantage of compiled sprites, that is... compile your sprites into code.

More details about compiling sprites here:

http://www.nondot.or...ro/sprite4.html

The part about jack jazzrabbit, one of the first games released by epic megagames, is very enlighting about how things were done back at the time.


look at the game "Jazz the Jackrabbit" published by Epic Software. It uses compiled sprites exclusively for the background tiles and the characters in it. If you have played it, you may have noticed a number of lens effects. This is because the sprites are not simple compiled bitmaps, they read the data already on the screen and rearrange it to apear like a lens is sitting on it. Other applications are endless.
[/quote]
If I'm allowed to say that, Tim Sweeney was already kicking a***s around!
Then what is egavga.bgi? And all the other BGI? Sure, you can rewrite everything from scratch using only interrups and inline assembly code and no standard library stuff at all.

But the OP was asking for libraries, as far as I can remember.....

Then what is egavga.bgi? And all the other BGI? Sure, you can rewrite everything from scratch using only interrups and inline assembly code and no standard library stuff at all.

But the OP was asking for libraries, as far as I can remember.....

Egavga.bgi is a graphics library from borland. As far as I can remember it has no support for sprites or anything even remotely related to game programming.

http://www.cs.colora.../~main/bgi/doc/

From what I see except functions for windows (marked with WIN) there's only one function to output an image on screen: putimage
Putimage doesn't support transparency so you have to use putpixel and that is a performance killer.

I assume we are talking about DOS programming on a DOS emulator for a target machine whose clock speed is between 25 and 66Mhz. If a 1Ghz CPU is required to run a DOS game then there's something I'm clearly missing. Also there's a grey area between writing the most inefficient code possible and writing code running as fast as tim sweeney's one.

Honestly I can't see so much difference between calling:

color = getpixel(xsprite,ysprite)
if( color != TRANSP_COLOR )
putpixel(x,y,color);

and:

color = m_aMSprite[ysprite*SPRITE_WIDTH+xsprite] ;
if( color != TRANSP_COLOR )
m_aBackBuffer[y*320+x] = color;

Or even better calculate starting pixels outside a double for loop this way:

pBackBuffer = &m_aBackBuffer[y*320+x];
pSprite = &m_aMSprite[ysprite*SPRITE_WIDTH+xsprite] ;

and inside the two for loops:

if( *pSprite != TRANSP_COLOR)
*pBackBuffer = color;
pBackBuffer++;
pSprite++;


Even if these are just four lines of code it is likely on a 386 they run a lot faster. Because function calls are killers when it comes down to realtime graphics on a 386 using an old C/C++ compiler.

Let me do the math on an extreme case: you copying a full screen buffer on screen using putpixel or stosw (I don't have stosd timing on my 386 manual but I assume it's the same). Just to make it clear how slow those machines were when it comes down to function calls.

A simple pair of PUSHA/POPA (needed to preserve registers for a function call) takes a lot of cycles.

I have an old 286/386 manual referencing clock cycles needed for each x86 op:

PUSHA 18 cycles
POPA 24 cycles
MOV takes between 2 and 22 cycles.

So every function call has to push and pop all general registers and make 2 movs to read and write the pixel color.
18+24+2+2 = 46 cycles per pixel.
And you'll surely need other 2 movs to configure es and ds and other 2 for di and si.
If we assume MOV takes 2 cycles (which is NOT always the case) then it's 54 cycles per pixel.
And you have a function call (CALL) taking 3-275 clock cycles and a return instruction (RET) taking 10-68 clock cycles!!!!!!!!!!!!!!
Suppose everything is superfast, so let's add just 13 cycles. I'm not even counting the for loop.

64000*67 = 4288000

Best case: more than four million cycles. That means on a 25Mhz 80386 CPU, performing 25 million cycles per second, there's no way you can get more than 5-6FPS just for copying a background if you use borland's putpixel. Extreme case, I know.

On the other hand with stosw you copy two pixels at a time so it's 32000 stosw operations for a 320x200 screen.

Configure es and ds, si and di = same 8 cycles (4 MOVs) but you do it once.
MOV cx, 32000 other 2 cycles we are at 10 cycles for configuring our copy.
And now
rep stosw
stosw is 4 cycles on a 386, 3 cycles on a 286.
rep is 5 cycles for config and 9 cycles for each iteration.

So we end up with:

15 cycles for configuration
13 cycles every 2 pixels for 32000 times.

Total cycles = 32000*13+15 = 416015 cycles.

In performance that would mean:
putpixel = 5,83 FPS
asm = 60,09 FPS

Assuming stosd still takes 4 cycles (which might be the case as stosw and stosb take the same amount of clock cycles), we have 16000*13+15 = 208015

A stosd can copy the back buffer at a 120,2 FPS rate... 24x faster than any putpixel you can write, no matter how skilled you are, at the cost of inlining 10 lines of asm code!

Also, you get an extra bonus: with rep and stosd the timing is 100% predictable just the setup can vary from 13 to 500 clock cycles.
The same doesn't apply to mov, ret and call ops as you could end up spending up to 500-600 clock cycles for some pixels!!!!!

The reason why libraries weren't so popular is the CPU was so slow you had to do it yourself everytime as even the cost of calling a function many times on a 386 is too high for a realtime application.

I think trying to do proper DOS programming in 21st century is worthy exactly for this reason: doing things the old school way people can learn programming with a different mindset.

:)

This topic is closed to new replies.

Advertisement