


                  |====================================|
                  |                                    |
                  |   TELEMACHOS proudly presents :    |
                  |                                    |
                  |    Part 5 of the PXD trainers  -   |
                  |                                    |
                  |        SVGA using VESA 1.2         |
                  |                                    |
                  |                                    |
                  |====================================|

         ___---__-->   The Peroxide Programming Tips   <--__---___

<><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>


Intoduction
-----------

Hi folks... It's been a while, I know, but I have been busy living my REAL
life :)  (YEAH, I know : REAL LIFE SUXX!)

There have been quite a few people mailing me about my last two tutorials and
one of them made me discover a little error in my gouraud routines.
Actually it's not an error - I just presume that there is a maximum of 64
colors in the shading table. If using more a few variables / regs will overflow
resulting in graphical errors. The problems can be fixed by reducing the
shl 8 statement to shl 7 when calculating the fixed point step values.
Then later on when the vars has been calculated as 9.7 fixed point values you
just have to multiply them by two to get them back to 8.8 fixed point.
Now things should work correct.
Also, in the gouraud polygonside scanner change the type of the variable
"color" from integer to word.

This tutorial will be on SVGA graphic using the VESA 1.2 BIOS implemention.
In the past I have seen quite a few shareware SVGA programming libs - but so
far all of them has sucked pretty much. Either they presume a granularity of
64K - or they are written in pure asm without any decent comments - making
them virtually impossible to learn from.
Or they use BIOS calls to switch banks which suck just as much as code without
comments.
So what we'll do is to build a small pascal/asm SVGA lib that supports *ALL*
SVGA cards on the market (at least we can try :) ).
Also I'll try to explain everything about SVGA in a way so that the reader
actually understands VESA-coding after reading this text.
If you are bored with theory stop reading right now and skip to the headline
called "THE CODE". But I strongly recommend you to read the entire text -
otherwise you'll be coding VESA graphics in half an hour but not understand
what you're doing :)

BTW. Some lamer seems to find it extremely funny to reset the hit-counter on
my homepage. If you're reading this : Please don't do it again - it makes me
very unhappy and I lie fever-striken on my bed for several days each time
it happen.
If you really enjoy resetting counters THAT much drop me a mail and I'll code
you an internet-hit-counter-resetter-simulater FOR FREE!!!!!!!
If any of you NON-lamers who reads this - you know, the kind of people who
spend their time actually CODING instead of resetting counters, spreading
virusses and beating their little brothers - I would like to know if any of
you know how to make a hit-counter that cannot be reset by the weak-minded
people of the net.


If you want to get in contact with me, there are several ways of doing it :

1) E-mail me               :      tm@image.dk

2) Snail mail me           :      Kasper Fauerby
                                  Saloparken 226
                                  8300 Odder
                                  Denmark

3) Call me  (Voice ! )     :    +45 86 54 07 60


Get this serie from the major demo-related FTP-sites - currently :

  GARBO ARCHIVES  (forgot the address)  :  /pc/programming/

  ftp.teeri.oulu.fi  :   /msdos/programming/docs/

  ftp.cdrom.com      :  something with demos/incomming/code.....

Or grap it from my homepage :

   Telemachos' Codin' Corner   http://www.image.dk/~tm



WHAT IS VESA AND WHY DO WE NEED IT ?
------------------------------------

Well... as you all know the standard mode $13 is a linear mode taking up
64000 bytes of memory. This fits into a single segment and therefore it is
extremely easy to code. When mode $13 was introduced all agreed on using
segment $a000 as base for the mode. Therefore our mode $13 code will work on
EVERY SINGLE VGA-card available.
When SVGA was introduced for the first time it was IBM who developed a very
expensive GFX-card capable of doing extremely high resolutions - for that time,
that is.
But IBM never released the register information for programming the card.
In time other companies developed SVGA cards - each with their own way of
laying out the gfx-cards registers. The result was that it became almost
impossible to code applications for SVGA - if the application was to support
the different gfx cards the programmers had to do routines for EVERY SINGLE
gfx card on the market. The result was that most programmers chosed not to
support SVGA at all. The high resolution was'nt worth the time spend on doing
the code.
VESA saw the problem and came up with a BIOS extension that supplied some
standard routines for coding the different SVGA cards available at the time
being.
For a long time it has been VESA 1.2 that has been the standard in SVGA coding.
But now UniVBE 5.3 has been released making VESA 2.0 available on almost every
graphic card out there.
But for us pascal coders VESA 2.0 is pretty uninterresting. First of all :
The main improvement in VESA 2.0 if something called Linear Frame Buffer (LFB)
This means that you map your entire VGA-memory into one single linear buffer.
Then you can adress each pixel on the SVGA screen lineary - just as in good
old mode $13. But this is obviously only available to those coding in protected
mode - and I take it that most of my readers are'nt.
Another thing is that most gfx cards out there only has VESA 1.2 installed -
so in order to use VESA 2.0 you'll have to assume that the user owns UniVBE 5.3
So for now we'll stick to good old VESA 1.2


