;----------------------------
;     From: Albert Brown
;  Subject: 640x480x256 PutPixel

Q.

 Does anyone know of a quick, small routine that puts a
 pixel on the screen with X, Y coordinates, and COLOR???

A.

   I am assuming you mean the default chained mode.

   These formulas should work for every 256 colour mode:

        Pixel Address = (Y * Row Size [640 in this case]  ) + X

   Now that we have the address we can find the bank number.

        Bank = Pixel Address / 64K (or whatever your bank size is)

   Now that we have the bank number we can find the Pixel Offset within
   the bank.

        Pixel Offset = Pixel Address - (64K * Bank Number)

   Here is a simple routine that will display a point on the screen.
   The inputs are:

        X               The X coordinate to display a pixel
        Y               The Y coordinate to display a pixel
        Colour          The colour value you wish to display
        BytesPerRow     The number of bytes per row.
                                ex. 640x480 will have 640 bytes per row
                                ex. 320x200 will have 320 bytes per row
        LastBank        This is the last bank that was used.
                        It should be initialized to zero once and should be
                        kept as a static variable. This is the only value
                        that gets returned.

   The following code is in Assembly. Plotting a 256 colour pixel just
   happens to work out perfectly in assembly, as you will see. Pascal and C
   accept assembly in there code so all you have to do is add your function
   header and compile as normal. This routine only works for 64K banks.
   Therefore you should switch your card to 64K bank mode.




   Put your Pascal or C procedure/function header thingeroonie here


   Function Name: PlotPointInAnyNormal256ColourModeWithBankSwitching

   IN
        X,Y,Colour,BytesPerRow,LastBank
   OUT
        LastBank
   REGISTERS DESTROYED
        A whole lot if you don't save them.


;  Save your registers here as well


   mov  ax,0a000h
   mov  es,ax           ; Load the 64K Graphic segment for the 256 colour mode

   mov  ax,Y            ; Get the Y value
   mov  bx,BytesPerRow  ; Get the number of bytes per row or Row Size
   mul  bx              ; Multiply Y by BytesPerRow... (Y * RowSize)
                        ; The result of Y * Row Size will be placed in DX:AX

   add  ax,X            ; Add the X value... (Y * RowSize) + X

   adc  dx,0            ; If the result of addition exceeded the limits of
                        ; the register then place the remainder in DX.

;  The AX register is only 16 bits wide which means that the largest
;  number available is 65535 or FFFF. If we have two 16 bit numbers added
;  together the  result would overflow.
;       FFFF
;     + FFFF
;      -----
;      1FFFE
;     /\
;  I.E. the largest number that we can store in AX register, in this case, is
;  FFFE and the carry bit is set meaning that we have an error. What we would
;  have liked is that extra one be added to DX (the top 16 bits part of our
;  32 bit result).... AX is the lower 16 bits.
;  We are in luck, the ADC instruction does just that.
;       adc     dx,0   is saying   DX = DX + Carry Bit (1) + 0
;  If the result did not overflow (did not go over FFFF) then the carry bit
;  would be 0... DX = DX + Carry bit (0) + 0. Which is the same as adding
;  zero to DX. Which is what we want.

;  Now we that we have the Pixel Address we can find the Bank Number.
;  We know that a Bank Size is 64K which is 16 bits which is a full register
;  therefore our bank number is just DX. DX tells us how many times the AX
;  register went over FFFF (or how many times it went over our bank size).
;
;  To prove this take the number in DX multiply it by 64K and add the
;  number in AX and you will have Pixel Address.
;
;  You probably guess it by now. The AX register holds the offset into the
;  bank. So all we have to do is tell the hardware what bank we want and
;  the offset from this bank or, in other words, the offset from 64K * bank
;  size.

   mov  di,ax           ; Store the offset in this register because we
                        ; will have to use the AX register to access the port
                        ; or for calling the VESA routine. And this is one
                        ; of the few registers that we can do indexing on
                        ; with the 8086.
   cmp  dx,LastBank     ; Did we change the bank changed from last time?
   jz   SameBank        ; Nope, then don't switch bank.


;  The above could be a cmp  dl,LastBank which would be a byte compare rather
;  then the word

   mov  LastBank,dx     ; Save the current bank number

;  Put your own Bank switch code here or your own call to the VESA Bank
;  routine. DX or DL holds the bank you wish to switch to.



SameBank:

   mov  al,Colour       ; Get the colour value you wish to display
   mov  es:[di],al      ; And display it.



; Restore the registers that you saved

   ret                  ; Return to the caller

Pascal or C procedure/C closure thingy


   Optimizations can be made for the above code. For example, instead
   of doing a mul  bx. You can replace that with a look up table. This can
   easily be changed in the routine. Replace BytesPerRow with the address for
   that particular look up table. i.e. x320 would have its own look up table
   and so would x640.
   An Example for the 8086 of getting the result
        mov  di,Y            ; Get the Y value
        mov  bx,LookUpTable  ; Get the address of one of the look up tables
        shl  di,1            ; Since each element in the index table is
        shl  di,1            ; 32 bits long (4 bytes) we must multiply Y by
                             ; 4 to get the correct answer. This is just
                             ; a fast way of multiplying Y by 4.
        mov  dx,[bx+di]      ; Get the high 16 bit part
        inc  di              ; Point to the next value
        mov  ax,[bx+di]      ; Get the low 16 bit part

   An Example for the 286

        mov  di,Y            ; Get the Y value
        mov  bx,LookUpTable  ; Get the address of one of the look up tables
        add  di,di           ; Another way of multiplying by 4. Faster then
        add  di,di           ; two shifts or a shl,2 on the 286 (I think).
        mov  dx,[bx+di]      ; Get the high 16 bit part
        inc  di              ; Point to the next value
        mov  dx,[bx+di]      ; Get the low 16 bit part

   An Example for the 386

        movzx   edi,Y           ; Clear the 32 bit register and get the Y
                                ; value
        movzx   ebx,LookUpTable ; Clear the 32 bit register and get the
                                ; Look Up Table address
        mov     dx,[ebx+edi*4]  ; Get the high 16 bit part
        mov     ax,[ebx+edi*4+1] ; Get the low 16 bit part

   However, if you are coding for the 386 the whole display routine can be
   shortened and optimized far more than you can on the 286. For example,
   I would just use one memory move instead of two for the above.

        mov     edx,[ebx+edi*4] ; Get the high 16 bits and the low 16 bits
                                ; in one shot.

 * Origin: Burst Mode (1:163/528)
