home *** CD-ROM | disk | FTP | other *** search
/ Programmer 7500 / MAX_PROGRAMMERS.iso / PASCAL / MCGA#05.ZIP / LESSON05.TXT < prev    next >
Encoding:
Text File  |  1992-06-05  |  11.4 KB  |  380 lines

  1.                            MCGA Graphics Tutorial
  2.                                  Lesson #5
  3.                                 by Jim Cook
  4.  
  5. Welcome to the fifth installment of the MCGA tutorial.  Those of you
  6. that have followed this tutorial since its inception realize that I have
  7. really slacked off lately.  I will try to make it up to you.  Of course,
  8. since I can't give you all cash, I am offering code.  I wish to
  9. start getting into some animation techniques.
  10.  
  11. The basis for any animation is simply:
  12.  
  13.         Save Background -> Put Image -> Repeat
  14.  
  15. In MCGA #4 we learned about the PCX format and how to put an entire PCX
  16. or even a smaller PCC on the screen.  Next we will learn how to cut out
  17. a section of the screen (usually the background) and then redisplay it.
  18. Those of you familiar with Borland's BGI are saying, "Sure, GetImage and
  19. PutImage!"  You are exactly correct, except Borland's BGI lacks one
  20. thing...SPEEED!!!  It is not directly their fault, since they did not
  21. write GRAPH.TPU with SPEEED in mind, but rather flexibility.  What we
  22. will do is implement our own version of GetImage and PutImage.
  23.  
  24. The first thing we need to do, is allocate some memory to put this piece
  25. of the screen we will be cutting out.  Our experience with PCX files
  26. reminds us that before we allocate memory we need to determine how much
  27. memory we need.  This is a very simple technique because each pixel in
  28. MCGA is equivalent to one byte of video memory (see MCGA #2), hence:
  29.  
  30.         Function ImageSize (X1,Y1,X2,Y2:Integer) : Word;
  31.         begin
  32.           ImageSize := Word(Y2 - Y1 + 1) * Word(X2 - X1 + 1) + 4;
  33.         end;
  34.  
  35. You might be asking yourself, "Why add four?".  Well, along with the
  36. picture data (num pixels high * num pixels wide), we are going to store
  37. the width and height of the saved image.  You will notice in the
  38. declarations of PutImage we only specify the upperleft corner of where
  39. we want to place the image.  This is because the height and width are
  40. stored along with the image data.
  41.  
  42.         Procedure GetImagePas  (X1,Y1,X2,Y2:Integer;P:Pointer);
  43.         Procedure PutImagePas  (X1,Y1:Integer;P:Pointer);
  44.  
  45. The routines themselves aren't terribly difficult.
  46.  
  47.    type
  48.       PointerType =  array [0..65500] of byte;
  49.       NewPointer  =  ^PointerType;
  50.  
  51.     Procedure GetImagePas (X1,Y1,X2,Y2:Integer;P:Pointer);
  52.         var
  53.           I           :  Integer;
  54.           Count,
  55.           ScnPos,
  56.           Wide,High   :  Word;
  57.   8:      Buf         :  NewPointer absolute P;
  58.         begin
  59.   1:      Wide   := (X2 - X1) + 1;
  60.           High   := (Y2 - Y1) + 1;
  61.   2:      ScnPos := (Y1 * 320) + X1;
  62.           Count  := 4;
  63.  
  64.   3:      Move (Wide,Buf^[0],SizeOf(Wide));
  65.           Move (High,Buf^[2],SizeOf(High));
  66.  
  67.   4:      For I := 1 to High do begin
  68.   5:        Move (Mem[$A000:ScnPos],Buf^[Count],Wide);
  69.   6:        Inc (ScnPos,ScreenWide);
  70.   7:        Inc ( Count,Wide);
  71.             end;
  72.         end;
  73.  
  74. 1:  Calculate the width and height of the image.
  75. 2:  Calculate the screen offset in video memory where the image begins
  76. 3:  Store the width and height of the image as the first two words
  77.     in the buffer.  Note how this is done.  You may be wondering why I'm
  78.     referring to Buf, when the pointer that was passed is called P.  Take
  79.     a look at 8:.  There, I absolute a different data type on P.  You can
  80.     think of "absolute" like laying a new type of data on top of an
  81.     existing one.  In Pascal, you can't refer to a specific offset
  82.     pointed to by a pointer like you can in C.  I could of said:
  83.             Move (High,Ptr(Seg(Buf^),Ofs(Buf^)+2)^,SizeOf(High));
  84.     But I choose the former method for obvious clarity.
  85. 4:  Next we set up a loop for each row
  86. 5:  Here is the meat of the routine.  The first time through the loop,
  87.     Mem[$A000:ScnPos] refers to the address of the first pixel in the
  88.     section we are cutting out.  Actually, copying out is a better term.
  89.     Count keeps track of where in the storage buffer we are currently
  90.     positioned.  So we are moving an entire row (WIDE) of pixels from
  91.     video memory and into our storage buffer.
  92. 6:  Next we point our screen position to the beginning of the next row.
  93. 7:  And increment our storage buffer pointer to the next free position.
  94.  
  95. I hope that explains it well enough.  If there are parts you don't
  96. understand. it would probably do you some good to reread MCGA #2.
  97.  
  98. I detest what I am about to write and I'll tell you why.  Have you ever
  99. repaired your car or truck with a socket wrench in one hand and a
  100. Chilton's manual in the other?  After you have taken the cylinder head
  101. off your car and replace some intricate part they always state,
  102. "To replace the part, follow the reverse of the original procedure."
  103. I hate that!  To round out my hypocrisy, I am about to do the same to
  104. you.  At least you can post a message if it is not clear:
  105.  
  106.         Procedure PutImagePas (X1,Y1:Integer;P:Pointer);
  107.         var
  108.           I           :  Integer;
  109.           Count,
  110.           ScnPos,
  111.           Wide,High   :  Word;
  112.           Buf         :  NewPointer absolute P;
  113.         begin
  114.           ScnPos := (Word(Y1) * 320) + Word(X1);
  115.           Count  := 4;
  116.  
  117.           Move (Buf^[0],Wide,SizeOf(Wide));
  118.           Move (Buf^[2],High,SizeOf(High));
  119.  
  120.           For I := Y1 to Y1+High-1 do begin
  121.             Move (Buf^[Count],Mem[$A000:ScnPos],Wide);
  122.             Inc (ScnPos,ScreenWide);
  123.             Inc ( Count,Wide);
  124.             end;
  125.         end;
  126.  
  127. Okay, now add these routines to your MCGALIB unit.  Following is a
  128. program to test the effectiveness of the procedures.  I have benchmarked
  129. (and I use the term loosely) the copying of a 60 by 40 chunk of screen at
  130. 325 displays per second.  I have also achieved 15 full screens per
  131. second with these routines:
  132.  
  133. Program MCGATest;
  134.  
  135. uses
  136.   Crt,Dos,MCGALib;
  137.  
  138. var
  139.   Stop,
  140.   Start       :  LongInt;
  141.   Regs        :  Registers;
  142.   PicBuf,
  143.   StorageBuf  :  Pointer;
  144. const
  145.   NumTimes    = 1000;
  146.  
  147. Procedure LoadBuffer (S:String;Buf:Pointer);
  148. var
  149.   F           :  File;
  150.   BlocksRead  :  Word;
  151. begin
  152.   Assign (F,S);
  153.   Reset (F,1);
  154.   BlockRead (F,Buf^,65000,BlocksRead);
  155.   Close (F);
  156. end;
  157.  
  158. Function Tick : LongInt;
  159. begin
  160.   Regs.ah := 0;
  161.   Intr ($1A,regs);
  162.   Tick := Regs.cx shl 16 + Regs.dx;
  163. end;
  164.  
  165. Procedure ShowAndTell;
  166. var
  167.   Ch               :  Char;
  168.   NumSecs,
  169.   NumTicks,
  170.   SecsPerIter,
  171.   TicksPerSec,
  172.   TicksPerIter     :  Real;
  173. begin
  174.   TextMode (3);
  175.   NumTicks     := Stop - Start;
  176.   NumSecs      := NumTicks / 18.2;
  177.   TicksPerIter := NumTicks / NumTimes;
  178.   SecsPerIter  := NumSecs  / NumTimes;
  179.   TicksPerSec  := 18.2     / TicksPerIter;
  180.  
  181.   Write   ('After ',NumTimes,' iterations ');
  182.   WriteLn ('and ',NumSecs:6:4,' seconds...');
  183.   Write   ('  Each iteration took ',TicksPerIter:6:4,' ticks or ');
  184.   WriteLn (SecsPerIter:4:3,' seconds!');
  185.   WriteLn ('  That''s about ',TicksPerSec:6:4,' times per second.');
  186.   Repeat Until Keypressed;
  187.   While Keypressed do Ch := Readkey;
  188. end;
  189.  
  190. Procedure Control;
  191. var
  192.   I,X,Y :  Integer;
  193.   Size  :  Word;
  194. begin
  195.   SetGraphMode ($13);
  196.   LoadBuffer ('E:\NAVAJO.PCX',PicBuf);
  197.  
  198.   DisplayPCX (0,0,PicBuf);
  199.  
  200.   Size := ImageSize (40,60,100,100);
  201.   GetMem (StorageBuf,Size);
  202.  
  203.   GetImagePas (40,60,100,100,StorageBuf);
  204.  
  205.   ClearScreen (0);
  206.  
  207.   Start := Tick;
  208.   For I := 1 to NumTimes do begin
  209.     X := Random (280);
  210.     Y := Random (140);
  211.     PutImagePas (X,Y,StorageBuf);
  212.     end;
  213.   Stop := Tick;
  214.   ShowAndTell;
  215. end;
  216.  
  217. Procedure Init;
  218. begin
  219.   GetMem (PicBuf,65500);
  220. end;
  221.  
  222. Begin
  223.   Init;
  224.   Control;
  225. End.
  226.  
  227. I would like to take a minute to pose a puzzlement.  The pascal Get and
  228. Put routines make extensive use of the Move command.  The Move command
  229. copies data from the Source pointer to the Destination pointer one byte
  230. at a time.  In the interest of SPEEED, I turned on 286 code in my
  231. compiler options box, expecting move to start moving data a word at
  232. time, but found no increase in SPEEED.  I guess since this is not an
  233. optimizing compiler, the MOVSW command is not substituted for even
  234. amounts of MOVSB's.  So.......the next best thing is writing our own:
  235.  
  236.         Procedure Move (Var Source,Dest;Count:Word);
  237.         begin
  238.           asm
  239.             push ds
  240.             lds  si,Source
  241.             les  di,Dest
  242.             mov  cx,Count
  243.             shr  cx,1
  244.             rep  movsw
  245.  
  246.             mov  cx,Count
  247.             test cl,1
  248.             jz   @@EvenCount
  249.             movsb
  250.           @@EvenCount:
  251.             pop  ds
  252.           end;
  253.         end;
  254.  
  255. After adding this procedure at the beginning of the MCGALIB unit, my
  256. speeeds were increased 20%!  I would have to say that this is as close
  257. to top speeed that Pascal gets. Soooo, as usual, we move our procedures
  258. into the realm of assembly language and enjoy even faster processing.
  259. Take a look:
  260.  
  261.         Procedure GetImageAsm (X1,Y1,X2,Y2:Integer;P:Pointer); assembler;
  262.         asm
  263.             mov  bx,ScreenWide
  264.             push ds
  265.             les  di,P
  266.  
  267.             mov  ax,0A000h
  268.             mov  ds,ax
  269.             mov  ax,Y1
  270.             mov  dx,320
  271.             mul  dx
  272.             add  ax,X1
  273.             mov  si,ax
  274.  
  275.             mov  ax,X2
  276.             sub  ax,X1
  277.             inc  ax
  278.             mov  dx,ax
  279.             stosw
  280.  
  281.             mov  ax,Y2
  282.             sub  ax,Y1
  283.             inc  ax
  284.             stosw
  285.             mov  cx,ax
  286.  
  287.           @@1:
  288.             mov  cx,dx
  289.  
  290.             shr  cx,1
  291.             rep  movsw
  292.  
  293.             test dx,1
  294.             jz   @@2
  295.             movsb
  296.           @@2:
  297.             add  si,bx
  298.             sub  si,dx
  299.  
  300.             dec  ax
  301.             jnz  @@1
  302.  
  303.             pop  ds
  304.         end;
  305.  
  306.         Procedure PutImageAsm (X1,Y1:Integer;P:Pointer); assembler;
  307.         asm
  308.             mov  bx,ScreenWide
  309.             push ds
  310.             lds  si,P
  311.  
  312.             mov  ax,0A000h
  313.             mov  es,ax
  314.             mov  ax,Y1
  315.             mov  dx,320
  316.             mul  dx
  317.             add  ax,X1
  318.             mov  di,ax
  319.  
  320.             lodsw
  321.             mov  dx,ax
  322.  
  323.             lodsw
  324.  
  325.           @@1:
  326.             mov  cx,dx
  327.  
  328.             shr  cx,1
  329.             rep  movsw
  330.  
  331.             test dx,1
  332.             jz   @@2
  333.             movsb
  334.           @@2:
  335.             add  di,bx
  336.             sub  di,dx
  337.  
  338.             dec  ax
  339.             jnz  @@1
  340.  
  341.             pop  ds
  342.         end;
  343.  
  344. Following are the times I realized on a 386 33mhz Packard Bell.  The
  345. measurements are in iterations per second.  I broke the times to show
  346. the slight difference between Pascal and assembly code and the larger
  347. difference between word moves and byte moves:
  348.  
  349.    Image Size              Pascal               Assembler
  350.  
  351.                        Byte     Word          Byte     Word
  352.  
  353.    100 x 100            89      115           102      147
  354.  
  355.    320 x 200            15       29            16       32
  356.  (full screen)
  357.  
  358. Also note, that the full screen word moves approach the speeds necessary
  359. for full screen motion, which is 30 frames per second.  In the assembly
  360. source we actually exceed that standard.
  361.  
  362. So now that we are blazing fast....what next?  Stay tuned for MCGA #7.
  363. We are close to building an animation model.  I think the next thing we
  364. need to cover is page flipping and since this video mode only supports
  365. one video page, we will have to come up with another method.  You can
  366. bet it will have something to do with word moves!
  367.  
  368. MCGA #6 is a listing showing the MCGALIB we have created thus far.
  369.  
  370. jim
  371.  
  372.  
  373.  
  374.  
  375.  
  376.  
  377.  
  378.  
  379.  
  380.