THE WORKING OF A SVGA CARD - THE THEORY FOR OUR CODE
-----------------------------------------------------

As mentioned before each pixel takes up 1 byte of memory. In mode $13 this is
OK as the screen is only 320 X 200 = 64000 bytes. This way the video memory
needed for a single screen of graphic fits into one single segment.
But a SVGA mode takes up much more space. Lets take the standard 640X480 SVGA
mode. This mode takes up 640 X 480 = 307200 bytes = 4,6875 segments of RAM.
Hmm... how do we adress all this memory. The modenr for this mode is $101
Try and set this mode using the following VESA code : (I'll explain the code
later on)

asm
 mov ah,4Fh
 mov al,02h
 mov bx,$101
 int 10h
end;

Now try and address the video mem via the good old $a000 segment.... Yes, just
fill the entire segent with some color. Woila! You have just plotted your first
pixels in 640 X 480 X 256. But as you might have discovered you are limited to
accessing only the uppermost part of the screen. The narrow band of graphic you
see takes up exactly 1 segment of memory.
What you have just discovered is the greatest pain in SVGA programming :
the need of windowing. Imagine the entire SVGA-memory laid out lineary. You can
form it to many different sizes. One size is 640 pixels wide, another 800 and
yet another 1024. But all the time you only have access to 1 segment of the
memory. Think of this segment as a window you can look through into the entire
video memory. Fortunately you can move this window to many different positions
in the video memory. If you move it around enough you'll discover that you can
see every byte in video memory through it - just only one segment at a time.

We call all those different window positions for banks. Well, VESA does provide
us with standard procedures to set the graphic modes, test them and move the
bank window but still SVGA is a little complicated. First of all there is
the GRANULARITY of the video card. The granularity decides how the window can
be moved around in the video memory. A granularity of 64K means that the window
can be moved only in 64K chunks - ie. an entire window at a time. Some cards
have finer granularity - Cirrus Logic fx. has a granularity of 4K which allows
the windows to overlap. Each window position is called a bank even though some
of the memory can be accesed through multiple window positions.
The window size is an entirely different matter from the granularity. On most
cards the window size is 64K (actually on every card I have seen so far) but
some cards implement 128K. This is however not important to us as the greatest
granularity is 64K - so as long the window is not below 64K in size we can
acces all memory by moving the window.

It is pretty safe to assume that the video memory is accesed from the $a000
segment but to be sure one should always test this by making a BIOS call before
trying to write to memory. Some cards have multiple windows - We call them
window A and Window B. This is because some cards have ReadOnly access in one
window and WriteOnly in another. So this is another thing to check : Can we
do both our reads and writes in the same window ?



THE VESA FUNCTIONS EXPLAINED
-----------------------------

All VESA functions is reached from a sub-function of the BIOS interrupt $10.
This subfunction is 4Fh so in all interrupt calls we must load the ah register
with 4Fh and the al register with the VESA function number we want.

I'll just run over the different VESA BIOS functions and then do the code in
the CODE section.

Function 00h -  Return SVGA info
---------------------------------

Input :      AH  : 4Fh
             AL  : 00h
          ES:DI  : Pointer to 256bytes buffer.

Returns :    AX  : Status


The status register is set up as follows :

        AL == 4Fh:      Function is supported
        Al != 4Fh:      Function is not supported
        AH == 00h:      Function call successful
        AH == 01h:      Function call failed

Now, as we see we load ah with our VESA subfunction number and AL with the
VESA function number. ES:DI must point to a 256bytes buffer. This can be any
buffer of this size. Fx. buffer : Array[0..255] of byte;
But using a buffer like this leaves us with an enormous amount of converting
from byte to word and so on... So I'll give you a structure to use instead :


