;----------------------------
; 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)