TYPE

 ListOfAvailModesT = Array[0..255] of word; {terminated by -1 ($FFFF) }
 ListOfAvailModesP = ^ListOfAvailModesT;

 VESAInfoT = record
              VESASignature : array[0..3] of byte;
              VESAVersion   : word;
              OEMStringPtr  : Pchar;
              Capabilities  : array[0..3] of byte;
              VideoModePtr  : ListOfAvailModesP;
              TotalMemory   : word;
              Reserved      : Array[0..235] of byte;
             end;

The VESASignature is 4 bytes and when converted to a string they must form the
word 'VESA'. Otherwise it is not a valid VESA-structure.

The VESAVersion is a word with the high-byte being the major version number and
the low byte being the minor version number.

OEMStringPtr is a pointer to a 0-terminated String which contains the ident
string of the SVGA card. But we are in luck - in pascal the type Pchar means
a pointer to such a string, so we don't have to think any more about this one.

Capabilities is mostly unused. If bit 0 in the first byte is set it means that
the DAC can be reprogrammed to another DAC width (standard is 6-bit)

VideoModePtr : Now this is a little tricky. This is a pointer to a list of
words - each word containing a valid graphic mode on the card. The list is
terminated by -1 ($FFFF).

TotalMemory contains the number of 64K blocks of RAM installed on the card.

The reserved field is reserved for future VESA expansions.


This function is the first function your application should run for checking
if VESA is available at all.



Function 01h - Return VESA MODE information
-------------------------------------------

Input :        AH = 4Fh
               AL = 01h
               CX = Mode number (must be from the mode list from func 00h)
            ES:DI = pointer to 256 bytes buffer

Returm         AX = Status


Again I'll give you a structure to use for the 256 bytes buffer. I'll leave out
some fields for true-color stuff that we will not discuss in this text.

TYPE

 ModeAttributesT = (Available,
                   Reserved,
                   BIOSFunctionsSupport,
                   color,
                   graphic,
                   bit5,
                   bit6,
                   bit7,
                   bit8);


 WindowAttributesT  = (Supported,R,W);   {rest of the bits are unused...}


 VESAModeInfoT = record
                   ModeAttributes     : set of ModeAttributesT;
                   WinAAttributes     : set of WindowAttributesT;
                   WinBAttributes     : set of WindowAttributesT;
                   WinGranularity     : word;
                   WinSize            : word;
                   WinASegment        : word;
                   WinBSegment        : word;
                   BankSwitch         : procedure;
                   BytesPerScanLine   : word;

                   {Xtended Information}
                   XResolution        : word;
                   YResolution        : word;
                   XCharSize          : byte;
                   YCharSize          : byte;
                   NumberOfPlanes     : byte;
                   BitsPerPixel       : byte;
                   NumberOfBanks      : byte;
                   MemoryModel        : byte;
                   BankSize           : byte;
                   NumberOfImagePages : byte;

                   Reserved           : Array[0..225] of byte;
                   {reserved is for something we won't think about now...
                    true color and stuff...                               }
                 end;


WOW.... This sure is a tough one to get through. Well.. to start from the top.

ModeAttributes : This is a word with every bit meaning something different.
That's why I made the ModeAttributesT. It contains different information about
the mode from CX. If bit 0 is set the mode is available - if not : Your guess.
The rest of this field should be obvious from the names in ModeAttributes :
is it a color mode ? Is it a graphic or text mode? The BIOSSupport field
decided if you can use BIOS scroll, TTY output and BIOS pixel output in the
selected mode.

WinAAttributes and WinBAttributes : These are pretty important as they tell you
if you can read or write to the two windows (A and B). Some cards has only one
window availble so a window CAN be Read AND Writeable.


WinGranularity : This field contains the granularity in KB.

WinSize : The Size of the window in KB.

WinASegent and WinBSegment : This decides where in memory the window base is
placed. Usually WinASegment will be $A000 - but check it out to be sure.
If a window reports $0000 as address DON'T use that address :)

BankSwitch : This is a pointer to the hardware bankswitch function. A far call
can be made to this address for quick bankshifting. Lucky us - pascal allows us
to use the type "procedure" here so we don't have to worry about the pointer
stuff... just call this field from an assembler routine. More on this facility
under function 05h later on.

BytesPerScanLine : The logical number of bytes pr y-line in memory. This is
usually the same as the maximum x-value in the graphic mode.

The Xtended fields should be pretty self-explaining. One word of warning though.
I have experienced some problems with the NumberOfBanks and BankSize fields on
my Cirrus Logic card. Don't trust these fields blindly.


function 02 - Set VESA mode
----------------------------

Input :       AH = 4Fh
              AL = 02h
              BX = Video Mode
                   bit 15 : 1 = Don't clear video RAM
                            0 = Clear video RAM


Returns :     AX = Status



OK.. Use this one to set the VESA modes. The modes is as follows :


                GRAPHICS                                TEXT

15-bit   7-bit    Resolution   Colors   15-bit   7-bit    Columns   Rows
mode     mode                           mode     mode
number   number                         number   number
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
100h     -        640x400      256      108h     -        80        60
101h     -        640x480      256
                                        109h     -        132       25
102h     6Ah      800x600      16       10Ah     -        132       43
103h     -        800x600      256      10Bh     -        132       50
                                        10Ch     -        132       60
104h     -        1024x768     16
105h     -        1024x768     256

106h     -        1280x1024    16
107h     -        1280x1024    256

10Dh     -        320x200      32K   (1:5:5:5)
10Eh     -        320x200      64K   (5:6:5)
10Fh     -        320x200      16.8M (8:8:8)
110h     -        640x480      32K   (1:5:5:5)
111h     -        640x480      64K   (5:6:5)
112h     -        640x480      16.8M (8:8:8)
113h     -        800x600      32K   (1:5:5:5)
114h     -        800x600      64K   (5:6:5)
115h     -        800x600      16.8M (8:8:8)
116h     -        1024x768     32K   (1:5:5:5)
117h     -        1024x768     64K   (5:6:5)
118h     -        1024x768     16.8M (8:8:8)
119h     -        1280x1024    32K   (1:5:5:5)
11Ah     -        1280x1024    64K   (5:6:5)
11Bh     -        1280x1024    16.8M (8:8:8)



Function 03h - Return current Video Mode
-----------------------------------------

Input :          AH = 4Fh
                 AL = 03h

Returns :        AX = Status
                 BX = Current Video mode


Not much to say about this one I guess...



Function 05h - Set Window position / Bank Switch
-------------------------------------------------

Input :          AH = 4Fh
                 AL = 05h
                 BH = 00h
                 BL = Window nr
                       Window A = 0
                       Window B = 1
                 DX = Window position in granularity units.
                      This means bank number.

Returns :        AX = Status


Now, this routine sets the window position that we talked about earlier. The
key to making a good VESA unit is to make certain that it is the correct
bank numbers that is passed to this function. On a SVGA card with a granularity
of 64K each bank matches one segment of memory and one potion of the screen.
If we want to clear out Screen we usually selects bank nr 0 and then fill
the $A000 segment with one color. Then we want to move on to the next section
of the screen. So we increase our banknr by 1 and do the same trick. But WAIT
A SECOND!! This will ONLY work with granunarities of 64K. 'Cause on cards with
a granularity of fx. 4K we might have set the window to bank 0. And we might
have filled the entire window - but by doing this we have filled SEVERAL banks!
With a granularity of 4K we have filled 64K / 4K = 16 banks! So the next bank
we should set should NOT be 1 but 16!
This is where most errors I have seen in SVGA libs lies.

As we all know calling BIOS is pretty SLOW! And with small granularities we
might have to do it MANY times during a screen-update so we're pretty lucky
that we have the brain to use the far function address we got from function
01h.
When using it we don't have to set the AX register before call. Just the BX
and DX register. Beware though : Both AX and DX is destroyed in the call so
be sure to save the information in them if you wanna reuse them.


Function 06h - Set/Get logical Scanlenght
------------------------------------------

Input :        AH = 4Fh
               AL = 06h
               BL = 00h {Set scanlength}
               CX = Desired width in pixels

Returns :      AX = Status
               BX = Bytes per Scanline
               CX = Actual pixels per Scanline
               DX = Maximum number of scanlines based on mode and memory

Input :        AH = 4Fh
               AL = 06h
               BL = 01h {Get scanlength}

Returns :      AX = Status
               BX = Bytes per Scanline
               CX = Actual pixels per Scanline
               DX = Maximum number of Scanlines.


Now this function can be VERY useful. It allows you to set a logical width
in video memory. It is based on this width that you calculate the offset for
a pixel - just like in mode $13h.
If you set this value to a power of 2 you can make sure that there will never
be any bank-crossing along a horizontal scanline. This can speed up your code
if you update the screen Scanline per Scanline 'cause you only have to do a
bank check once per Scanline - and not once per pixel as you would in fx. mode
640 X 480!
Also you can use function 07h to place the actual screen anywhere in this
logical memory and this way do hardware scrolls!
If you plan on using 640 X 480 and you know that your video card has 1MB of RAM
installed you could set the logical scanlength to 1024 and thereby ease the
offset calculation from :

   ofs := y shl 9 + y shl 7 + X

to

   ofs := y shl 10 + X;


It's a waste of memory, but if you are smart you store something in the memory
outside the screen.


Function 07h -  Set / Get display start
----------------------------------------

Input :         AH = 4Fh
                AL = 07h
                BH = 00h
                BL = 00h   {set display start}
                CX = Xpos
                DX = Ypos

Returns :       AX = Status


Input :         AH = 4Fh
                AL = 07h
                BL = 01h  {get display start}

Returns :       AX = Status
                BH = 00h
                CX = Xpos
                DX = Ypos


This one sets the position of the screen in video memory.


Function 08h - Set/Get DAC pallette control
--------------------------------------------

Input :        AH = 4Fh
               AL = 08h
               BL = 00h  {Set DAC pallette width}
               BH = desired number of bits per primary color

Returns :      AX = Status
               BH = Current number of bits per primary color

Input :        AH = 4Fh
               AL = 08h
               BL = 01h   {get DAC pallette width}

Returns :      AX = Status
               BH = Current number of bits per primary color


This function sets the DAC pallette width - check if this function is
available by checking bit 0 in the Capabilities field in VESAInfo.


THE CODE :
-----------

First lets get the VESA information and save it in a variable called
VESAInfo :


FUNCTION GetVESAInfo : boolean;
Assembler;
asm
 mov ah,4Fh          {The usual VESA sub-function number}
 mov al,00h          {This is VEAS function 00h         }
 lea di,VESAInfo     {load the address of VESAInfo into es:di}
 int 10h             {call the BIOS interrupt}
 cmp ah,0            {ah returns 0 if succes}
 jne @fail
 mov ax,1
 jmp @out
@fail:
 mov ax,0            {the ax register will be passed to the function}
@out:
end;


The same way we get the Info for the mode we wanna set :


FUNCTION GetVESAModeInfo(mode : word) : boolean;
Assembler;
asm
 mov ah,4Fh
 mov al,01h               {VESA function 01h}
 xor cx,cx
 mov cx,mode              {load the desired mode in cx}
 lea di,VESAModeInfo      {load the address to VESAModeInfo into es:di}
 int 10h                  {call BIOS video interrupt}
 cmp ah,0
 jne @fail
 mov ax,1
 jmp @out
@fail:
 mov ax,0                 {we put the result in ax }
@out:
end;



OK... Now we need to be able to switch the banks :


Procedure SetBank(nr : word);
Assembler;
asm
 xor bl,bl                         {Window A selected}
 mov dx,nr
 call [VESAModeInfo.BankSwitch]    {here we call the bankSwitch procedure}
 mov dx,nr                         {dx is destroyed by far procedure call}
 mov cur_page,dx                   {update global page variable}
end;


Now this routine assumes that Window A is Read/Writeable. BX desides which
window is active bl = 0 means Window A and bl = 1 means Window B.
It is a good idea to have a global variable that keeps track of which bank is
active at the moment so you don't change to the bank that is allready active!


This done I think we need to set the mode up :


FUNCTION SetVESAMode(mode : word) : boolean;
Assembler;
asm
 mov ax,03h
 int 10h              {start from textmode}

 mov ah,4Fh
 mov al,02h
 mov bx,mode
 int 10h
 cmp ah,0
 jne @fail           {set the VESA mode through VESA function 02h}

 mov ah,4Fh
 mov al,05h
 xor bx,bx
 mov dx,0
 int 10h             {set bank 0 from BIOS call this time - to be safe }
 mov cur_page,0      {initialize global page variable to bank 0}
 cmp ah,0
 jne @fail

 mov ax,1
 jmp @out
@fail:
 mov ax,0           {return fail or succes to function}
@out:
end;


Now lets plot a pixel, shall we ??


PROCEDURE   VESAputpixel(x, y : WORD; c : BYTE);
VAR
  bank   : WORD;
  offs   : longint;
BEGIN

  offs := LONGINT(y) * VESAModeInfo.Xresolution + x;
  bank := offs SHR (16-BankShiftModifier);
  offs := offs - (bank SHL (16-BankShiftModifier));

  IF bank <> Cur_page THEN {page = global var - active page}
  BEGIN
    cur_page := bank;
    ASM
      Xor bl,bl
      mov dx,bank
      call [VESAModeInfo.BankSwitch]
    END;
   END;

  ASM
    MOV AX, $A000
    MOV ES, ax
    MOV DI, WORD(offs)
    MOV AL, c
    MOV ES:[DI], AL
  END;
END;


Now in this routine I use a variable called BankShiftModifier. This is because
the routine assumes a granularity of 64K when using the shr 16 and shl 16
statements. With a granularity of fx. 4K it should be shr 12 and shl 12.
So we'll have to calculate the modifier when we recieve the information about
the granularity from function 01h.

Case VESAModeInfo.WinGranularity of
 1  : BankShiftModifier := 6;
 2  : BankShiftModifier := 5;
 4  : BankShiftModifier := 4;
 8  : BankShiftModifier := 3;
 16 : BankShiftModifier := 2;
 32 : BankShiftModifier := 1;
 64 : BankShiftModifier := 0;
end;    {With granularities smaller than 64 but with page-size 64K the
         banknr for each full segment of SVGA graphic will not be 0,1,2,3
         but multiplied with BankMult ( 2^BankShiftModifier for easy bit
         shifting)                                                         }
BankMult := 64 div VESAModeInfo.WinGranularity;

Now, I know of granularities of 64K, 32K and 4K... But it can't hurt to have
the other posibilities as well... you never know with SVGA.
The Variable BankMult is used for our next routine - The clear routine.
As mentioned when discussing function 05h the banks won't be 0,1,2,3,4 and so
on when dealing with granularities smaller than 64K. Each Segment filled is
the same as 16 banks filled with a granularity of 4K

So here goes : The ClearScreen routine :


PROCEDURE ClearScreen(color : byte);
VAR
 Xres, Yres : longint;
 number_of_segments : longint;
 i : integer;
BEGIN
 Xres := VESAModeInfo.Xresolution;
 Yres := VESAModeInfo.Yresolution;
 number_of_segments  := Round(((Xres *  Yres)/65536)+0.5)-1;

 for i := 0 to number_of_segments do
  BEGIN
   SetBank(i*BankMult);
    ASM
     mov     cx, 32768;
     mov     ax,$A000
     mov     es,ax
     xor     di,di
     mov     al,[color]
     mov     ah,al
     rep     stosw
    END;
  END;
END;


And now the final two small routines :

Procedure SetLogicalScanline(width : word);
Assembler;
asm
 mov ah,4Fh
 mov al,06h
 mov bl,0
 mov cx,width
 int 10h
end;


Procedure SetScreenPosition(x,y : word);
Assembler;
asm
 mov ah,4Fh
 mov al,07h
 xor bx,bx
 mov cx,[x]
 mov dx,[y]
 int 10h
end;




LAST REMARKS
-------------

Well, that's about all for now.
Hope you found this doc useful - and BTW : If you DO make anything public using
these techniques please mention me in your greets or where ever you se fit.
I DO love to see my name in a greeting :=)

Now, this done you should all be able to code some pretty stunning SVGA games/
demos. We can no longer say anything else : SVGA is the future of all graphic
programming.
A few things to take note of : All the routines presented here in this text are
very general. The ClearScreen will do any resolution - same thing with the
putpixel routine. But I have sacrificed speed to achieve this! If you use the
routines in games/demos where you KNOW what resolution it will run at you
should modify the routines and optimize them to meet those specific resolutions!
Fx. I use a normal * to calculate the offset in the putpixel. This should off
cause be changed to shl's when dealing with a predefined gfx-mode.


But what now ??
If you have any good ideas for a subject you wish to see a tutorial on please
mail me. If I like the idea (and know anything about it :)  ) I'll write a
tut on it.
I have spoken of a tutorial on interrupts for quite a while now....
maybe it'll come out soon - but I also plan on doing some graphical effects.
MAIL ME!!


Keep on coding...

  Telemachos - August '97.
